XSS と CSRF の違いとは?React が守ること・守らないこと(React 19 の URL まで)

PR
XSS と CSRF の違いとは?React が守ること・守らないこと(React 19 の URL まで)
この記事は約29分で読めます。

XSS(クロスサイト・スクリプティング)とは、攻撃者が作れる入力を、動かないことを確かめずにページへ入れてしまい、悪いコードを、そのサイトの一部であるかのように動かされる攻撃です。CSRF(クロスサイト・リクエスト・フォージェリ)とは、悪いサイトから、ログイン中の利用者のブラウザーに、目的のサイトへのリクエストを出させる攻撃です。前者は「ページに出す側」、後者は「リクエストを受ける側(サーバー)」の守りが中心になります。

React は、JSX(React で画面を書くときの、HTML に似た書き方)に埋め込んだ値を、タグとして解釈せず、文字のまま出します。ここは XSS を防ぐ助けになります。ただし、dangerouslySetInnerHTML と、href に入れる URL は、React に任せきりにできません。CSRF は、OWASP が「React のアプリには、それに合うサーバー側の守りが要る」と書いているとおり、サーバーで守るものです。

この記事は、「React なら XSS は大丈夫?」「CSRF は誰が守る?」と思った初心者〜若手の人に向けて、2つの違いと、React が守ること・守らないことをまとめたものです。MDN、OWASP(ウェブの安全に取り組む団体)、IPA(情報処理推進機構)の手引き、React の公式の告知と変更履歴を、2026年10月20日に開いて確かめました。手元の出力は、macOS 14.4.1・Node 24.21.0・react-dom 19.3.1 と 18.3.1 で、サーバー側の renderToString(React を HTML の文字列にする関数)と、jsdom(ブラウザーの代わりに Node の中で画面の部品を作る道具)で流したものです。ブラウザーでは確かめていません。

動く攻撃の入力は、この記事に載せません。手元の例は、無害なタグ <b>太字</b> と、何もしない javascript:void(0) だけです。

この記事でわかること
  • XSS と CSRF の違い。どちらも、起きる条件と、守る場所が違うこと
  • React は、JSX に埋め込んだ値を文字のまま出すこと(手元の React 19.3.1 で確認)。今の react.dev には、その旨の原文が見つからないこと
  • dangerouslySetInnerHTML が危ない理由と、React 19.3 の Trusted Types への対応(2026年10月時点)
  • href に入る javascript: の URL は、React の版で扱いが違うこと(16.9〜18 はそのまま出力し、開発版で警告だけ〔手元の 18.3.1〕。19.0.0 から差し替え)。それでも URL は自分で確かめる必要があること
  • CSRF は React ではなくサーバー側で守るもの。トークン・Fetch Metadata・SameSite の役割
  • XSS は、エスケープと sanitize が先。CSP(Content Security Policy)は重ねる守り

XSS と CSRF の違いは?

2つの違いを、MDN・OWASP・IPA が書いていることで並べると、次のとおりです。

XSSCSRF
どんな攻撃か目的のサイトに、悪いコードを、そのサイトの一部のように実行させる(MDN)悪いサイトから目的のサイトへ、利用者のブラウザーにリクエストを出させる。リクエストに利用者の資格情報が付くので、サーバーは本人の操作と思って実行する(MDN)
起きる条件攻撃者が作れる入力を受け取り、動かないことを確かめずにページに入れる、の2つがそろったとき(MDN)ログインした利用者のリクエストが、本人の意図したものかを見分ける仕組みがないとき(IPA)
起きることそのサイト自身のコードにできることは、何でもできる。ページの中身や local storage の読み書き、利用者の資格情報つきのリクエストなど(MDN)送金やメールアドレスの変更など、状態を変えるリクエスト。管理者なら、アプリ全体が危うくなる(OWASP)
守る場所ページに出す側(エスケープ・sanitize)。CSP は重ねる守りサーバー側(トークン・出どころの確認など)

「資格情報」とは、ここでは、Cookie など、ログイン中であることを示すものです。Cookie・セッション・トークンといった言葉は、JWT とは?仕組み・セッションとの違い・保存場所の注意点で説明しています。

IPA も、XSS の影響は「ウェブサイト自体ではなく、そのページを見ている利用者に及ぶ」と書いています。「状態を変えるリクエスト」とは、OWASP が例に挙げる送金やメールアドレスの変更のように、サーバーのデータを変える操作のことです。

言葉を2つ:エスケープと sanitize

  • エスケープ(出力エンコード):危険になりうる文字を、HTML などの一部ではなく「文字として」扱わせる形に変えることです(MDN)。IPA は、<・>・& を &lt;・&gt;・&amp; に置き換える方法を挙げています。
  • sanitize(サニタイズ):MDN は、XSS が起きる条件の説明のなかで、sanitize を「JavaScript として動かないことを確かめること」と書いています。HTML を入れてよい場面で、安全な形に整えることを指します。MDN は、sanitize はむずかしいので、評判のよいライブラリを使うよう勧めています。

2つはつながっている

OWASP は、XSS があると、CSRF の守りはすべて破られうると書いています。それでも、Cookie でログインを確かめるアプリに、CSRF トークンは欠かせない、とも書いています。XSS を防ぐことと、CSRF を防ぐことは、別々にやる必要があります。

OWASP Top 10:2025(開発者が知っておくべき危険の10項目)での位置も、少し違います。XSS は A05 Injection(インジェクション。5位)に入っています。OWASP は、インジェクションを「信頼できない入力が、解釈する側(ブラウザー・データベースなど)に送られ、その一部が命令として実行される欠陥」と定義しています。XSS では、解釈する側がブラウザーです。CSRF は独立した項目ではなく、A01 Broken Access Control(1位)の CWE(弱点の種類の番号)の一覧に、CWE-352 として入っています。A01 の背景の節は、主な CWE の一つとして CWE-352(CSRF)を名前で挙げていますが、CSRF の仕組みや守り方は説明していません。

React は XSS をどこまで守る?

MDN は、ブラウザーで画面を描くサイト(SPA。ページを切り替えずに画面を書き換えるタイプ)では、document.createElement() のような Web API で、直接か、React のようなフレームワークを通して、ページを変える、と書いています。XSS の注入は、その過程で起きます。ここでは、React が守る所と、守らない所を分けて見ます。

JSX の値は、文字のまま出る

JSX の { } に埋め込んだ値について、MDN は「React でブラウザー側で描くとき、JSX に埋め込んだ値は自動でエンコードされる」と書いています。更新が止まった旧い React 公式(legacy.reactjs.org)にも、同じ趣旨の説明があります。「既定で React DOM は、JSX に埋め込んだ値を描く前にエスケープする。すべて文字列に変えてから描くので、XSS を防ぐ助けになる」という内容です。

ただし、旧公式のページには、自分で「もう更新しない」と書いてあります。今の react.dev の本文には、この趣旨の文を見つけられませんでした(react.dev のリポジトリの 2026年10月6日の版を、escape・sanitize などの語で検索しました)。そのため、この記事では、「react.dev に書いてある」とは書きません。MDN と旧公式の説明に加えて、手元で出力を確かめました。

手元で使った部品は、次のとおりです。無害なタグを含む文字と、何もしない javascript:void(0) だけを使っています。

const text = '<b>太字</b>'
const url = 'javascript:void(0)'

export default function Demo() {
  return (
    <div>
      <p id="a">{text}</p>
      <p id="b" dangerouslySetInnerHTML={{ __html: text }} />
      <a id="c" href={url}>リンク</a>
      <a id="d" href="https://example.com/">ふつうのリンク</a>
    </div>
  )
}

これを、react-dom 19.3.1 の renderToString で HTML の文字列にした出力です(開発版・本番版とも同じ1行で、警告は出ませんでした)。

<div><p id="a">&lt;b&gt;太字&lt;/b&gt;</p><p id="b"><b>太字</b></p><a id="c" href="javascript:throw new Error(&#x27;React has blocked a javascript: URL as a security precaution.&#x27;)">リンク</a><a id="d" href="https://example.com/">ふつうのリンク</a></div>

1つ目の <p id="a">{text}</p> は、&lt;b&gt;太字&lt;/b&gt; になりました。< と > が &lt; と &gt; に変わっているので、タグとしては読まれず、画面には <b>太字</b> という文字がそのまま出ます。

次に、ブラウザー側で描く形(jsdom の上で、react-dom/client の createRoot を使う)でも確かめました。#a の中は、文字のノード(画面の部品の一種で、文字だけのもの)が1つだけで、<b> の要素はできませんでした。

#a childNodes: 1 nodeType: 3 textContent: <b>太字</b> <b> inside #a: false

サーバーで描いたときは &lt;b&gt; に変わり、ブラウザー側で描いたときは HTML を書かずに文字のノードとして入ります。どちらも、タグとして解釈されず、文字のまま出る、という結果は同じです。この記事では、「エスケープされる」という言い方より、この結果のほうを書きます。

なお、Vite で作った本番用のファイル(npm run build の dist/index.html)は、本文(body)は <div id="root"></div> だけで(JS は <head> の script のタグで読み込むだけ)、描いた結果は HTML には出ません。描くのはブラウザーだからです。そのため、手元では上の2通りで確かめました。Vite で React を始める手順は、Vite とは?Create React App の代わりに React を始める方法にまとめています。この記事で確かめたのは Vite の React で、Next.js のサーバー側の描き方では確かめていません。Next.js の最新の版については、Next.js 16.4にまとめています。

同じことは、DOM の textContent と innerHTML でも

MDN は、innerHTML を、おそらくいちばんよくある XSS の入口と書いています。利用者の文字をそのまま DOM(ブラウザーが持つ、画面の部品の木)に入れてしまうからです。そして「利用者の文字がただの文字だと分かっているなら、textContent を使う。HTML として解析されなくなる」と書いています。jsdom で確かめると、次のとおりでした。

document.getElementById('x').textContent = '<b>太字</b>'
document.getElementById('y').innerHTML = '<b>太字</b>'
x outerHTML: <p id="x">&lt;b&gt;太字&lt;/b&gt;</p> | <b> inside: false
y outerHTML: <p id="y"><b>太字</b></p> | <b> inside: true

textContent に入れた文字は <b> の要素になりませんが、innerHTML に入れた文字は <b> の要素になりました(jsdom はブラウザーそのものではありません)。ブラウザー側で描いたとき、JSX の {text} は、前者と同じく文字のノードになりました。

React が守らない所も、OWASP は書いている

OWASP の XSS Prevention Cheat Sheet は、今のフレームワークで作ったアプリは、XSS の不具合が少ない、と書いています。テンプレートや自動エスケープが、安全な書き方に導くからです。ただし、フレームワークを安全でない使い方をすると、問題が起きます。OWASP が挙げる例のうち、React に関わるのは次の2つです。

  • sanitize しない、React の dangerouslySetInnerHTML
  • 確かめていない URL の値(React 19 は src と href の javascript: の URL を止めるが、アプリでの URL の確かめの代わりにはならない)

さらに「完璧なフレームワークはなく、React や Angular にも穴は残る。そこは、出力エンコードと HTML の sanitize で埋める」とも書いています。この2つを、次から順に見ます。

dangerouslySetInnerHTML は、名前のとおり危ない

dangerouslySetInnerHTML は、{ __html: '…' } の形で、生の HTML の文字列(か、TrustedHTML の値)を渡すと、DOM の innerHTML を上書きして、その HTML を表示する属性です。react.dev のリファレンスは、次のように書いています。

This should be used with extreme caution! If the HTML inside isn't trusted (for example, if it's based on user data), you risk introducing an XSS vulnerability.

「細心の注意を払うこと。中の HTML が信頼できない(たとえば、利用者のデータをもとにしている)なら、XSS の脆弱性を作る危険がある」という意味です。また、同じページの別の節では「完全に信頼できる所から来た HTML でない限り、この方法で XSS の脆弱性を作るのはたやすい」「HTML に埋め込まれたコードは動く。攻撃者は利用者の情報を盗んだり、利用者になりすまして操作したりできる」「信頼でき、sanitize したデータにだけ使うこと」とも書いています。

手元の出力でも、先ほどの <p id="b" dangerouslySetInnerHTML={{ __html: text }} /> は、<p id="b"><b>太字</b></p> になり、<b> が要素として解釈されました。ブラウザー側で描いたときも同じで、開発版と本番版で変わりませんでした。JSX の {text} と、dangerouslySetInnerHTML の違いは、ここにあります。

react.dev は、使ってよい例として、Markdown を HTML に変えるライブラリを挙げています。ライブラリの構文解析に不具合がないと信じられて、利用者が自分の入力だけを見るなら、できた HTML を表示してよい、という条件つきです。{__html} のオブジェクトは、HTML を作る所のなるべく近くで作り、<div dangerouslySetInnerHTML={{__html: markup}} /> のようにその場で作るのは勧めない、とも書いています。children(タグの中身)と dangerouslySetInnerHTML は、同時に渡せません。

MDN は、テンプレートが持つ「出力エンコードを切る方法」の例として、Django の safe フィルターと、React の dangerouslySetInnerHTML を並べています。そのときは、開発者が sanitize して、安全を確かめる必要があります。sanitize のライブラリでは、DOMPurify を、OWASP を含む多くの専門家が勧めている、としています。

React 19.3 の Trusted Types(2026年10月時点)

Trusted Types は、ブラウザーの機能で、innerHTML のような危ない受け口に、sanitize の方針(ポリシー)を通して作った、型つきの値しか渡せなくするものです。サイトが Content-Security-Policy: require-trusted-types-for 'script' で強制すると、ブラウザーは、生の文字列を断ります。

2026年9月9日の React 19.3 の発表によると、React 19.3 からは、この Trusted Types に対応しています。それまでは、React が値を文字列に変えてから渡していたので、ブラウザーに断られていました。19.3 からは、変えずに渡します。Trusted Types を強制しているサイトでは、方針で作った TrustedHTML の値を __html に渡します。ただし、react.dev は「方針は、入力が信頼でき、sanitize されていることを確かめる必要がある」と書いています。Trusted Types を使うことは、sanitize をしなくてよい、という意味ではありません。

これは、ブラウザーでは確かめていません。

href に入る javascript: の URL は?React の版で変わる(2026年10月時点)

javascript: の URL とは、https:// の代わりに javascript: で始まる、リンク先の URL のことです。IPA は、URL には http:// や https:// で始まるものだけでなく、javascript: で始まるものもあり、リンク先や画像の URL を外からの入力で作ると、XSS ができる場合がある、と書いています。React 16.9 の告知も、<a href> のようなタグに、sanitize していない出力を入れて穴を作りやすいので、危ない攻撃の入口だ、と書いています。

また、MDN は、出力の場面によって、必要なエンコードが違うと書いています。属性の値は、属性によって、安全なときとそうでないときがあります。href に入る URL は、その一つです。

React の版ごとの扱い

React の版ごとに、同じ javascript: の URL の扱いが違います。手元で流した版は、19.3.1 と 18.3.1 の2つだけです。

React の版扱い確かめ方
16.9.0(2019年8月8日)javascript: で始まる URL を非推奨にして、警告を出す。動きはそのまま。将来のメジャー版で、見つけたらエラーにする、と予告告知と変更履歴を読んだ(手元では流していない)
18.3.1そのまま出力される。開発版だけ警告が出る。本番版は警告も出ない手元で流した
19.0.0(2024年12月5日)変更履歴に「javascript の URL を、エラーを投げる関数に置き換える」とある変更履歴を読んだ。19.0〜19.2 の各版では流していない
19.3.1href が、別の URL に差し替わる。描く時点ではエラーにも警告にもならない手元で流した

16.9 の告知は、javascript: の URL をロジックに使っているなら、React のイベントハンドラー(クリックなどで動く関数)に替えるよう勧めています。

18.3.1 では、同じ <a id="c" href="javascript:void(0)"> が、次のようにそのまま出力されました。

<a id="c" href="javascript:void(0)">リンク</a>

開発版(NODE_ENV=development)だけ、「将来の版の React は、安全のために javascript: の URL を止める。できるならイベントハンドラーを使う」という趣旨の警告が出ました。

19.3.1 では、前の節で見た出力のとおり、href が次の URL に差し替わりました。

href="javascript:throw new Error('React has blocked a javascript: URL as a security precaution.')"

react-dom 19.3.1 の中身を読むと、javascript: で始まる URL(頭に制御文字や空白がついた形、途中に改行やタブを挟んだ形、大文字小文字の違いも含む)を、この URL に差し替える処理がありました。開発版にも本番版にも同じ文字列があり、警告を出す文は見つかりませんでした。ふつうの https:// の URL は、そのまま出ます。

資料ごとに、言い方が違う

19 以降の扱いは、資料によって、言い方が違います。

資料言い方
React 16.9 の告知(2019年)将来のメジャー版で、javascript: の URL を見つけたら、エラーを投げる
react.dev の 19 への移行の手引き"Error for javascript URLs in src and href"(src と href の javascript の URL でエラー)
変更履歴の 19.0.0"Javascript URLs are replaced with functions that throw errors"(エラーを投げる関数に置き換えられる)
変更の PR #26507 の題"Generate safe javascript url instead of throwing with disableJavaScriptURLs is on"(投げる代わりに、安全な javascript の URL を作る。disableJavaScriptURLs がオンのとき)
OWASP の XSS Prevention Cheat Sheet"React 19 blocks javascript: URLs in src and href"(止める)
手元の 19.3.1描く時点では、エラーにも警告にもならず、href が差し替わった

そのため、この記事では次のように書きます。React 19 からは、href に入った javascript: の URL を、動かすとエラーになるだけの URL に差し替えます。描くときに止まるわけではありません(手元の 19.3.1 で確かめました)。「React 19 はエラーにする」とだけ書くと、描くときに落ちると読まれるおそれがあります。

差し替えた href をブラウザーでクリックしたときの様子は、見ていません。

javascript: の URL を React が防ぐかどうかは、版で変わります。18.3.1 はそのまま出力し、19.3.1 は差し替えました。版を書かずに「防ぐ」「防がない」と言い切ると、誤ります。

差し替わっても、URL は自分で確かめる

ただし、「React 19 なら、URL を確かめなくてよい」とは言えません。OWASP は「React 19 は src と href の javascript: の URL を止めるが、アプリでの URL の確かめの代わりにはならない」と書いています。

URL の確かめ方は、IPA と OWASP がそろって、http と https だけを許す、としています。

  • IPA:URL を出力するときは、http:// や https:// で始まる URL だけを許可する
  • OWASP:信頼できない URL を href や src に入れるときは、URL を正規化(決まった形にそろえる)し、確かめ、http と https だけを許可する(許可リスト)。属性のエンコードもする

この記事で確かめたのは、<a href> だけです。src・<object>・フォームの送り先は流していません。また、javascript: 以外の危ない URL(たとえば data:)を React がどう扱うかも、確かめていません。URL を外からの入力で作るときは、React の版にかかわらず、アプリ側で許可リストを通す、と考えておくのが無難です。

CSRF は誰が守る?React ではなく、サーバーの仕事

OWASP の CSRF Prevention Cheat Sheet は、React について「React のアプリには、HTTP クライアントと、それに合うサーバー側の CSRF の守りが要る」と書いています。react.dev の本文には、CSRF という語は見つかりませんでした(react.dev のリポジトリを検索して 0 件)。React の画面の部品が、CSRF を防いでくれるわけではありません。

MDN は、Cookie でセッション ID を送るなら、CSRF の守りは必須だと書いています。SameSite(あとで説明します)は、守りの一部でしかありません。

守り方は、いくつかある

OWASP と MDN が書く守り方を、並べます。OWASP の原則は、まず、フレームワークに組み込みの CSRF の守りがあるか確かめて、あれば使うことです。無ければ、状態を変えるすべてのリクエストに CSRF トークンを付けて、サーバーで確かめます。

守り方中身
CSRF トークンサーバーで作る、秘密で、予測できない値(安全な方法で作った大きな乱数)を、状態を変えるリクエストに付けさせ、サーバーで確かめる。無いか、セッションの値と合わないなら断る。MDN は、フォームから状態を変えるなら、いちばん一般的な方法としている
Fetch Metadataブラウザーが付ける Sec-Fetch-Site(same-origin・same-site・cross-site・none)を見る。OWASP は、これを CSRF の守りのいちばんの手がかりとし、cross-site のときは、既定で POST・PUT・PATCH・DELETE を断る。新しいブラウザーだけが相手なら、下に書く予備の手段と組み合わせれば、Fetch Metadata に頼ってよい、とも書いている
「単純リクエスト」にしないfetch() で送るとき、Content-Type を application/json にするか、独自の見出しを付けると、単純リクエストではなくなり、既定ではクロスオリジンで送れない(MDN)
Origin の確かめOrigin 見出しがあれば、自分のオリジンと合うか確かめる。無ければ Referer のホスト名を確かめる。HTTPS でも Origin が必ず付くとは限らない(OWASP。重ねる守りの扱い)
SameSite の Cookie重ねる守り(次の節)
状態を変える操作に GET を使わないOWASP も MDN も書いている

「単純リクエスト」と CORS の関係は、CORS エラーとは?で説明しています。MDN は、次の場合に注意するよう書いています。CORS で、相手のオリジンを Access-Control-Allow-Origin に載せ、Access-Control-Allow-Credentials も返していると、そのオリジンからの CSRF を受けます。

SameSite は、代わりにならない

SameSite は、Cookie につける属性で、ほかのサイトから出たリクエストに、その Cookie を付けるかどうかを決めます。値は None・Lax・Strict の3つです。

  • Strict:クロスサイトのリクエストには一切付けない。いちばん強い。ただし、ほかのサイトのリンクから来ると、ログインしていない扱いになる
  • Lax:「最上位の移動」で、かつ「安全なメソッド(GET など。POST は違う)」のときだけ付ける

MDN の目安は、ページを見せるかどうかの判断に使う Cookie は Lax、状態を変えるリクエストに使う Cookie は Strict です。

ただし、OWASP は「SameSite は、重ねる守りとしては役に立つが、たいていの場合、ちゃんとした CSRF の守りの代わりにならない」と書いています。理由は2つあります。

  • Lax でも、GET による最上位の移動には Cookie が付く。GET で状態が変わる操作があると止められない。OWASP は、これを「運用でいちばん多い失敗」としている
  • SameSite の単位は、オリジンではなく、登録可能ドメイン。app.example.com と anything.example.com は「同じサイト」になる(MDN も、サブドメインはすべて信頼することになる、と書いている)

MDN の点検表は、次のとおりです。

  1. セッション Cookie で利用者を確かめる「状態を変えるリクエスト」が、どこにあるか把握する
  2. 主な守りを、少なくとも1つ入れる(フォームならトークンのあるフレームワーク、fetch() なら単純リクエストにしない)
  3. Fetch Metadata でクロスサイトを断ることを考える
  4. 状態を変えるのに GET を使わない
  5. セッション Cookie の SameSite は、できれば Strict、だめなら Lax

IPA の手引きとは、重みが違う

IPA の「安全なウェブサイトの作り方」は、CSRF の対策として、次を挙げています。

  • 処理を実行するページを POST にして、hidden パラメーター(画面に出ない入力欄)に、第三者に予測できない秘密情報を入れ、正しいときだけ実行する
  • 実行の直前に、もう一度パスワードを入れさせる
  • Referer が正しいリンク元かを確かめる(Referer が空でも実行しない)
  • 重要な操作をしたら、登録済みのメールアドレスに知らせる(保険的な対策)

この手引きは、改訂第7版第4刷(2021年3月31日。中身は 2015 年の版)で、SameSite・Origin・Fetch Metadata の語は出てきません(文字を抜いて検索しました)。OWASP は Origin を先に確かめ、無いときに Referer を見る形で、しかも重ねる守りの扱いです。新しい守り方は、OWASP と MDN を見て、IPA は「日本の公的な手引きの基本形(トークンと再認証)」として読むのがよさそうです。

XSS を重ねて守る:CSP・HttpOnly(X-XSS-Protection は使わない)

MDN は、XSS の守りを2本に分けています。

  1. 出力エンコードと sanitize で、入力が動くものにならないようにする(ブラウザーで描くなら、Trusted Types で sanitize を通したことを保証できる)
  2. CSP で、動かしてよい JavaScript・CSS をブラウザーに伝える。これは予備の守りです。1が破られて、動く入力がページに入っても、正しく設定した CSP なら、実行を止めるはずだからです

OWASP も、打ったとおりに表示したいなら、まずフレームワークの既定の出力エンコードを使う、と書いています。エスケープと sanitize が先で、CSP は重ねる守りです。

CSP とは?

CSP(Content Security Policy)は、サイトからブラウザーへの一連の指示で、サイトのコードにできることを制限します。いちばんの使い道は、文書が読み込んでよい資源(特に JavaScript)を決めることで、主に XSS への守りです。MDN は「CSP を設定することは、入力の sanitize の代わりではない。sanitize と CSP の両方で、重ねて守る」と書いています。OWASP も、CSP は設定を誤りやすいので、主な守りにせず、追加の守りとして使う、と書いています。

  • default-src か script-src があると、インラインの JavaScript(ページの中の script 要素や、インラインのイベントハンドラー属性など)は、そのままでは動きません。'unsafe-inline' で許せますが、CSP の目的の多くを損なうので、MDN は避けるよう書いています
  • MDN が勧めるのは、nonce か hash を使う「strict CSP」です。strict CSP では、インラインのイベントハンドラー・javascript: の URL・eval() のような API も止まります
  • 入れる前の試しには、止めずに違反だけを報告する、Content-Security-Policy-Report-Only の見出しがあります

この節の CSP の効き目は、ブラウザーでは確かめていません。

HttpOnly は、盗まれにくくする

Cookie に HttpOnly を付けると、JavaScript から読めません。MDN は、XSS が成功しても、セッション ID そのものは盗めない、と書いています。ただし、完全な守りではありません。悪いコードは、利用者のブラウザーから、Cookie が付いたリクエストを出せるので、その利用者の権限で操作できます。

また、OWASP は、認証トークンやセッション ID を、localStorage にも sessionStorage にも入れないよう書いています。XSS が1つあれば、すべてのトークンが漏れるからです。トークンの置き場の考え方は、JWT の記事にまとめています。

X-XSS-Protection は、今は使わない

IPA の手引きには、XSS の保険的な対策として、X-XSS-Protection という応答の見出しが載っています。しかし、今の MDN は、X-XSS-Protection を「非推奨」「非標準」としています。古いブラウザーを守れる場合もありますが、安全なサイトに XSS の穴を作ることもある、としています。インラインの JavaScript を止める強い CSP があれば、今のブラウザーでは、ほぼ要らないとされ、XSS のフィルターではなく CSP を使うよう書いています。

IPA の一覧を読むときは、この項目だけ、今の資料と食い違う、と覚えておいてください。

よくある質問

React を使っていれば、XSS は起きない?

起きないとは言い切れません。JSX に埋め込んだ値は、手元の React 19.3.1 で、タグとして解釈されず文字のまま出ましたが、dangerouslySetInnerHTML は HTML として解釈されました。OWASP も、sanitize しない dangerouslySetInnerHTML と、確かめていない URL の値を、React で起きる問題の例に挙げています。

react.dev に、JSX の値は自動でエスケープされると書いてある?

今の react.dev の本文には、その趣旨の文を見つけられませんでした(react.dev のリポジトリの 2026年10月6日の版で検索)。同じ趣旨の説明は、更新が止まった旧い React 公式と、MDN にあります。この記事では、手元の React 19.3.1 の出力でも確かめています。

React 19 なら、javascript: の URL を確かめなくていい?

確かめる必要があります。React 19.0.0 からは、href に入った javascript: の URL が差し替わりますが、OWASP は、これはアプリでの URL の確かめの代わりにならない、と書いています。IPA と OWASP は、http と https だけを許すよう求めています。また、data: など javascript: 以外の URL を React がどう扱うかは、確かめていません。差し替わった href をクリックしたときの様子も、ブラウザーでは見ていません。

CSRF は、React で防げる?

React の側では防げません。OWASP は、React のアプリには、それに合うサーバー側の CSRF の守りが要る、と書いています。守りの中心は、CSRF トークン、Fetch Metadata、fetch() を単純リクエストにしないこと、で、SameSite は重ねる守りです。

X-XSS-Protection は付けたほうがいい?

MDN では「非推奨」「非標準」です。XSS のフィルターではなく CSP を使うよう、MDN は書いています。IPA の手引き(2021年の刷り)には載っていますが、今の MDN と食い違います。

自分のアプリで確かめること(チェックリスト)
  • dangerouslySetInnerHTML に渡す HTML は、信頼できる所から来て、sanitize したものだけになっているか(→「dangerouslySetInnerHTML は、名前のとおり危ない」)
  • href などに入れる URL を外からの入力で作るとき、アプリ側で http と https だけを許す確かめを通しているか。React 19.0.0 から href に入った javascript: の URL は差し替わるが、その確かめの代わりにはならない。使っている React の版も確かめる(手元では 18.3.1 はそのまま出力し、19.3.1 は差し替わった)(→「差し替わっても、URL は自分で確かめる」)
  • 状態を変えるリクエストに、CSRF の守り(フレームワークに組み込みがあれば、それ。無ければ CSRF トークン)をサーバー側で入れているか。状態を変える操作に GET を使っていないか(→「守り方は、いくつかある」)
  • SameSite を、CSRF の守りの代わりにしていないか。セッション Cookie の SameSite は、できれば Strict、だめなら Lax にしているか(→「SameSite は、代わりにならない」)
  • CSP を、エスケープや sanitize の代わりではなく、重ねる守りとして使っているか。入れる前の試しに、止めずに報告だけする Content-Security-Policy-Report-Only があることを知っているか(→「CSP とは?」)
  • セッション Cookie に HttpOnly を付けているか。認証トークンやセッション ID を、localStorage や sessionStorage に入れていないか(→「HttpOnly は、盗まれにくくする」)
  • X-XSS-Protection を頼りにせず、CSP を使っているか(→「X-XSS-Protection は、今は使わない」)

まとめ

  • XSS は、攻撃者が作れる入力を、動かないことを確かめずにページに入れると起きる。CSRF は、本人の意図したリクエストか見分けられないサーバーに、ログイン中の利用者のブラウザーから、別のサイトがリクエストを出させる。守る場所が違う
  • JSX に埋め込んだ値は、タグとして解釈されず、文字のまま出る(MDN・旧公式・手元の React 19.3.1)。今の react.dev には、その旨の原文は見つからなかった
  • dangerouslySetInnerHTML は、HTML として解釈される。信頼でき、sanitize したデータにだけ使う。React 19.3 からは Trusted Types にも対応した(2026年10月時点。ブラウザーでは確かめていない)
  • href の javascript: の URL は、16.9〜18 はそのまま出力し、開発版で警告だけ(手元の 18.3.1)。19.0.0 から差し替え。差し替わっても、アプリで http・https だけを許す確かめは要る
  • CSRF は React ではなく、サーバー側で守る。トークン・Fetch Metadata・単純リクエストにしないこと。SameSite は重ねる守りで、代わりにならない。GET で状態を変えない
  • XSS は、エスケープと sanitize が先。CSP と HttpOnly は重ねる守り。X-XSS-Protection は今は使わない
  • 点検の全体は、Web 開発のセキュリティの基本にまとめています

参考資料

この記事の内容は、2026年10月20日に開いた文書をもとにしています。OWASP の Cheat Sheet は、頻繁に書き換わります。手元の出力は、macOS 14.4.1・Node 24.21.0・react-dom 19.3.1 と 18.3.1 のもので、ブラウザーでは確かめていません。

※この記事は、公式の資料をもとに AI(Claude)で下書きし、2026年10月20日時点の公式の資料と照らして確かめてから公開しています。誤りに気づいたらお問い合わせからお知らせください。

PR
PR
タイトルとURLをコピーしました