どちらも「Webの信頼」を悪用しますが、悪用する信頼の向きが逆です。XSS はサイトがユーザーを信頼しすぎること、CSRF はサイトがブラウザを信頼しすぎることを突きます。ここが腹落ちすれば、対策が別物になる理由も見えます。
他人のブラウザで、悪意あるスクリプトを実行させる。サイトが受け取った入力を、無害化せずページに埋め込んでしまうのが原因。
ログイン済みユーザーに、意図しない操作を送信させる。ブラウザが Cookie を自動送信する仕組みに便乗する。
2つの攻撃は、Webアプリが暗黙に置いている「信頼」の別々の穴を突きます。まず絵で捉えます。
XSS はコードを盗ませる(実行させる)攻撃、CSRF は操作をさせる攻撃。XSS で奪ったスクリプト実行権を使って CSRF 対策トークンごと盗む、という合わせ技もあり、XSS があると CSRF 対策は無力化されがちです。だから優先度は XSS の根絶が上。
XSS の根っこは1つです。ユーザーが入力した文字列を、HTML/JavaScript として解釈される場所へ、無害化せずに出力してしまうこと。ブラウザは「データ」と「コード」を見た目で区別できないので、<script> と書かれていればコードとして実行します。
たとえば掲示板が、名前欄の入力をそのまま表示するとします。
// サーバーが受け取った名前をそのまま埋め込む(危険)
echo "<p>こんにちは、" . $_GET['name'] . "さん</p>";
ここに name=<script>alert(document.cookie)</script> を渡すと、出力はこうなります。
<p>こんにちは、<script>alert(document.cookie)</script>さん</p>
ブラウザはこの <script> を「表示する文字」ではなく「実行するコード」と解釈します。これで攻撃者の JavaScript が被害者のブラウザの、そのサイトの権限で動きます。Cookie(セッション)の窃取、なりすまし、フォーム改ざん、キーロガー設置まで何でもできます。
「スクリプトがどこを経由して届くか」で分類されます。
投稿内容などとしてサーバーに保存され、そのページを開いた全員に配信される。掲示板・プロフィール・コメント欄が典型。被害が広く、最も危険。
URL のパラメータなどに仕込まれ、サーバーがその場で応答に echo(反射)する。攻撃用 URL を踏ませることで成立するので、フィッシングと組み合わせて使われる。
サーバーを経由せず、ブラウザ内の JavaScript が location.hash などの値を innerHTML に書き込むことで発火する。サーバーのログに残りにくく、フロント側の実装が原因。
データを HTML に出す瞬間に <→<、>→>、"→" へ変換する。こうすれば <script> は「実行されるタグ」ではなく「画面に出る文字列」になる。出力先(HTML本文/属性値/JavaScript内/URL)ごとに正しいエスケープが違う点に注意。
React(JSX の {})、Vue の {{ }}、各テンプレートエンジンはデフォルトでエスケープする。dangerouslySetInnerHTML や v-html のような自動エスケープを外す機能を安易に使わない。
「どこから来たスクリプトを実行してよいか」をブラウザに宣言するヘッダ。インラインスクリプトを禁止すれば、埋め込まれた <script> が実行されない。エスケープ漏れの保険になる多層防御。
セッション Cookie に HttpOnly 属性を付けると、JavaScript の document.cookie から読めなくなる。XSS が起きてもセッション窃取だけは防げる被害軽減策。
想定外の文字を入口で弾く。ただし入力対策だけに頼らない——本丸はあくまで出力時のエスケープ。入力チェックはすり抜ける経路が多い。
CSRF の根っこは、ブラウザの親切な仕様です。あるサイト宛のリクエストには、そのサイトの Cookie が自動で添付される——たとえそのリクエストが、別の(罠)サイトのページから発生していても。
あなたが銀行サイトにログイン中(=セッション Cookie を保持)だとします。別タブで開いた罠サイトに、こんな仕込みがあったら——
<!-- 罠サイトのHTML。画像に見せかけて送金リクエストを発射 -->
<img src="https://bank.example/transfer?to=attacker&amount=100000">
ブラウザはこの src を読みにいくとき、bank.example の Cookie を自動で付けて送信します。銀行サーバーから見れば「正しいセッション Cookie 付きのリクエスト」なので、本人の送金指示と区別できません。被害者は画像が表示されないな、と思うだけで、裏で送金が実行されます。
攻撃者は Cookie の中身を盗む必要がない。ブラウザに「代わりに送らせる」だけ。だから CSRF は「リクエストが本当に本人の意思か」を確かめれば防げる。
フォーム表示時に、推測不能なランダム値をページに埋め込み、送信時に照合する。罠サイトはこのトークンを知り得ないので偽リクエストを作れない。Synchronizer Token Pattern。多くのフレームワークに標準搭載。
Cookie に SameSite=Lax(or Strict)を付けると、別サイト起点のリクエストには Cookie が付かなくなる。CSRF の前提そのものを崩す強力な一手。現代ブラウザは Lax がデフォルト寄り。
サーバー側で、リクエストの Origin/Referer ヘッダが自サイトかを確認する。別サイト起点なら拒否。トークンと併用する多層防御として有効。
送金・パスワード変更など影響の大きい操作では、パスワード再入力や確認ステップを要求する。自動発射の一撃では通らなくなる。
なお <img> や単純フォームで発射できるのは「単純リクエスト」。Content-Type: application/json を要求する API などは、そもそもプリフライト(CORS)が挟まって罠サイトから叩きにくい。ただしこれは副次的効果で、CSRF 対策そのものではない点に注意。
| 観点 | XSS | CSRF |
|---|---|---|
| 正式名 | Cross-Site Scripting | Cross-Site Request Forgery |
| 何をされる | ブラウザで悪意のスクリプトを実行される | 本人の権限で意図しない操作を送信される |
| 悪用する信頼 | サイト → ユーザー入力 | サイト → ブラウザ(Cookie) |
| 攻撃者が奪うもの | スクリプト実行権(Cookie・DOM 何でも) | 操作の実行(Cookie 中身は見えない) |
| 主因 | 出力時の無害化漏れ | リクエスト出所の未検証 |
| 主対策 | 出力エスケープ・CSP・HttpOnly | CSRFトークン・SameSite・Origin検証 |
XSS = サイトが悪いスクリプトを「配ってしまう」/ CSRF = 本人に操作を「させてしまう」。XSS はデータをコードにさせない(エスケープ)、CSRF はリクエストの本人性を確かめる(トークン)。守る層が違う。