⚠️ 注意:
日本語は勉強中のため、本記事は一部AIを使用して作成しています。
不自然な表現や誤りがあればご指摘いただけると嬉しいです 🙏
はじめに
Web開発において「自分はもうセキュリティ対策をしている」という思い込みが、最も危険な脆弱性になり得ます。
特にフロントエンドが中心のアプリケーション(React、Vue、SPAなど)では、XSS、CSRF、クライアントサイドのセキュリティ が軽視されがちです。
この記事では、実務でそのまま使える知識を提供します:
- XSS(クロスサイトスクリプティング)
- CSRF(クロスサイトリクエストフォージェリ)
- クライアントサイドセキュリティ(LocalStorage、トークン管理など)
👉 「なぜ危険なのか」と「どう防ぐのか」を両方理解できる内容です。
1. XSS(クロスサイトスクリプティング)
XSSとは?
攻撃者が悪意あるスクリプト(JavaScript)をWebサイトに挿入し、被害者のブラウザ上でそのスクリプトが実行される攻撃です。
<input value="<script>alert('Hacked')</script>" />
💥 被害例
- Cookieやログイントークンの盗難
- セッションハイジャック
- ユーザーの意図しない操作(投稿、送金など)の実行
🔥 絶対にやってはいけない例
❌ ユーザー入力値をそのまま innerHTML に代入
element.innerHTML = userInput; // 非常に危険
👉 たったこれだけで、あなたのWebサイトは乗っ取られる可能性があります。
✅ XSSを防ぐための対策
1. エスケープ処理
専用ライブラリ DOMPurify を使用する:
import DOMPurify from 'dompurify';
const safeHTML = DOMPurify.sanitize(userInput);
element.innerHTML = safeHTML;
2. フレームワークの組み込み機能を使う
React はJSX内で自動エスケープします:
<div>{userInput}</div> // 安全 – Reactがエスケープしてくれる
⚠️ ただし、次のようなコードは危険:
<div dangerouslySetInnerHTML={{ __html: userInput }} />
👉 これがいわゆる「XSSの裏口」です。リスクを完全に理解し、適切にサニタイズした場合のみ使ってください。
3. Content Security Policy (CSP)
CSPは最後の防御層です。ブラウザに対して「どこからスクリプトを読み込んで良いか」を指示します。
Content-Security-Policy: default-src 'self'; script-src 'self'
4. HttpOnly Cookie
Set-Cookie: token=abc123; HttpOnly; Secure
HttpOnly を設定すると、JavaScriptからCookieを読み取れなくなります → XSSが発生してもトークンを盗まれません。
2. CSRF(クロスサイトリクエストフォージェリ)
CSRFとは?
ユーザーが既にログインしている状態を悪用し、ユーザーの意図に反してリクエストを送信させる攻撃です。
💥 攻撃例
攻撃者が掲示板に次のような画像タグを埋め込みます:
<img src="https://bank.com/transfer?amount=10000&to=attacker" />
銀行にログインした状態でその掲示板を開くだけで、勝手に送金されてしまいます。
✅ CSRFを防ぐための対策
1. CSRFトークン
サーバーがランダムなトークンを生成し、クライアント(フォームやヘッダー)に送信します。
データを変更するリクエストには必ずこのトークンを含めます。
<input type="hidden" name="csrf_token" value="random_token_xyz" />
サーバー側での検証例:
if (req.body.csrf_token !== session.csrf_token) {
return res.status(403).send("Forbidden");
}
2. SameSite Cookie
SameSite=Strict または Lax を付けるだけで、ほとんどのCSRFを防げます。
Set-Cookie: session=abc; SameSite=Strict; Secure
3. Double Submit Cookie
トークンをCookieとリクエストヘッダーの両方で送信し、サーバーで比較します。
4. Cookieを使わず、Authorization Headerを使用する
Authorization: Bearer <jwt_token>
ブラウザは異なるオリジンへのリクエストにこのヘッダーを自動添付しません → CSRFは成立しません。
3. クライアントサイドのセキュリティ
LocalStorage – 諸刃の剣
多くのアプリケーションがJWTを localStorage に保存しています:
localStorage.setItem("token", jwt);
👉 XSSがあった場合、localStorage.getItem("token") するだけで攻撃者はトークンを盗めます。
✅ 安全な設計
✔ トークンは HttpOnly Cookie で保存する
JavaScriptからアクセス不可 → XSSでも盗まれません。
✔ パスワードや秘密鍵など機密データはクライアントに保存しない
❌ 悪い例:
const API_SECRET = "123456";
✔ 入力値のバリデーション(クライアント+サーバー両方)
クライアント側でも簡単なチェックをしますが、サーバー側でのバリデーションは必須です。
if (!email.includes("@")) {
throw new Error("Invalid email");
}
✔ HTTPSの強制
- Man-in-the-Middle(MITM)攻撃を防ぎます
- Cookieに
Secureフラグを付けられます
✔ 正しいCORS設定
本番環境では Access-Control-Allow-Origin: * を使わないでください。
Access-Control-Allow-Origin: https://yourdomain.com
🚨 よくある誤解
| 誤解 | 現実 |
|---|---|
| Reactを使っていれば安全 | ❌ dangerouslySetInnerHTML を使うとXSS発生 |
| JWTは安全 | ❌ localStorage に保存している時点で危険 |
| フロントエンドだけ対策すればOK | ❌ サーバー側の防御が最終ライン |
まとめ
ゴールデンルール
-
XSS → エスケープ、CSP、信頼できない入力に対する
innerHTML禁止 -
CSRF → CSRFトークン または
SameSite=StrictCookie -
クライアント → トークンは
HttpOnlyCookie に保存。LocalStorageに機密情報を入れない
実務チェックリスト
-
innerHTMLをユーザー入力に対して使っていないか? - CSPを設定しているか?
- CSRF対策(トークン / SameSite)を導入しているか?
- Cookieに
SameSiteとSecureが付いているか? - トークンやパスワードを
localStorageに保存していないか? - 全ページでHTTPSを強制しているか?
- CORS設定が
*になっていないか?