コース目次 / 第1章
旗A — ソースに残された、手がかり
arena-webを起動し、HTMLに埋め込まれたJavaScriptのコメントから隠しエンドポイントの存在を見つけ、直接アクセスして旗を取得します。
標的を立てる
arena-web.js として保存します。
// arena-web.js — CTF練習用ギャラリーアプリ。127.0.0.1:4500。
const http = require('node:http');
const PAGE = `<!doctype html><meta charset="utf-8"><body>
<h1>Arena Gallery</h1>
<p>ようこそ。画像一覧は /api/photos から取得しています。</p>
<script>
// TODO: 本番公開前に /api/internal/debug-status を消す
fetch('/api/photos?role=guest').then(r=>r.json()).then(d=>console.log(d));
</script>
</body>`;
http.createServer((req, res) => {
const url = new URL(req.url, 'http://127.0.0.1:4500');
res.setHeader('Content-Type', 'application/json; charset=utf-8');
if (url.pathname === '/') { res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.end(PAGE); return; }
if (url.pathname === '/api/internal/debug-status') {
res.end(JSON.stringify({ status: 'debug mode', flag: 'flag{????????????????}' }));
return;
}
if (url.pathname === '/api/photos') {
const role = url.searchParams.get('role') || 'guest';
if (role === 'admin') {
res.end(JSON.stringify({ photos: ['public1.jpg', 'public2.jpg'], flag: 'flag{????????????????}' }));
return;
}
res.end(JSON.stringify({ photos: ['public1.jpg', 'public2.jpg'] }));
return;
}
res.writeHead(404); res.end(JSON.stringify({ error: 'not found' }));
}).listen(4500, '127.0.0.1', () => console.log('arena-web: http://127.0.0.1:4500 (Ctrl+C で停止)'));
node arena-web.js
画面を見るだけでは、何も分からない
curl http://127.0.0.1:4500/
ブラウザで見ても、「Arena Gallery」という見出しがあるだけです。でも、送られてきたHTMLの中身には、目に見えないヒントが埋まっています。
ソースを、じっくり読む
curl -s http://127.0.0.1:4500/ | grep -A2 script
<script>
// TODO: 本番公開前に /api/internal/debug-status を消す
fetch('/api/photos?role=guest').then(r=>r.json()).then(d=>console.log(d));
</script>
コメントに、「本番公開前に消すはずだった」痕跡が、消されずに残っていました。/api/internal/debug-status という、画面のどこにもリンクの無いパスです。
直接、叩いてみる
curl http://127.0.0.1:4500/api/internal/debug-status
{"status":"debug mode","flag":"flag{c1d6160d52924a75}"}
取れました。(値はあなたの環境で変わります。)開発中の消し忘れが、そのまま旗になりました。sec-audit で「読むだけで穴を見つける」と学んだ視点が、ここでは「配られたページのソースを読むだけで、隠された機能を見つける」という形で活きています。
持ち帰る一言
画面ではなく、送られてきたものを読む。 HTMLやJSのコメント、fetchが呼んでいる先——開発者が消し忘れた痕跡は、思ったより頻繁に残っています。次は、もう一つの旗——パラメータを信じた昇格です。
こうなっていればOK
卒業まであと2章です。
この章はまだ完了していません。
保存できませんでした(プライベートブラウズ中かもしれません)。この端末に進捗は残りませんが、先へは進めます。