コース目次 / 第1章
反射XSS — 攻めて、直して、再検証する
検索欄の入力がエスケープされずに反射される穴を、参照エクスプロイトで突き、server.mjs を直して、同じ攻撃が通らなくなるのを確かめます。
この章は攻撃をします(自作環境専用)。攻撃対象は、前章で自分のパソコンに起動した同梱の shanai-memo だけです。ここで学ぶ技術を、自作環境・自分の許可環境・CTF の外にある相手に試してはいけません(法と倫理)。
反射XSS(Reflected Cross-Site Scripting)は、Web でいちばん多い穴のひとつです。仕組みは一言で言えます——利用者から受け取った入力を、エスケープせずにそのまま HTML に埋め込んで返す。すると入力に紛れ込んだスクリプトが、そのページを開いた人のブラウザで動きます。
法と倫理ゲート
手を動かす前に
このコースは攻撃の技術に触れます。実習してよい相手は、次の3つだけです。
- (a) 自分で作った練習用のやられ環境(このサイトが配る自作ターゲット)
- (b) あなた自身の許可された環境(自分の Thousand SKY・secret-notes-ctf など)
- (c) CTF 競技の中(規約の範囲で)
実在する第三者のシステムを許可なく触ることは、不正アクセス禁止法に触れます。 このゲートは、その線引きを毎回の入口で確かめるためのものです。詳しくは「セキュリティの法と倫理」へ。
同意すると、この先の内容が表示されます。
穴のある場所を読む
shanai-memo の検索は、vulnerable/server.mjs の /search にあります。該当部分はここです。
if (pathname === '/search') {
const q = url.searchParams.get('q') || '';
const hits = notes.filter((n) => n.title.includes(q) || n.body.includes(q));
res.end(
page(
'検索',
`<h1>検索</h1><p>「${q}」の検索結果: ${hits.length}件</p>` + // ← ここが穴
...q(利用者が検索欄に入れた文字列)を、何の加工もせずに HTML の文字列へ連結しています。q に <img> タグを入れれば、それはタグとして解釈されます。
攻める
2つ目のターミナルで、参照エクスプロイトを走らせます。
cd sec-web-basics
node exploit/xss.mjs[XSS] 成立: ペイロードが未エスケープで反射されました(HTMLとして解釈される)このエクスプロイトは、次のようなペイロードを /search?q=... に送っています。
<img src=x onerror="document.title=String.fromCharCode(88,83,83)">src=x の画像は必ず読み込みに失敗するので、onerror に書いた JavaScript が動きます。手で確かめるなら curl でも見えます。
curl -s "http://127.0.0.1:4599/search?q=%3Cimg%20src%3Dx%20onerror%3Dalert(1)%3E"返ってきた HTML の中に、送ったタグがそのままの形で入っています。
<p>「<img src=x onerror=alert(1)>」の検索結果: 0件</p>< が < になっていない——つまりブラウザはこれをタグとして解釈します。これが反射XSSの発火点です。実際の攻撃では、この URL を別の利用者に踏ませることで、その人のブラウザで任意のスクリプトを動かします。
直す
直し方の芯は一つ。HTMLに出す前に、エスケープする。&<>"' を実体参照(& < など)に置き換えれば、入力は「タグ」ではなく「ただの文字」として表示されます。
vulnerable/server.mjs の先頭近く(page 関数の前)に、エスケープ関数を足します。
function escapeHtml(s) {
return String(s)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}そして /search の穴の行を、q をエスケープしてから埋め込む形に変えます。
`<h1>検索</h1><p>「${escapeHtml(q)}」の検索結果: ${hits.length}件</p>` +
'<ul>' +
hits.map((n) => `<li>${escapeHtml(n.title)}</li>`).join('') +& を最初に置換するのが大事です。先に < を < にしてから & を処理すると、二重エスケープで表示が壊れます。順番に意味があります。
再検証する
サーバを止めて(1つ目のターミナルで Ctrl+C)、もう一度起動します。そして同じエクスプロイトを走らせます。
node exploit/xss.mjs[XSS] 防御されています: ペイロードはエスケープされ、コードになりませんcurl で見ると、今度は < が < になっています。
<p>「<img src=x onerror=alert(1)>」の検索結果: 0件</p>同じ入力が、タグではなく文字として表示されるようになりました。「成立」が「防御されています」に変わった——これが攻撃 → 修正 → 再検証の一周です。
持ち帰る一言
入力は信じない。出す前に、出す文脈に合わせて整える。 ここではHTMLに出すのでHTMLエスケープでした。文脈がURL属性やJavaScript内なら、必要なエスケープは変わります。「どの文脈に出すか」を意識するのが、XSS対策の芯です。
次章は、URLの数字を変えるだけで他人のデータが見えてしまう穴——IDORです。
こうなっていればOK
卒業まであと3章です。
この章はまだ完了していません。
保存できませんでした(プライベートブラウズ中かもしれません)。この端末に進捗は残りませんが、先へは進めます。