WAF・DDoS対策が本番で失敗した話|AWS WAF v2の地雷と3年の運用知見
AWS WAFを本格導入した際のFalse Positive地獄とレート制限の失敗事例を、3年の運用経験からぶっちゃけます。実装で引っかかるポイントを先回りで解説。
WAF・DDoS対策2026|本番導入で学んだ実装の地雷と運用の本当
先日プロジェクトでWAFとDDoS対策を本格導入したんですよね。正直、事前のセキュリティレビューでは「AWS WAFとShield Advancedを入れとけば大丈夫」って感じだったんですけど、実際に運用が始まると、想像以上に複雑でした。特にFalse Positiveとの付き合い方とレート制限の設計で、めちゃくちゃハマりました。
うちのチームは過去に何度か不正アクセスの検知遅れで、信頼を失いかけた経験があるんです。だから今回は「ちょうどいい加減」じゃなくて「きちんと運用できるレベル」で構築することにしました。その過程で見えたこと、失敗したことを共有したいと思います。
AWS WAF v2の基本と実装で最初に引っかかったこと
AWS WAFの仕組みは理解していたつもりですが、実装段階で「あ、これ思ったより細かいな」って感じました。WAFのルールベースは大きく3つの層があります。
AWS マネージドルール — AWSが定期更新するOWASPベース、カスタムルール — IP制限やレート制限、IPレピュテーションリスト — 既知の悪質なIPの自動ブロック。こういった層構造になっているんですよね。
うちが最初にやってしまったのは、マネージドルールを全部有効化して、すぐに本番に流したことです。結果、正規ユーザーからのリクエストが大量にブロックされました。特に海外ユーザーが使うかもしれないAPI経由のアクセスが、SQLインジェクション検知ルールに引っかかり始めたんです。
実は、マネージドルールって「セキュリティ度100%」じゃなくて、業務に合わせて調整する前提なんですよね。自分たちのAPIがどんなパターンでリクエストを送るのか、まずそれを把握しないとダメでした。
{
"Name": "AWSManagedRulesCommonRuleSet",
"Priority": 1,
"Statement": {
"ManagedRuleGroupStatement": {
"VendorName": "AWS",
"Name": "AWSManagedRulesCommonRuleSet",
"ExcludedRules": [
{
"Name": "SizeRestrictions_BODY"
},
{
"Name": "GenericRFI_BODY"
}
]
}
},
"OverrideAction": {
"None": {}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "AWSManagedRulesCommonRuleSet"
}
}
こんな感じで、除外ルールを指定することになります。うちは最初「SizeRestrictions_BODY」を除外しました。理由は、ファイルアップロード機能で正規の大容量リクエストが弾かれたから。その後、アクセスログを分析して、3週間かけて10個のルール調整を加えました。地味に大変でしたよ。
CloudFront × Shield Advanced × WAFの組み合わせで学んだこと
DDoS対策といったら、AWS Shield Advancedを思い浮かべる人が多いと思うんです。でも正直な話、Shield Advancedだけじゃ不十分で、CloudFront + WAF + Shield Advancedの3層設計になってようやく実用的な対策になるんですよね。
うちが本番前に検証したのは、こういう構成です:
graph TB
Internet["Internet"] -->|DDoS Attack| CloudFront["CloudFront<br/>Layer 1: Shield Standard"]
CloudFront -->|Filtered Requests| WAF["AWS WAF v2<br/>Layer 2: Custom Rules"]
WAF -->|Safe Traffic| ShieldAdv["Shield Advanced<br/>Layer 3: DDoS Detection"]
ShieldAdv -->|Clean Traffic| ALB["Application Load Balancer"]
ALB -->|Internal| App["Application Servers"]
CloudFront -.->|Real-time Metrics| CloudWatch["CloudWatch<br/>Monitoring"]
WAF -.->|WAF Logs| S3["S3<br/>Log Storage"]
ShieldAdv -.->|Advanced Metrics| ShieldConsole["Shield Console"]
CloudFront単体だと、キャッシュ攻撃には強いんですが、Origin直撃のDDoS(CloudFrontをバイパスされるケース)に対応できません。それにWAFを組み合わせて、アプリケーション層の攻撃を検知・防止します。そしてShield Advancedで、より高度なボリュメトリック攻撃を検知します。
ただ、この3層設計が本当に重いんですよ。特にCloudFrontのキャッシュ戦略とWAFの相性がシビアでした。キャッシュキーの設定がちょっとズレるだけで、WAFのルール評価がおかしくなったりするんです。
レート制限で本番が一度死んだ話
正直、ここが一番痛い経験です。うちは「1IPあたり1秒間に100リクエスト」っていうレート制限ルールを設定しました。シンプルで、攻撃的なスクレイピングを止めるには十分だと思ったんです。
ですが、本番流してから30分で、正規ユーザーから大量の「403 Forbidden」エラーが報告されました。原因はバッチ処理です。あるユーザーが、正規の範囲内でAPIを連続呼び出ししていただけなんです。その方は、DataGridで50行のデータをロードするのに、APIを60回呼び出していた。つまり、0.83秒間に60リクエストですね。
# こういうクライアント側の実装があった
for row in data_rows:
response = await api.fetch(f"/api/data/{row.id}")
# キャッシュなし、連続呼び出し
process(response)
レート制限を設計するには、実装側も考慮する必要があります。うちはこの失敗から、以下の対策を入れました。
APIレスポンスにキャッシュヘッダー追加 — クライアント側でも一時的にキャッシュするよう促す。バッチAPI新設 — 複数データを一度に取得できるエンドポイント実装。段階的なレート制限 — 初日は200req/sec、1週間後に100req/secに下げるという戦略です。
{
"Name": "RateLimitRule",
"Priority": 2,
"Statement": {
"RateBasedStatement": {
"Limit": 2000,
"AggregateKeyType": "IP",
"ScopeDownStatement": {
"ByteMatchStatement": {
"SearchString": "/api/",
"FieldToMatch": {
"UriPath": {}
},
"TextTransformation": "LOWERCASE",
"PositionalConstraint": "STARTS_WITH"
}
}
}
},
"Action": {
"Block": {}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "RateLimitRule"
}
}
2000(Limit)= 2秒間に200リクエストという意味です。この設定で、正規のバッチ処理は許可しつつ、攻撃的なボットはブロックできるようになりました。
IPホワイトリスト管理の運用地獄
WAFでよくある実装パターンが、特定のIPからのアクセスを無条件に許可することです。例えば、社内VPNのIP、パートナー企業のIPなんかですね。
うちも最初、「ホワイトリストIPは全ルールをスキップする」という設定にしていました。
{
"Name": "IPWhitelist",
"Priority": 0,
"Statement": {
"IPSetReferenceStatement": {
"Arn": "arn:aws:wafv2:ap-northeast-1:xxx:regional/ipset/whitelist-ips/xxx"
}
},
"Action": {
"Allow": {}
},
"OverrideAction": {
"None": {}
}
}
ただね、パートナー企業のネットワーク構成が変わるたびに、IP追加依頼が来るんです。それを手作業で対応していたら、1ヶ月で30IPぐらい溜まりました。その後、うっかり1つのIPを削除したら、パートナーの本番が止まってしまった。ほんと冷や汗ですよ。
インシデント対応のベストプラクティス2026でも触れていますが、こういった「手作業の設定変更」は、ログと承認フロー無しでやるべきではありません。
今は、以下のように改善しました。IPホワイトリスト更新APIを作成して、チケット管理と連動させる。AWS Systems Manager Parameter Storeに保存し、IaCでバージョン管理する。自動テストで、ホワイトリストIP経由でアクセス可能かテストする。この3つが揃って初めて安心なんですよね。
# IPホワイトリスト更新の自動化例
import boto3
import json
waf_client = boto3.client('wafv2')
ssm_client = boto3.client('ssm')
def update_ipset_from_parameter_store():
# Parameter Storeからホワイトリストを取得
response = ssm_client.get_parameter(
Name='/waf/whitelist-ips',
WithDecryption=False
)
whitelist_ips = json.loads(response['Parameter']['Value'])
# WAFのIPセットを更新
waf_client.update_ip_set(
Name='whitelist-ips',
Scope='REGIONAL',
Id='xxx-xxx-xxx',
Addresses=whitelist_ips['addresses'],
LockToken='xxx'
)
print(f"Updated {len(whitelist_ips['addresses'])} IPs")
if __name__ == "__main__":
update_ipset_from_parameter_store()
False PositiveとBlockedリクエストの監視
WAFを本番に入れたら、必ずやることがログの監視と分析です。知られていないんですが、WAFのログだけで月に数GBになるんですよ。それをクエリするのが大変。
うちは以下のような構成で監視しています。CloudWatch Logsに全ブロックリクエストを送信して、Athenaで集計分析するんですね。
xychart-beta
title "WAF Blocked Requests Daily (2026年6月)"
x-axis [6/1, 6/2, 6/3, 6/4, 6/5, 6/6, 6/7]
y-axis "Requests" 0 --> 5000
line [120, 145, 312, 98, 87, 156, 203]
このデータから、6/3に異常があったことがわかります。原因は、API仕様が変わったのに、古いクライアントアプリが動いてたからです。WAFのSQLインジェクション検知ルールが、パラメータフォーマットの違いで引っかかっていました。
CloudWatch Logsにフィルターをセットして、毎日10件以上ブロックされたルールをアラートするようにしました。
[time, request_id, action="BLOCK", rule_group_name != "RateLimitRule", ...]
| stats count() as block_count by rule_group_name
| filter block_count > 10
これで、False Positiveを早期に発見できるようになりました。業務影響が最小限で済むんですよね。
OWASPトップ10を意識した設定
OWASP Top 10 2024対策とも関連しますが、WAFのマネージドルールって実は、OWASPで定義されている脆弱性パターンに基づいています。
うちのチームで重視しているのは、以下の3つです。
| 脆弱性 | WAFルール | 対応内容 |
|---|---|---|
| SQLインジェクション | SQLiRuleSet | クエリパラメータ検査・エスケープ |
| クロスサイトスクリプティング (XSS) | XSSRuleSet | HTMLエンコード検査・サニタイズ |
| 認証回避・ブルートフォース | RateLimitRule | IP単位のレート制限・ログ監視 |
マネージドルールだけで完全には防げないので、カスタムルールと併用する必要があります。
例えば、認証エンドポイント(/api/auth/login)への攻撃対策として、以下のカスタムルールを入れています。1IPあたり100リクエスト/秒を超えたら、ログインエンドポイントをブロックするという仕組みですね。
{
"Name": "BruteForceProtection",
"Priority": 3,
"Statement": {
"RateBasedStatement": {
"Limit": 100,
"AggregateKeyType": "IP",
"ScopeDownStatement": {
"ByteMatchStatement": {
"SearchString": "/api/auth/login",
"FieldToMatch": {
"UriPath": {}
},
"TextTransformation": "LOWERCASE",
"PositionalConstraint": "STARTS_WITH"
}
}
}
},
"Action": {
"Block": {}
}
}
コスト意識の話
最後に、ぶっちゃけ気になるのがコストですよね。AWS WAFって実は、ルール数に応じて課金されます。料金体系をちゃんと把握してないと、予期しない請求が来るんですよ。
- ウェブACL: 月$5
- ルール: 1ルールあたり月$1
- リクエスト: 100万リクエストあたり月$0.60
うちの場合、マネージドルール(3つのグループ)+ カスタムルール(5つ)で、月$13 + リクエスト料金。月間リクエストが500万だと、リクエスト料金だけで$3追加されます。つまり、月$16前後ですね。
Shield Advancedは月$3000なので、中小規模なら「WAF + Shield Standard」で十分かもしれません。ただ、DDoS攻撃を受けた場合のAWS DDoS Cost Protectionが付くのは、Shield Advancedの大きなメリットです。実際の攻撃で追加の通信量が発生しても、AWS側が吸収してくれるんですよ。
今のWAF運用で自動化していること
SCOWL定義や監視系は、できるだけ自動化しています。手作業は必ずミスを招くから、仕組みで防ぐアプローチをとってるんですね。
CloudWatch LogsからAthenaでクエリ — 日次でブロックパターンを分析する。Slack通知 — False Positiveの疑いがあったら即通知する。月1回のルール見直し — ビジネス要件とセキュリティ要件のバランスを確認する。
これらのおかげで、現在のWAF運用はほぼ自動で回るようになりました。人が定期的にチェックするのは月1回の見直しくらいです。
まとめ
正直、WAF・DDoS対策は「入れたら終わり」ではなくて、むしろそこからが始まりなんですよね。
- AWS WAFは奥が深い — マネージドルールをそのまま使うのではなく、ビジネス要件に合わせた調整が必須。除外ルール設定が本当に重要になってきます
- False Positiveとの付き合い方 — CloudWatch Logsを活用して、正規ユーザーへの影響を監視。ブロック件数の増減をトレンド化して、異常検知する仕組みが欠かせません
- 3層防御(CloudFront + WAF + Shield Advanced) — 単一の対策じゃなく、層別に設計することで、初めて実用的なDDoS対策になるんです
- IPホワイトリストはIaC化 — 手作業は禁止。パラメータストア + 自動テストで、ミスを防止する仕組みが大事
- レート制限はAPI設計とセット — クライアント側の実装を考慮して、適切な閾値を決める。急激な変更は避ける
次のステップとしては、WAFのログを機械学習で分析して、新種の攻撃パターンを自動検知するような仕組みを検討しています。その実装記録も、また書きたいですね。