仕組みから学ぶ Web
応用読了目安 16#XSS#CSRF#SQLインジェクション#脆弱性

XSS・CSRF・SQLi — データとコードの境界が壊れる

3つの代表的な脆弱性は、どれも「データとして扱うべきものが命令として解釈される」という同じ形をしている。

この記事の進み方

What — 3つに共通する形

一見まったく違う3つの脆弱性ですが、データとして扱うべきものが、命令として解釈されるという同じ構造を持っています。

Compareどこで境界が壊れるか

入力された文字列が、HTML や JavaScript として解釈される。

// 入力
検索キーワード: <script>steal()</script>

// 出力される HTML
<p>"<script>steal()</script>" の検索結果</p>
     ~~~~~~~~~~~~~~~~~~~~~~~~
     ブラウザはこれを「データ」ではなく
     「実行すべきスクリプト」と解釈する
壊れる境界
HTML / JS パーサ
攻撃者ができること
その人として何でも
根本対策
出力時のエスケープ
多層防御
CSP
  • HttpOnly Cookie は「盗み出す」ことは防ぐが、「ブラウザ内から使う」ことは防げない。
  • 被害範囲は、そのユーザーができることすべて。管理者が踏むと致命的になる。

XSS は HTML パーサ、SQLi は SQL パーサ、CSRF はブラウザの自動送信。壊れる場所は違うが、いずれも「文字列の連結」か「意図の欠落」が原因になっている。

XSS の被害範囲

FigureXSS が成立してから何が起きるか
  1. 1① Cookie の持ち出しdocument.cookie を外部へ送る重い
  2. 2② API の代理実行そのユーザーとして API を叩く重い
  3. 3③ 画面の書き換え偽のログインフォームを表示する重い
  4. 4④ 入力の記録キー入力やフォーム内容を送信する重い
  5. 5⑤ 持続化投稿やプロフィールに仕込む重い

対策の要は「そもそも実行させない」(出力エスケープ)こと。実行された後の被害を減らす対策(HttpOnly・CSP)は重要だが、あくまで二重の備え。

1/5
① Cookie の持ち出しHttpOnly が付いていれば、この経路は塞がれる。付いていなければセッション ID がそのまま外部サーバーへ送られる。

スクリプトが実行された時点で、そのページでできることはすべて攻撃者ができる。HttpOnly は①を防ぐが、②以降は防げない。

確認 — ここまで読めたか

XSS・CSRF・SQLi に共通する構造はどれですか?

まず選ぶ(解答例は a〜d の記号で説明します)

Why — なぜ「エスケープ漏れ」が繰り返されるのか

3つの脆弱性がなくならない理由は、安全な書き方より危険な書き方のほうが短くて自然だからです。

// 危険だが、自然に書ける
const sql = `SELECT * FROM users WHERE id = ${id}`;
element.innerHTML = `<p>${keyword}</p>`;

// 安全だが、一手間かかる
db.query('SELECT * FROM users WHERE id = ?', [id]);
element.textContent = keyword;

人間が毎回正しく選ぶことに期待する設計は、必ずどこかで破れます。だから現代のフレームワークは、既定を安全側にする方向に進みました。

  • React・Vue は、変数を埋め込むと自動でエスケープする。危険な書き方には dangerouslySetInnerHTML のようなわざと長くて怖い名前が付いている
  • ORM やクエリビルダは、既定でプレースホルダを使う
  • 主要なフレームワークは、CSRF トークンを既定で有効にする

安全な道が既定で、危険な道に進むには明示的な操作が要る。 この形をとることで、うっかりミスが構造的に減ります。

CSRF はなぜ成立するのか

CSRF が成立する条件は2つだけです。

  1. ブラウザが Cookie を自動で送る(同一オリジンポリシーは「送信」を止めない)
  2. サーバーがリクエストを「本人の意図」だと信じる

CSRF トークンは、2番目を壊します。攻撃者のページはあなたのページの中身を読めないので、フォームに埋め込まれたランダムなトークンを知りようがありません。

<form method="POST" action="/transfer">
  <input type="hidden" name="_csrf" value="ランダム値">
  ...
</form>

サーバーはこのトークンがセッションのものと一致するかを確認します。攻撃者は正しい値を書けないので、リクエストは弾かれます。

「読めないこと」を利用して「本人性」を作る——同一オリジンポリシーの上に立った、簡潔な仕組みです。

確認 — ここまで読めたか

HttpOnly を付けていれば XSS の被害は防げますか?

まず選ぶ(解答例は a〜d の記号で説明します)

演習 — まず自分で判断する

解説を読む前に、まず自分で判断してみてください。ここで一度詰まっておくと、 次の節の判断軸が「なるほど」ではなく「そう来たか」に変わります。

演習 — 設計判断を問う

この4つの指摘に、どの順で対応しますか?

与えられた条件
  • 社内の勤怠管理システム。全社員 1200 人が使い、人事部が全員のデータを見られる
  • ① 申請コメント欄の内容が、承認画面で innerHTML により表示されている
  • ② 検索結果のソート順を ?sort=created_at+desc のようにクエリから受け取り、SQL に連結している
  • ③ 「申請を取り消す」が GET /applications/123/cancel で実装されている
  • ④ セッション Cookie に HttpOnly が付いていない
  • リリースまでに全部は直せず、まず2つだけ直せる状況
この軸で考える
  • · 攻撃が成立したとき、最も権限の高い人に届くのはどれか
  • · 1つの脆弱性が、他の対策を無効化してしまう関係はあるか
  • · 被害が自動的に広がる(persistent な)ものはどれか
まず選ぶ(解答例は a〜d の記号で説明します)

演習 — 説明できるか

「入力値のバリデーションを強化すれば脆弱性は防げる」と言う同僚に、それでは足りない理由を説明してください

与えられた条件
  • 相手は禁止文字のフィルタを追加しようとしている
  • 対象は検索フォームと、その結果表示
  • 何を入れるべきかまで示したい

読み終わりましたか?

読了にすると、これを前提とする記事がロードマップで開放されます。