コース目次 / 第1章

旗A — ソースに残された、手がかり

arena-webを起動し、HTMLに埋め込まれたJavaScriptのコメントから隠しエンドポイントの存在を見つけ、直接アクセスして旗を取得します。

第1章 / 全4章目安 約10分この章のゴール: ページのソースから隠しエンドポイントを見つけ、旗を取得できる

標的を立てる

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章です。

この章はまだ完了していません。