Webセキュリティ対策
更新日:
Webサイトは世界中の誰でもアクセスできるので、悪意ある人が来る前提で作る必要がある。基本の考え方は「ユーザーから来るものは全部疑う」「守る層を重ねる(多層防御)」「権限は必要最小限」の3つ。
生成AIが書いたコードも、ここに挙げた穴をそのまま含むことがあるので、レビュー時のチェックリストとしても使う。
代表的な攻撃と対策
1. XSS(クロスサイトスクリプティング)
何が起きる: 入力欄などから <script> を埋め込まれ、他のユーザーのブラウザで勝手にJavaScriptが動く → Cookie盗難・なりすまし操作。
対策
- 表示するときにエスケープする(
<→<など)。React / Next.js は{value}で自動エスケープされる dangerouslySetInnerHTML/innerHTMLにユーザー入力を渡さない。どうしても必要なら DOMPurify などでサニタイズhref={userInput}にjavascript:URL が入らないよう検証する- CSP(Content-Security-Policy)ヘッダーで、読み込めるスクリプトの出どころを制限(万一の保険)
2. SQLインジェクション
何が起きる: 入力値がそのままSQL文に組み込まれ、' OR 1=1 -- のような文字列でデータを全件取得・削除される。
対策
- プレースホルダ(パラメータ化クエリ)を必ず使う。文字列連結でSQLを作らない
// ❌ 危険
db.prepare(`SELECT * FROM users WHERE email = '${email}'`)
// ✅ 安全(Cloudflare D1 の例)
db.prepare("SELECT * FROM users WHERE email = ?").bind(email)- ORM(Drizzle / Prisma 等)を使うと基本的に安全。ただし生SQLを書く機能では同じ注意が必要
3. CSRF(クロスサイトリクエストフォージェリ)
何が起きる: ログイン中のユーザーが罠サイトを開くと、勝手に「送金」「退会」などのリクエストが本物のサイトへ送られる(ブラウザがCookieを自動で付けるため)。
対策
- Cookie に
SameSite=Lax(またはStrict) を付ける - 状態を変える操作は
GETではなくPOST等にする - フォームに CSRFトークンを入れる /
Originヘッダーを検証する - Next.js の Server Actions は Origin チェックが組み込まれている
4. 認証・セッションの不備
何が起きる: パスワード漏洩、セッション乗っ取り、総当たり攻撃。
対策
- 認証は自作しない。Auth.js / Clerk / Supabase Auth など実績あるものを使う
- パスワードは平文で保存しない(bcrypt / Argon2 でハッシュ化)
- セッションCookieは
HttpOnly(JSから読めない)・Secure(HTTPSのみ)・SameSiteを付ける - ログイン試行にレート制限をかける。可能なら多要素認証(MFA)
- ログアウト・パスワード変更時はセッションを無効化する
5. 認可(アクセス制御)の不備 ← 最も多い
何が起きる: /api/orders/123 の数字を変えるだけで他人のデータが見える・消せる(IDOR)。
対策
- 「ログインしているか(認証)」だけでなく「この人がこのデータを触っていいか(認可)」をサーバー側で毎回チェック
// ✅ 自分の注文だけに絞る
db.prepare("SELECT * FROM orders WHERE id = ? AND user_id = ?").bind(id, session.userId)- 画面でボタンを隠すだけでは対策にならない。APIは直接叩ける
- 管理者機能は特に厳しくチェックする
6. 入力値検証の不足
対策
- サーバー側で必ず検証する(フロントの検証はUXのため。簡単に回避できる)
- Zod などでスキーマを定義し、型・長さ・形式をチェック
- ファイルアップロードは拡張子・MIMEタイプ・サイズを制限し、ユーザー指定のファイル名をそのまま使わない
7. SSRF・オープンリダイレクト
- ユーザーが指定したURLへサーバーからアクセスする機能(OGP取得など)は、社内・クラウドのメタデータURLにアクセスされないよう許可リストで制限
?redirect=のようなリダイレクト先は自サイト内のパスだけ許可する(フィッシングに悪用されるため)
設定・インフラ面の対策
HTTPS
- 全ページHTTPS化し、HTTPはHTTPSへリダイレクト(詳細は HTTP・HTTPS・TLS)
- HSTSヘッダーで「常にHTTPSで来て」とブラウザに覚えさせる
セキュリティヘッダー
| ヘッダー | 役割 |
|---|---|
Content-Security-Policy | 読み込めるスクリプト・画像などの出どころを制限(XSS対策) |
Strict-Transport-Security | HTTPS強制(HSTS) |
X-Content-Type-Options: nosniff | ファイルの種類をブラウザに推測させない |
X-Frame-Options / CSP frame-ancestors | 他サイトの iframe に埋め込ませない(クリックジャッキング対策) |
Referrer-Policy | 遷移先に送るURL情報を制限 |
設定後は securityheaders.com などで確認できる。
CORS
Access-Control-Allow-Origin: *を認証付きAPIで使わない。許可するオリジンを明示する
シークレット管理
- APIキーをコードやブラウザに置かない → APIキー・シークレット管理
依存パッケージ
npm audit/ Dependabot / Renovate で脆弱性のあるライブラリを検知・更新- AIが提案した聞き覚えのないパッケージ名は実在・信頼性を確認してから入れる(偽パッケージ対策)
package-lock.jsonなどのロックファイルをコミットする
エラー・ログ
- 本番でスタックトレースやSQLエラーを画面に出さない(内部構造のヒントになる)
- ログにパスワード・トークン・個人情報を出力しない
- 不審なアクセス(ログイン失敗の連続など)は記録して気付けるようにする
レート制限・Bot対策
- ログイン・問い合わせフォーム・APIに回数制限をかける
- Cloudflare の WAF / Rate Limiting / Turnstile(CAPTCHA代替)が手軽
バックアップ
- DBは定期バックアップし、復元できることを確認しておく(D1 は Time Travel で過去に戻せる)
リリース前チェックリスト
- 全ページHTTPS、HSTS設定済み
- ユーザー入力を
innerHTML/dangerouslySetInnerHTMLに入れていない - SQLはすべてプレースホルダ or ORM
- すべてのAPIで「認証」と「認可(本人のデータか)」をサーバー側でチェック
- セッションCookieに
HttpOnly/Secure/SameSite - 入力値をサーバー側で検証(Zod等)
- シークレットがリポジトリ・ブラウザに含まれていない
- セキュリティヘッダー(CSP等)を設定
- 本番でエラー詳細を表示していない
-
npm auditで重大な脆弱性なし - ログイン・フォームにレート制限
- バックアップと復元手順がある
参考
- OWASP Top 10: Webアプリの代表的なリスクのランキング。迷ったらここを見る
- IPA「安全なウェブサイトの作り方」: 日本語で読める定番資料