- ブラウザURL入力
- DNS名前→IP
- ルータ経路選択
- ロードバランサ振り分け
- Nginx受け口今ここ
- アプリ処理
- DB保存
第37週はWAFが何を見てどう判定するかでした。今回は、その判定が正しい通信を止めてしまう場合を扱います。
まず結論
CRSは相手のアプリを知らないまま汎用のパターンで検出するため、正常な通信が攻撃に見えることが構造的に避けられません。これを誤検知(false positive)と呼びます。
直し方はルール本体を書き換えることではなく、除外ルール(rule exclusion)を外から足すことです。置き場所を間違えると効きません。
利用者がパスワードを `/bin/bash` に設定していました。CRSがこれを攻撃と誤検知した場合、ログイン処理が止まる以外に、どんな問題が起きるでしょうか。
当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。
汎用であることの代償
CRSは、守る相手のアプリを知りません。公式の言葉では「A fresh CRS deployment has no awareness of the web services that may be running behind it」。汎用の攻撃パターンだけで検出するので、正規の入力がたまたまそのパターンに一致することが起こります。
ブログの投稿欄に <h1> を含む本文を書けば、パラノイアレベル2ではHTMLタグを探すルールに一致します(公式の例)。入力は正しく、ルールも壊れていません。それでも遮断されます。誤検知はバグではなく、汎用性の代償です。
直し方 — ルールには触らない
まず、やってはいけない方法から。公式は「Making direct modifications to CRS rule files is a bad idea and is strongly discouraged」と述べます。直接編集はルールセットのフォークに等しく、更新のたびに手作業でやり直す羽目になるからです。
正しい方法は除外ルールを外から足すことです。除外には2つの軸があり、組み合わせで4通りになります。
ルール丸ごと外す 変数だけ外す設定時 SecRuleRemoveById SecRuleUpdateTargetByIdSecRuleRemoveByTag SecRuleUpdateTargetByTag実行時 ctl:ruleRemoveById ctl:ruleRemoveTargetByIdctl:ruleRemoveByTag ctl:ruleRemoveTargetByTag
先の投稿欄の例なら、ルールごと外すのではなくそのパラメータだけを対象から外します。他の入力への保護は残ります。
特定のルールが誤検知を起こしています。最も適切な対処はどれですか。
置き場所を逆にすると、黙って効かない
ここが実務で刺さる点です。公式は「It is crucial to put rule exclusions in the correct place, otherwise they may not work」と強調しています。
理由は動詞を見れば分かります。設定時の除外はルールを「削除」するので、削除される側が先に定義されていなければなりません。だからCRSの後に置きます。実行時の除外はルールを「変更」するので、そのルールが実行される前でなければ意味がありません。だからCRSの前に置きます。
CRSは REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example(実行時用)と RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example(設定時用)を同梱しており、末尾の .example を外して使います。ファイル名の BEFORE / AFTER がそのまま置き場所を表しています。
逆に置いても、エラーにはなりません。黙って効かないだけです。誤検知が直らないとき、除外の書き方ではなく置き場所を疑う必要があります。
WAFのパラノイアレベルと誤検知
レベルを上げると検出できる攻撃は増えますが、正規の通信を攻撃と判定する率も上がります。公式は「上げれば誤検知が増えるのは非常に起こりやすい」と明言しており、増えた分は除外ルールを書いて個別に打ち消します。
PL1 誤検知はほとんど起きない。既定PL2 実利用者のデータを扱う水準。誤検知は出る前提PL3 誤検知が多い。除外を書けることが前提PL4 極めて厳しく、大量の誤検知を覚悟する
CPUの分岐予測(第6週)
第6週で見たとおり、CPUは条件が確定する前に行き先を予測して進みます。外れれば進めた分を捨てて15〜20サイクル失いますが、毎回待つよりは速いので、外す前提で予測しています。
当たり そのまま継続。損失ゼロ外れ 進めた分を破棄して詰め直し(15〜20サイクル)方針 外れを無くすのではなく、外れた分を払う
パラノイアレベルと分岐予測。片方はセキュリティ、もう片方はCPUの話ですが、設計の考え方に共通するものがあります。それは何で、代価はどこで払われているでしょうか。
「外れをゼロにしない」と決めた結果、何を得て、そのぶん何を負担しているかを比べてください。
パラノイアレベルを上げたいが、誤検知で正規利用者を止めたくありません。公式が勧める手順はどれですか。
この章のまとめ
- 誤検知は汎用性の代償。CRSは背後のアプリを知らないまま検出するので、正規の入力が攻撃パターンに一致しうる。
- 直し方は除外ルール。CRSのファイルを直接編集しない(フォークになり、更新のたびにやり直しになる)。
- 除外は2軸4分類。設定時/実行時 × ルール丸ごと/変数だけ。誤検知した入力だけを外せば保護は残る。
- 置き場所を逆にすると黙って効かない。 設定時は削除なのでCRSの後、実行時は変更なのでCRSの前。
- レベルを上げるときは閾値ではなく
tx.detection_paranoia_levelを先に上げる。閾値を上げる方法は公式が「risky」と呼ぶ。 - 放置してはいけない理由は3つ。アラート疲れ / パスワードが平文でログに残る / 利用者体験の悪化。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
CRSで誤検知が構造的に避けられない理由はどれですか。
用語集(5語)
- 誤検知 (false positive)
- 正規のやりとりがルールに誤って一致すること。公式は「When a genuine transaction causes a rule from CRS to match in error」と定義し、除外ルールで調整すべきものとしている。
- 除外ルール (rule exclusion, RE)
- CRSのファイルを書き換えずに、外からルールの適用を取り消したり対象を狭めたりする仕組み。ルールセットをそのまま保てるので、更新が容易になる。
- 設定時の除外 / 実行時の除外
- 設定時は起動・再読み込み時に一度だけ適用されルールを削除する(`SecRuleRemoveById` など)。実行時はトランザクションごとに適用されルールを変更する(`ctl:ruleRemoveById` など)。置き場所は前者がCRSの後、後者がCRSの前。
- detection paranoia level
- `tx.detection_paranoia_level`。ここまでのレベルのルールを実行するが、スコアに影響するのは `tx.blocking_paranoia_level` までに限られる。既定では両者が同値で、レベルを安全に上げる足場になる。
- 早期ブロック (early blocking)
- フェーズ1と3の終わりにも判定を挟む設定。既定は無効。有効にすると判定は4回になるが、公式のアノマリスコアリングの文書は「潜在的なアラートを隠しうる」という欠点を挙げている。