3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【保存版】XSS完全対策ガイド2026 ─ React/Next.jsでも油断すると刺さる箇所7つ

3
Posted at

ReactやNext.jsを使っていれば、JSXが自動的にエスケープしてくれるからXSSは起きない──そう信じている開発者は多い。確かにReactのJSXは文字列をレンダリングする際にHTMLエスケープを行うが、それだけでは防げないXSSの入口が少なくとも7つ存在する。

1. dangerouslySetInnerHTML

最も有名な穴。名前に "dangerously" と入っているにもかかわらず、CMSから取得したHTMLをそのまま表示するために使われているケースが多い。

// 危険: サニタイズなしで挿入
<div dangerouslySetInnerHTML={{ __html: userContent }} />

// 対策: DOMPurifyでサニタイズしてから挿入
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userContent) }} />

DOMPurifyは現時点で最も信頼性の高いHTMLサニタイザーライブラリだ。sanitize-html など他の選択肢もあるが、バイパスされた実績が少ない点でDOMPurifyが推奨される。

2. href属性へのjavascript:スキーム

ReactはJSXの属性値をエスケープするが、href 属性に javascript: スキームのURLを渡した場合は防げない。

// 危険: ユーザー入力をそのままhrefに渡す
<a href={userUrl}>リンク</a>
// userUrl = "javascript:alert(document.cookie)" でXSS成立

// 対策: プロトコルをホワイトリストで検証
function sanitizeUrl(url: string): string {
  const parsed = new URL(url, 'https://dummy.com');
  if (!['http:', 'https:', 'mailto:'].includes(parsed.protocol)) {
    return '#';
  }
  return url;
}

3. Server Componentsでの直接HTML出力

Next.js App Routerでは、Server Componentsの中でHTMLを直接返す処理が書ける。この場合もエスケープ漏れに注意が必要だ。

// Server Component内で動的にmetaタグを生成する場合
// OGP画像のURLにユーザー入力が含まれると危険
export async function generateMetadata({ params }) {
  const post = await getPost(params.id);
  return {
    openGraph: {
      // post.titleにスクリプトが含まれていても
      // Next.jsのmetadata APIが適切にエスケープする
      title: post.title,
    },
  };
}

Next.jsのMetadata APIを使えば基本的に安全だが、手動で <head> にタグを挿入する場合は話が変わる。

4. SVGファイルのインライン読み込み

SVGはXMLベースのフォーマットであり、<script> タグを含めることができる。ユーザーがアップロードしたSVGをそのままインライン表示すると、XSSが成立する。

<!-- 悪意のあるSVG -->
<svg xmlns="http://www.w3.org/2000/svg">
  <script>alert(document.cookie)</script>
</svg>

対策は、SVGファイルを <img> タグで読み込むか、サーバーサイドでSVGのサニタイズを行うことだ。<img> タグ経由であればスクリプトは実行されない。

5. CSS-in-JSでのスタイルインジェクション

styled-componentsやEmotionなどのCSS-in-JSライブラリで、ユーザー入力をスタイルに埋め込むケースも危険だ。

// 危険: ユーザー入力をCSSに直接埋め込む
const UserDiv = styled.div`
  background-color: ${props => props.userColor};
`;
// userColor = "red; } body { background: url('javascript:...');" のような入力

CSSインジェクション自体でJavaScriptを実行するのは現代のブラウザでは困難だが、url() を使った外部リクエストでデータを窃取する手法は有効だ。

6. window.locationやwindow.nameの参照

ReactコンポーネントでURL パラメータを直接レンダリングしている場合、Reflected XSSが成立する可能性がある。

// 危険: URLパラメータをサニタイズせずに表示
function SearchResults() {
  const params = useSearchParams();
  const query = params.get('q');
  return <div dangerouslySetInnerHTML={{ __html: `検索結果: ${query}` }} />;
}

これは dangerouslySetInnerHTML との組み合わせで発生するが、検索結果ページなどで意外とよく見かけるパターンだ。

7. サードパーティスクリプトの読み込み

next/script やuseEffectで外部スクリプトを動的に読み込む際、そのURLにユーザー入力が含まれていると、任意のスクリプトを実行される。

// 危険: ユーザー入力からスクリプトURLを構築
<Script src={`https://cdn.example.com/${userInput}/analytics.js`} />

防御の多層化

XSS対策はアプリケーションコードだけでは不完全だ。以下のHTTPヘッダーを組み合わせることで、仮にXSSが発生しても被害を最小限に抑えられる。

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}';
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

特にCSP(Content Security Policy)は強力な第二の防御線になる。unsafe-inline を許可しないだけで、大半のXSS攻撃は無効化される。

ReactやNext.jsの自動エスケープに頼りきるのではなく、「どこが自動エスケープの範囲外なのか」を正確に把握しておくことが重要だ。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?