はじめに
前回はCSRFについて学んだが、その特徴として攻撃者は「ユーザーの認証情報を知らない」「ブラウザとサーバーの通信の中身も読めない」という点があった。
攻撃者にできるのは、ログイン済みのブラウザに、本人の意図しないリクエストを送らせることまでだった。
もちろんOWASPtop10に掲載されている攻撃方法は多彩であり、その中には
より直接的にデータを書き換えたりユーザーのブラウザの中で動く攻撃がある
今回はそのうちの一つXSSについてまとめ基本的なサイバーセキュリティの基礎知識を固めていきたい。
能動的攻撃・受動的攻撃
XSSの説明に移る前に二つの違いを明確にしておきたい。
受動的攻撃とは
攻撃者が罠を仕掛けユーザーがそれを踏むのを待つ攻撃だ
前回学んだCSRFもこれに分類される
例えば
攻撃者が自分のサイトにリクエストを送るコードを置いておく罠が挙げられる
<form action="https://example.com/password/change" method="POST">
<input type="hidden" name="newPassword" value="attacker123">
</form>
<script>document.forms[0].submit()</script>
ユーザーがこのスクリプトの埋め込まれたページを開くだけでCookieをつけたリクエストなどが送られる。
認証情報を攻撃者は知らないまま本人の意図しないリクエストを遅らせることができる。
XSSはこちらに分類され後述する。
能動的攻撃とは
攻撃者がサーバーに直接リクエストを送る攻撃であり、そこにユーザー操作は介在しない。
入力欄やURLに細工した値を送り込み、サーバーがその値をコマンドの一部として実行してしまうことで成立してしまう攻撃だ。
SQLインジェクションはこちらに分類される。
能動的・受動的と分類できるがどちらも
データと命令が混ざると言い換えることでイメージが掴みやすいだろう。
XSSの概要
XSSとは悪意あるスクリプトを他のユーザーのブラウザで実行させる攻撃だ
昨今サイバー攻撃で頻繁に見る機会が増えているフィッシングメールを入り口に
ユーザーに罠のURLを踏ませるもしくは汚染されたページにアクセスさせることで成立する。
攻撃の流れ
- 攻撃者が、スクリプトを含むURLを作る、またはサイトの入力欄にスクリプトを書き込む
- ユーザーが、罠のURLを開く、または汚染されたページを見る
- サイトが、入力値をエスケープせずに画面へ出す
- ブラウザは、サイト本来のスクリプトと攻撃者のスクリプトを区別できず、どちらも実行する
- 攻撃者のスクリプトが、そのユーザーの権限で動く
※エスケープとは画面内で描画される<、>、 &、 "などの特別な意味を持つ文字を無害な文字列へ変換すること。
例えば<script>と画面に出してしまうとそのままスクリプトが動いてしまう。
XSS攻撃の種類
XSSのスクリプトを実行させる手法も多岐にわたる
反射型
攻撃者がスクリプトを含むURLを作りメールやSNSでユーザーが踏むことで成立する。
例
https://example.com/search?q=<script>悪意のあるコード</script>
格納型
コメント欄やプロフィール欄などで攻撃者が入力した内容がDBに保存される。
サイトがそれをエスケープしないまま描画しユーザーがアクセスした場合、ブラウザがスクリプトを実行し攻撃が成立する
例
<li>いい記事ですね<script>alert('XSS')</script></li>
DOM型
サイトのJavaScriptがURLなどから値を読み込みHTMLとして画面に挿入することで成立する
特に注意が必要だと感じる特徴があり、サーバーを通らずブラウザの中で完結するため、サーバー側のエスケープでは防げない。
例
https://example.com/#<img src=x onerror=alert('XSS')>
※このURLを開くとサイト側のコードが#以降の値を画面に挿入しonerrorが実行される。
反射型と格納型はサーバーが危険なHTMLを作って返すのに対し、DOM型はサーバーは普通のHTMLを作って返すだけ。
サーバー側のログにも残らないエスケープでも防げないDOM型が一番厄介だと感じた。
XSSに対する防御法
防御策は二点ある
原因を取り除くか、被害を抑えるかだ。
順に見ていこう
原因を取り除く方法
出力時のエスケープを徹底する
反射型や格納型では画面に描画されなければいいのでエスケープによる変換を徹底するだけで対応できる。
初めて知ったがそもそもReactなどのフレームワークは通常の書き方で自動エスケープが働くそうだ。
工夫が必要なのはDOM型で、JavaScriptが値を画面に入れる処理の経路を
データと命令で分けて書くことを意識しなければならない
例えばinnerHTMLをtextContentに書き換えて画面に入れる
// NG: 値をHTMLとして解釈する
element.innerHTML = name;
// OK: 値をただの文字として扱う
element.textContent = name;
name が <img src=x onerror=alert('XSS')> の場合、innerHTML ではタグが作られてスクリプトが動く。
textContent では、この文字列がそのまま画面に表示されるだけで、実行されない。
innerHTMLを例にとったがevalやdocument.writeのように値をコードとして解釈する処理いずれにおいても工夫が必要だ
Sanitizer API
同じく原因を除去するのがブラウザに標準で入っているサニタイズ機能だ
innerHTMLの代わりにsetHTML()を使うことで、スクリプトを実行できる部分を取り除き、残りのHTMLを挿入することができる
例として
// NG: 値をHTMLとして解釈する
element.innerHTML = userHtml;
// OK: 危険な部分を取り除いてから挿入する
element.setHTML(userHtml);
太字や画像タグなどを残し「HTMLを生かしたい」場合に使用できる。
※2026年10月末時点の各ブラウザの対応状況を把握しておく必要がある
Firefox: 148(2026年2月)から使える
Chrome、Edge: web.dev は146から対応
Safari: 未対応
対応していない場合ライブラリのDOMPurifyで代用できる。
被害を抑える方法
セキュリティに無知な自分も含めて、上記の防ぎ方を知らないないし設定し忘れることもあるだろう。
だからこそ多層的に防御して被害を抑える方法を組み込み攻撃者にできることを減らさなければならない。
HttpOnly Cookieを使う
CookieにHttpOnlyをつけると、JavaScriptからCookieを読み取れなくなる。XSSが成立しても、セッションIDなどのCookieを盗まれにくくなる。
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax
ただし、スクリプト自体は動くため、画面の読み取りや、ユーザーの権限での操作は防げない。
前回のCSRFでの対策はあくまで「別サイトから来たリクエスト」に対する防御策だった。
CSPで実行できるスクリプトを制限する
CSP(Content Security Policy)は、実行を許可するスクリプトの出どころをサーバーが指定する仕組みだ。
セキュリティヘッダーにこう記述しよう
Content-Security-Policy: script-src 'self'
この設定では、自分のサイトから配信されたスクリプトだけが実行される。HTMLに直接書かれたスクリプトやイベント属性(onerror など)は、ブラウザが実行しない。エスケープ漏れがあっても、攻撃のスクリプトが動かない可能性がある。
HttpOnlyはCookieの窃取を防ぐがスクリプト操作自体は防げないという欠点があったり
CSPでの制限も厳しくすればするほど堅牢になるが、
外部サービス連携ごとの設定の手間や画面が壊れやすくなることとのトレードオフになっている。
あくまでこれらの対策は保険として考え、
原因を取り除くエスケープとサニタイズを本筋として捉えるのが重要だろう。
防御策のまとめ
| 分類 | 防御策 | 役割 | 注意点 |
|---|---|---|---|
| 原因 | 出力時のエスケープ | 反射型・格納型で、値を無害な文字列に変換する | Reactなどのフレームワークは、通常の書き方なら自動で行う |
| 原因 | 安全な書き方(textContent など) |
DOM型で、値をただの文字として画面に入れる |
eval や document.write など、値をコードとして解釈する処理にも工夫が必要 |
| 原因 | Sanitizer API(setHTML()) |
HTMLを生かしたいときに、スクリプトを実行できる部分を取り除いて挿入する | 対応ブラウザが限られる。未対応ならDOMPurifyで代用する |
| 被害を抑える | HttpOnly Cookie | Cookieの窃取を防ぐ | スクリプトは動くため、画面の読み取りや、ユーザーの権限での操作は防げない |
| 被害を抑える | CSP | 許可していないスクリプトの実行を止める | 厳しくするほど堅牢だが、外部サービス連携ごとの設定の手間と、画面の壊れやすさとのトレードオフになる |
重要なのは原因をサニタイズや安全な書き方およびエスケープで取り除くこと!
HttpOnlyとCSPは、取り除けなかったときのための多層防御として使う。
終わりに
セキュリティ対策は有名なところから少しずつ味見して学ぼう
次はSQLインジェクションを中心にまとめたい
参考文献
小林恭平・坂本陽 著、佐々木拓郎 監修『イラスト図解式 この一冊で全部わかるWeb技術の基本』SBクリエイティブ
平野昌士 著、はせがわようすけ・後藤つぐみ 監修『フロントエンド開発のためのセキュリティ入門 知らなかったでは済まされない脆弱性対策の必須知識』翔泳社
株式会社シンシア
株式会社xincereでは、実務未経験のエンジニアの方や学生エンジニアインターンを採用し一緒に働いています。
※ シンシアにおける働き方の様子はこちら
シンシアでは、年間100人程度の実務未経験の方が応募し技術面接を受けます。