前回のレート制限の記事では、入口で「誰が・何回」を数えて止めるしくみを見ました。でも、回数が少なくても、リクエストの中身そのものが危ない形をしていることがあります。
そこで出てくるのが、HTTP の中身を見て危ない形のリクエストを止める WAF(ウェブアプリケーションファイアウォール)です。この記事では、空港の手荷物検査のたとえを使いながら、OWASP CRS と Microsoft のドキュメントで確かめ、テスト用の環境で nginx に ModSecurity と OWASP CRS を組み込んで実際に動かした結果と一緒に、一歩ずつ図にしました。
- WAF とは何か、ファイアウォールや API ゲートウェイとどう違うのか
- ルール集(OWASP CRS)と、点数(アノマリスコア)で止めるしくみ
- 記録だけのモードから始めて、止めるモードに切り替える進め方
- nginx + ModSecurity で、止まる・通る・誤検知を直すまでを試す方法
- Azure での WAF の置き場所(Front Door・Application Gateway)と、WAF だけでは守れないこと
先に覚えておく3つの言葉
最初に、いちばん大事な3つの言葉を、空港の手荷物検査にたとえて押さえておきます。
ほかにも、この記事にはいくつか専門の言葉が出てきます。先に、ざっくりした意味を並べておきます。読んでいて迷ったら、ここに戻ってきてください。
| 言葉 | ざっくりした意味 | 空港でたとえると |
|---|---|---|
| ModSecurity | オープンソースの WAF のエンジン。nginx などに組み込んで使う | 検査の機械 |
| OWASP CRS | ModSecurity などで使う、攻撃の形を集めたルール集 | 持ち込み禁止品のリスト |
| アノマリスコア | 合ったルールの点数の合計。しきい値以上で止める | 怪しさの点数 |
| しきい値 | 止めるかどうかを分ける点数。CRS の初期値は 5 | 「この点数から別室へ」 |
| 誤検知 | 正しいリクエストなのに、ルールに合って止められること | ただの水筒で止められる |
| 除外 | 決めた場所・欄・ルールだけ、照らさないようにする設定 | 「この水筒は確認済み」 |
この記事の地図
WAF は入口でリクエストの中身を照らし、危ない形のものは奥に届く前に 403 で止めます。
先に全体の地図を見ておくと、迷いにくくなります。
WAF は、URL や本文などの中身をルールと照らし、合ったら点数を足します。合計がしきい値以上なら 403 を返し、奥には送りません。通ったリクエストは、API ゲートウェイで「誰か・回数」を確かめられ、最後に奥の API で「この人がこのデータを扱ってよいか」を確かめられます。WAF は、その前に立つ係です。
まずはたとえで:空港の手荷物検査
WAF は、API の入口でする「手荷物検査」です。
手荷物検査がない空港を思い浮かべてください。危ない物もそのまま機内に持ち込まれ、乗務員が1人ずつ確かめるしかありません。見落としたら、機内で困ります。
手荷物検査がある空港では、乗る前に入口で、持ち込み禁止品のリストと照らして止めます。リストは専門家が更新してくれます。ただし、搭乗券を見て「この便に乗ってよいか」を確かめるのは別の係です。WAF も同じで、中身の形は見ますが、「この人がこの操作をしてよいか」は見ません。
WAF とは何か、ほかの係との違い
ファイアウォールは「どこから来たか」、WAF は「中身の形」、API ゲートウェイは「誰か・回数」を見ます。
Microsoft のドキュメントは、Azure Web Application Firewall を、よく知られた攻撃や脆弱性からウェブアプリケーションを1か所でまとめて守るもの、と説明しています。そして、SQL インジェクションやクロスサイトスクリプティングは、もっともよくある攻撃の1つだとしています。また、IP アドレスやポートなどネットワークの層を見る Azure Firewall と違い、WAF は HTTP の層だけで動き、ヘッダ・クエリ・本文・Cookie の意味を理解して照らす、と書き分けています。
| 係 | 止めるもの | 空港でたとえると |
|---|---|---|
| ファイアウォール | 来てはいけない住所・入口からの通信 | 空港の門の警備 |
| WAF | 中身に危ない形が入ったリクエスト | 手荷物検査 |
| API ゲートウェイ | キーのない相手、使いすぎの相手 | 搭乗券の確認・人数の整理 |
| アプリ(API) | 権限のない操作 | 機内での席の確認 |
WAF が照らす場所
WAF は、URL だけでなく、ヘッダや Cookie、本文の中身まで照らします。API では本文が JSON のことが多いので、WAF が JSON を項目ごとに読めるかどうかが大事になります(STEP 4 で確かめます)。また、暗号化された HTTPS のままでは中身が読めないので、WAF の手前か WAF 自身で TLS をほどく(終端する)必要があります。Microsoft のドキュメントも、Front Door の WAF は TLS をほどいたあとのリクエストを照らす、と説明しています。
ルール集(OWASP CRS)
CRS は、よくある攻撃の形をグループごとにまとめた、無料で使えるルール集です。
OWASP CRS(Core Rule Set)は、OWASP が公開している「ModSecurity や互換の WAF で使う、汎用の攻撃検知ルール集」です。SQL インジェクション、クロスサイトスクリプティング、ファイルの読み取りなどから守ります。ルールには6桁の番号が付いていて、上の3桁でグループが分かります。
Azure の WAF のマネージドルール(DRS)も、この OWASP CRS をもとにしています。そのため、Azure の WAF のログでも、同じ 942100 や 949110 といった番号を目にします。
なお、ModSecurity は長くトラストウェーブ社が管理してきましたが、2024年1月に OWASP に引き継がれました。
点数で止めるしくみ(アノマリスコア)
1つのルールに合っただけでは止めず、合計の点数がしきい値以上なら止めます。
CRS のドキュメントによると、ルールに合っても、その場では止めません。合ったルールの重さに応じて点数を足していき、リクエストの本文まで照らし終わったところで、合計がしきい値以上かを見て止めるかを決めます(そこで止めるのが 949110 というルールです)。しきい値の初期値は、リクエストが 5、レスポンスが 4 です。
| 重さ(severity) | 足される点数 | どういうことか |
|---|---|---|
| CRITICAL | 5 | 1つ合うだけで、しきい値 5 に届く |
| ERROR | 4 | ほかのルールと合わせて届く |
| WARNING | 3 | ほかのルールと合わせて届く |
| NOTICE | 2 | 単独では届かない |
こうすると、少し怪しいだけのリクエストは通し、怪しい点がいくつも重なったリクエストだけを止められます。空港でいえば、「ベルトのバックルが鳴っただけでは止めないが、いくつも重なったら別室で確かめる」ような考え方です。
厳しさの段階(パラノイアレベル)
CRS には、どこまで厳しく照らすかを決めるパラノイアレベル(PL)が4段階あります。CRS のドキュメントは、上げるほど見逃しは減るが、正しい通信まで止める誤検知が増える、と説明しています。テスト用の環境の CRS でも、初期値は PL1 でした(STEP 1 で確かめます)。
動かし方(モード)
WAF には、止めずに記録だけするモードと、記録して止めるモードがあります。テスト用の環境に入った ModSecurity の推奨設定ファイルにも、「導入直後の混乱を減らすため、最初は記録だけ(DetectionOnly)で始めるように」と書かれていました。
全体の流れ
ここからは、注文 API の前に WAF を置く例で、流れを順に見ます。準備、通るとき、止めるときの3つです。
準備:記録だけで始めて、誤検知を直してから止める
あらすじ:管理者はルール集を入れ、まず記録だけで動かします。記録を読んで誤検知だけを狭く除外し、それから止めるモードに切り替えます。
| 手順 | 誰が・どこで | 何をするか | ルール・記録はどうなるか |
|---|---|---|---|
| ① | 管理者 → WAF | ルール集(CRS)を入れる | 攻撃の形のリストが入る |
| ② | 管理者 → WAF | 記録だけで動かす | DetectionOnly(Azure では Detection) |
| ③ | WAF | 本番の通信を照らして記録する | 止めずに奥へ通す |
| ④ | 管理者 | 記録を読む | 正しい通信が引っかかっていないかを見る |
| ⑤ | 管理者 → WAF | 誤検知だけを除外する | 場所・欄・ルールを絞る |
| ⑥ | 管理者 → WAF | 止めるモードに切り替える | On(Azure では Prevention) |
通るとき:照らしても点数がたまらない
あらすじ:ふつうのリクエストは、ヘッダも本文も照らされますが、合うルールがないので点数がたまらず、そのまま奥に届きます。
| 手順 | 誰が・どこで | 何をするか | 点数はどうなるか |
|---|---|---|---|
| ⑦ | アプリ → WAF | JSON の本文を付けて送る | ― |
| ⑧ | WAF | ヘッダを照らす | プロトコル違反などがなければ 0 |
| ⑨ | WAF | 本文を読み、項目ごとに照らす | 危ない形がなければ 0 |
| ⑩ | WAF | 点数を合計する | 0 → しきい値 5 未満 |
| ⑪ | WAF → API | 転送する | ― |
| ⑫ | バックエンド API | 仕事をする | ― |
| ⑬⑭ | API → WAF → アプリ | レスポンスを返す | 200 |
止めるとき:点数がしきい値に届いたら 403
あらすじ:危ない形の文字列が入っていると、合ったルールの点数が足されます。合計がしきい値に届くと、WAF は奥に送らずに 403 を返し、記録を残します。
| 手順 | 最初の図では | この図では |
|---|---|---|
| ❶ | ⑦ ふつうの本文 | 危ない形の文字列が入っている |
| ❷ | ⑨ 合うルールがない | 942100 に合い、+5 点 |
| ❸ | ⑩ 合計 0 | 合計 5 ≥ しきい値 5。949110 が止めると決める |
| ❹ | ⑪〜⑭ 転送して 200 | 奥へは送らずに 403 |
| ❺ | ― | ルール ID・点数・URL を記録する |
誤検知と除外
誤検知は、ルールごと止めずに「場所・欄・ルール」を絞って除外します。
CRS のドキュメントは、正しいリクエストがルールに合ってしまうことを誤検知(false positive)と呼び、ルールの除外を書いて直すよう案内しています。CRS のルールファイルそのものを書き換えると、ルールを更新したときに消えてしまうので勧めていません。
| 直し方 | どういうものか | 気をつけること |
|---|---|---|
| 実行時の除外 | ctl:ruleRemoveTargetById などで、決めたリクエストのときだけ、決めた欄をそのルールから外す | CRS より前に書く |
| 起動時の除外 | SecRuleRemoveById などで、起動したときからルールを外す | すべてのリクエストに効く |
| ルールを止める | そのルールを使わない | その攻撃の形を、どこでも見なくなる |
除外は、狭ければ狭いほど安全です。この記事の「試してみる」では、「Q&A の投稿 API(/qa/posts)の、本文の body 欄だけ、ルール 942100 だけ」を外します。
WAF だけでは守れないこと
WAF は「形」を見る係です。「この人がこのデータを扱ってよいか」は見ません。
Microsoft のドキュメントは、攻撃をアプリのコードの中だけで防ぐのは難しく、WAF なら1か所でまとめて、よく知られた脆弱性への手当てを早くできる、と説明しています。一方で、WAF はリクエストの形を照らすものなので、正しい形のまま他人の注文番号を指定するような使い方は、形だけでは見分けられません。そうした「認可」の弱さは、次の記事の OWASP API Security Top 10 で扱います。WAF は、認証・認可やアプリの修正と重ねて使う守りの1枚です。
試してみる(nginx + ModSecurity + OWASP CRS)
ここからは、テスト用の環境で、nginx 1.24.0 に ModSecurity を組み込み、OWASP CRS を読み込ませて動かした結果を見ていきます。奥の API には、API ゲートウェイの記事で使ったものに POST を受ける処理を足した、Python の小さなサーバを使います。
Ubuntu 24.04 のパッケージで入れた、ModSecurity 3.0.12、nginx 用のコネクタ 1.0.3、OWASP CRS 3.3.5 です。2026年11月の時点で、CRS の最新は 4 系(OWASP のプロジェクトページでは 4.29.0)なので、本番で使うときは新しい版を確かめてください。
試験で送る 1' OR '1'='1 や <script>alert(1)</script> は、WAF が動いているかを確かめるときによく使われる、SQL インジェクションや XSS の形をまねたテスト用の文字列です。自分で管理していない環境には送らないでください。
WAF を組み込んで動かす(図10の①)
確かめること:nginx に ModSecurity が組み込まれ、CRS のルールが読み込まれること。
やっていること:入口に検査の機械を置き、禁止品のリストを読み込ませます。
waf.conf(nginx の設定)
load_module /usr/lib/nginx/modules/ngx_http_modsecurity_module.so;
worker_processes 1;
error_log logs/error.log;
pid logs/nginx.pid;
events {}
http {
access_log logs/access.log;
server {
listen 127.0.0.1:8080;
modsecurity on;
modsecurity_rules_file /srv/waf/modsec/main.conf;
location /orders/ {
proxy_pass http://127.0.0.1:9001/;
}
location /qa/ {
proxy_pass http://127.0.0.1:9002/;
}
location /detect/ {
modsecurity_rules 'SecRuleEngine DetectionOnly';
proxy_pass http://127.0.0.1:9001/;
}
}
}
load_module:ModSecurity を nginx に組み込むmodsecurity on:この server で WAF を使うmodsecurity_rules_file:読み込むルールのファイル(main.conf)/orders/・/qa/:注文 API と Q&A の投稿 API に渡す/detect/:この場所だけ記録だけのモードにする(STEP 5)
modsec/main.conf(読み込む順番)
Include /srv/waf/modsec/modsecurity.conf
Include /etc/modsecurity/crs/crs-setup.conf
Include /srv/waf/modsec/exclusions.conf
Include /usr/share/modsecurity-crs/rules/*.conf
除外ルールの exclusions.conf は、CRS のドキュメントのとおり、CRS のルール本体より前に読み込ませています。この時点では中身は空です。
奥の API(注文 API 9001番、Q&A 投稿 API 9002番)を動かす
python3 backend.py orders 9001 & python3 backend.py qa 9002 &
エンジンの主な設定を確かめる
grep -E "^SecRuleEngine|^SecRequestBodyAccess|^SecAuditEngine|^SecAuditLog " modsec/modsecurity.conf
SecRuleEngine On SecRequestBodyAccess On SecAuditEngine RelevantOnly SecAuditLog /srv/waf/logs/audit.log
CRS の初期値(しきい値・パラノイアレベル)を確かめる
grep -n "threshold=\|tx.paranoia_level=1\|critical_anomaly_score=" /usr/share/modsecurity-crs/rules/REQUEST-901-INITIALIZATION.conf
81: setvar:'tx.inbound_anomaly_score_threshold=5'" 90: setvar:'tx.outbound_anomaly_score_threshold=4'" 99: setvar:'tx.paranoia_level=1'" 126: setvar:'tx.critical_anomaly_score=5'"
設定を確かめて、動かす
nginx -t -p /srv/waf/ -c waf.conf
2026/11/02 15:35:58 [notice] 1121#1121: ModSecurity-nginx v1.0.3 (rules loaded inline/local/remote: 0/921/0) nginx: the configuration file /srv/waf/waf.conf syntax is ok nginx: configuration file /srv/waf/waf.conf test is successful
nginx -p /srv/waf/ -c waf.conf
2026/11/02 15:35:58 [notice] 1122#1122: ModSecurity-nginx v1.0.3 (rules loaded inline/local/remote: 0/921/0)
結果の読み方:
SecRuleEngine Onは止めるモード、SecRequestBodyAccess Onは本文も読む設定です。記録(監査ログ)は、引っかかったリクエストだけを残す設定(RelevantOnly)です。- CRS の初期値は、リクエストのしきい値が 5、レスポンスが 4、パラノイアレベルが 1、CRITICAL のルールが +5 点でした。
rules loaded … 0/921/0は、ファイルから 921 個のルールを読み込んだ、という意味です。
ふつうのリクエストは通る(図11の⑦〜⑭)
確かめること:WAF を入れても、ふつうのリクエストはこれまでどおり通ること。
やっていること:ふつうの荷物を持った人が、手荷物検査を通ります。
クエリ付きの GET
curl -s "http://localhost:8080/orders/list?item=apple"
{"service": "orders", "method": "GET", "path": "/list?item=apple"}
JSON の本文付きの POST
curl -s -H "Content-Type: application/json" -d '{"item":"apple","qty":2}' http://localhost:8080/orders/new
{"service": "orders", "method": "POST", "path": "/new", "received_bytes": 24}
結果の読み方:どちらも奥の注文 API まで届き、返事が返ってきました。照らしても合うルールがなかったので、点数がたまらなかったということです。
危ない形は止まる(図12の❶〜❺)
確かめること:危ない形の文字列が入ると 403 で止まり、どのルールに合ったかが記録に残ること。
やっていること:禁止品の形をした荷物を持って、検査を通ろうとします。
テスト用の文字列をクエリに入れて送る(番号だけ表示)
curl -s -o /dev/null -w "%{http_code}\n" "http://localhost:8080/orders/list?item=1'%20OR%20'1'%3D'1"
curl -s -o /dev/null -w "%{http_code}\n" "http://localhost:8080/orders/list?item=%3Cscript%3Ealert(1)%3C/script%3E"
403 403
監査ログで、合ったルールを見る
grep -oE '\[id "[0-9]+"\] \[rev "[^"]*"\] \[msg "[^"]*"\]' logs/audit.log
[id "942100"] [rev ""] [msg "SQL Injection Attack Detected via libinjection"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [id "941100"] [rev ""] [msg "XSS Attack Detected via libinjection"] [id "941110"] [rev ""] [msg "XSS Filter - Category 1: Script Tag Vector"] [id "941160"] [rev ""] [msg "NoScript XSS InjectionChecker: HTML Injection"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 15)"]
エラーログで、止めた記録を見る
tail -n 1 logs/error.log | grep -oE 'Access denied with code 403 \(phase 2\)|\[msg "[^"]*"\]|\[uri "[^"]*"\]'
Access denied with code 403 (phase 2)
[msg "Inbound Anomaly Score Exceeded (Total Score: 15)"]
[uri "/orders/list"]
結果の読み方:
- どちらも 403 でした。奥の注文 API には届いていません。
- SQL インジェクションの形は、942100(libinjection という部品で SQL インジェクションを見つけるルール)に合い、合計 5 点で 949110 が止めました。
- XSS の形は、941100・941110・941160 の3つに合い、合計 15 点でした。1つでもしきい値に届きますが、合ったルールの点数はすべて足されます。
- エラーログには、止めた理由が 949110 の「Inbound Anomaly Score Exceeded(点数の合計がしきい値を超えた)」として残ります。本当の原因(942100 など)は監査ログで見ます。
JSON の本文の中も照らす(図11の⑨)
確かめること:URL だけでなく、JSON の本文の中に危ない形があっても止まること。
やっていること:禁止品を、かばんの奥(本文)に入れて通ろうとします。
JSON の本文にテスト用の文字列を入れて送る
curl -s -o /dev/null -w "%{http_code}\n" -H "Content-Type: application/json" -d "{\"item\":\"1' OR '1'='1\"}" http://localhost:8080/orders/new
403
結果の読み方:本文の中でも 403 で止まりました。推奨設定ファイルには、Content-Type が application/json のときに本文を JSON として読む設定(ルール 200001)が入っていて、JSON の項目を1つずつ照らせます。API の前に WAF を置くときは、本文を読む設定が入っているか、送ってくる Content-Type が正しいかを確かめておきます。
記録だけのモード(図10の②③)
確かめること:DetectionOnly では止めずに通しながら、止めるはずだったことを記録すること。
やっていること:検査の機械は鳴らすけれど、通してしまい、鳴ったことだけを記録します。
記録だけの /detect/ に、同じテスト用の文字列を送る
curl -s -w "%{http_code}\n" "http://localhost:8080/detect/list?item=1'%20OR%20'1'%3D'1"
{"service": "orders", "method": "GET", "path": "/list?item=1'%20OR%20'1'%3D'1"}
200
監査ログの最後を見る
grep -oE 'ModSecurity: (Warning|Access denied)[^[]{0,40}|\[msg "[^"]*"\]' logs/audit.log | tail -n 4
ModSecurity: Warning. detected SQLi using libinjection. [msg "SQL Injection Attack Detected via libinjection"] ModSecurity: Warning. Matched "Operator `Ge' with parameter [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"]
結果の読み方:STEP 3 と同じ形なのに、今度は 200 で奥まで届きました。それでも監査ログには、Warning(警告)として 942100 と、合計 5 点だったことが残っています。止めるモードに切り替える前に、この記録を読んで、正しい通信が引っかかっていないかを確かめます。
誤検知を、狭く除外して直す(図10の④⑤)
確かめること:正しいリクエストが止められたとき、場所・欄・ルールを絞って除外すれば通り、ほかは守られたままになること。
やっていること:「この人のこの水筒は確認済み」と登録し、ほかの荷物はこれまでどおり検査します。
社内の Q&A 掲示板の API に、「SQL の書き方」の質問を投稿するとします。本文に SQL の文が入るのは、この API では正しい使い方です。
SQL の文を本文(body 欄)に入れて、Q&A に投稿する
curl -s -w "%{http_code}\n" -H "Content-Type: application/json" -d '{"title":"How to write SQL","body":"SELECT name FROM products WHERE price < 100"}' http://localhost:8080/qa/posts
<html> <head><title>403 Forbidden</title></head> <body> <center><h1>403 Forbidden</h1></center> <hr><center>nginx/1.24.0 (Ubuntu)</center> </body> </html> 403
監査ログで、合ったルールを見る
grep -oE '\[id "[0-9]+"\] \[rev "[^"]*"\] \[msg "[^"]*"\]' logs/audit.log | tail -n 2
[id "942100"] [rev ""] [msg "SQL Injection Attack Detected via libinjection"]
[id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"]
正しい質問なのに、942100 に合って止められてしまいました。誤検知です。そこで、除外ルールを書きます。
modsec/exclusions.conf(除外ルール)
SecRule REQUEST_URI "@beginsWith /qa/posts" \ "id:10001,phase:1,pass,nolog,ctl:ruleRemoveTargetById=942100;ARGS:json.body"
「URL が /qa/posts で始まるときだけ、ルール 942100 から、JSON の body 欄(ARGS:json.body)を外す」という意味です。欄の名前は、監査ログの中の ARGS:json.body という表示で確かめました。
設定を読み直す
nginx -s reload -p /srv/waf/ -c waf.conf
2026/11/02 15:35:58 [notice] 1142#1142: ModSecurity-nginx v1.0.3 (rules loaded inline/local/remote: 0/922/0)
2026/11/02 15:35:58 [notice] 1142#1142: signal process started
同じ投稿をもう一度送る
curl -s -w "%{http_code}\n" -H "Content-Type: application/json" -d '{"title":"How to write SQL","body":"SELECT name FROM products WHERE price < 100"}' http://localhost:8080/qa/posts
{"service": "qa", "method": "POST", "path": "/posts", "received_bytes": 81}
200
今度は title 欄にテスト用の文字列を入れて送る
curl -s -o /dev/null -w "%{http_code}\n" -H "Content-Type: application/json" -d "{\"title\":\"1' OR '1'='1\",\"body\":\"hello\"}" http://localhost:8080/qa/posts
403
結果の読み方:
- 読み込んだルールが 921 個から 922 個に増えました。除外ルールが1つ足されたからです。
- 同じ質問の投稿が、今度は 200 で Q&A の API まで届きました。
- title 欄に危ない形を入れると、同じ
/qa/postsでも 403 で止まりました。外したのは body 欄の 942100 だけなので、ほかの欄はこれまでどおり守られています。
ここからは Microsoft の場合です
Azure での WAF の置き場所
Azure では、WAF は Front Door(世界の入口)か Application Gateway(リージョンの入口)に付けます。
Microsoft のドキュメントによると、Azure Web Application Firewall は、Application Gateway、Application Gateway for Containers、Front Door などと組み合わせて使います。
| 置き場所 | 特徴 | 向いているとき |
|---|---|---|
| Front Door の WAF | 世界中のエッジで照らす。仮想ネットワークに入る前に止める | 複数のリージョンに置いたアプリ |
| Application Gateway の WAF | リージョンごとの入口。仮想ネットワークの前に置く | 1つのリージョンのアプリ・API Management の前 |
| 両方を重ねる | Front Door で広く守り、Application Gateway でリージョンごとに調整する | リージョンごとに違う守り方が要るとき |
Microsoft のアーキテクチャガイド「Protect APIs by using Application Gateway and API Management」では、外からの通信を Application Gateway の WAF で照らしてから、仮想ネットワークの中の API Management に渡す構成を紹介しています。Front Door の WAF は、Premium ではすべての機能が使えますが、Standard ではカスタムルールだけです。
WAF ポリシーの中身
| 項目 | 内容 | この記事の試験でいうと |
|---|---|---|
| マネージドルール | DRS(Default Rule Set)など。新しく作るなら DRS 2.2 が勧められていて、OWASP CRS 3.3.4 に Microsoft の脅威インテリジェンスのルールを足したもの。点数で止める方式 | OWASP CRS 3.3.5 |
| カスタムルール | 自分で書くルール。マネージドルールより先に評価され、合えばそこで決まる | ― |
| 除外 | ルールセット・グループ・ルールの単位で、決めた欄を外す | exclusions.conf |
| モード | Detection(記録だけ)と Prevention(止める) | DetectionOnly と On |
Application Gateway の WAF でも、点数のしくみは CRS と同じです。重さごとの点数は CRITICAL 5・ERROR 4・WARNING 3・NOTICE 2 で、合計が 5 以上なら、Prevention モードでは止め、Detection モードでは記録だけします。ログでは、点数を足したルールが Matched、止めると決めたルールが Blocked または Detected と表示されます。パラノイアレベルは、Azure の WAF では PL1 と PL2 までで、PL3・PL4 は使えません。
Microsoft のドキュメントは、マネージドルールは WAF ポリシーで Detection モードのまま有効になるのが初期値で、切り替えのときはまず Detection で正しい通信を確かめてから止めるようにと案内しています。一方で、本番を Detection のまま置いておかないように、とも注意しています。
誤検知の実例として、Microsoft の API Management のドキュメントは、Application Gateway の WAF のルール 942200 が、開発者ポータルからの OpenAPI の定義のダウンロードを止めてしまうことがあると注意しています。API の前に WAF を置くときは、この記事の STEP 6 のような調整が要る、ということです。
よくある間違い
| 間違い | どうするか |
|---|---|
| いきなり止めるモードで入れる | 記録だけで始め、記録を読んで誤検知を直してから止める |
| 記録だけのモードのまま本番を続ける | 調整が終わったら止めるモードに切り替える |
| 誤検知のたびに、ルールごと止める | 場所・欄・ルールを絞って除外する |
| 949110 を除外する | 949110 は合計で止めるだけのルール。その前に合ったルール(942100 など)を見る |
| CRS のルールファイルを直接書き換える | 除外は別のファイルに書く。更新で消えない |
| WAF を置いたので、アプリの入力の確認をやめる | WAF は守りの1枚。アプリ側でも入力と権限を確かめる |
| WAF を通らずに奥へ届く道が残っている | 奥の API は WAF(Front Door など)からの通信だけを受ける |
949110 については、Microsoft のドキュメントも、点数で止めるルールではなく、点数を足した元のルールを除外するようにと注意しています。また、Front Door の WAF を使うときは、奥のサーバが Front Door からの通信だけを受けるようにしておかないと、WAF を飛ばして直接届いてしまう、と説明しています。
うまくいかないとき
WAF がうまくいかないときは、まず記録で「どのルールに合ったか」と「どの欄か」を確かめます。
| 困っていること | よくある原因 | 確認すること |
|---|---|---|
| 正しいリクエストが 403 | 誤検知 | 監査ログ(Azure では WAF のログ)のルール ID と欄の名前(ARGS:…) |
| 949110 だけが見える | 合計で止めたルールだけを見ている | 同じリクエストで、その前に合ったルール |
| 危ない形なのに通る | 記録だけのモードのまま。本文を読んでいない | SecRuleEngine(Azure ではモード)と SecRequestBodyAccess |
| JSON の本文が照らされていない | Content-Type が JSON になっていない | 送ってくる Content-Type と、本文を読む設定 |
| WAF を飛ばして届く | 奥の API が外から直接受けている | 奥の API の受け付け元の制限 |
| 記録に秘密が残る | 本文やヘッダがそのまま記録される | ログの項目を伏せる設定(Azure ではログのスクラブ) |
WAF の記録には、リクエストの中身がそのまま残ることがあります。Microsoft のドキュメントも、WAF のログには Authorization ヘッダや Cookie、本文などが含まれうるので、伏せる設定(スクラブ)をするよう勧めています。この記事の試験の監査ログにも、送った本文がそのまま残っていました。
まとめ
| 項目 | 内容 | 空港でたとえると |
|---|---|---|
| WAF | HTTP の中身を照らし、危ない形のリクエストを入口で止める | 手荷物検査 |
| ルール集 | OWASP CRS。Azure の DRS もこれがもと | 持ち込み禁止品のリスト |
| 止め方 | 合ったルールの点数を合計し、しきい値(5)以上なら 403 | 怪しさが重なったら別室へ |
| 進め方 | 記録だけで始め、誤検知を直してから止める | 試行期間のあと本運用 |
| 誤検知 | 場所・欄・ルールを絞って除外する | 「この水筒は確認済み」 |
| Azure | Front Door か Application Gateway に WAF ポリシーを付ける | 空港の入口か、ターミナルの入口 |
| 限界 | 権限や業務の正しさは見ない。認証・認可と重ねる | 搭乗券の確認は別の係 |
参考にした資料
この記事は、次の公式ドキュメントで内容を確かめて書きました。仕様や対応状況は変わることがあるので、最新の情報はリンク先で確かめてください。
- OWASP CRS(OWASP):CRS とは何か、守る攻撃の種類、最新の版
- Anomaly Scoring・Paranoia Levels・False Positives and Tuning(CRS Documentation):点数で止めるしくみとしきい値、重さごとの点数、4つのパラノイアレベル、誤検知の定義、実行時と起動時の除外、除外は CRS より前に書くこと
- Trustwave Transfers ModSecurity Custodianship to OWASP(OWASP):ModSecurity が OWASP に引き継がれたこと
- What is Azure Web Application Firewall?・Azure Web Application Firewall on Azure Front Door・Frequently asked questions for Azure Web Application Firewall on Azure Front Door(Microsoft Learn):WAF の役割、組み合わせるサービス、Front Door の Standard と Premium の違い、TLS をほどいてから照らすこと、Front Door からだけ受ける設定
- Web Application Firewall for Azure networks(Microsoft Learn):Azure Firewall との違い、DRS 2.2 のもとになった CRS の版、Front Door と Application Gateway の選び方、Detection から Prevention への切り替え、ログに秘密が残ることと伏せる設定
- Application Gateway WAF CRS and DRS rule groups and rules・Web Application Firewall exclusion lists・Web Application Firewall with Azure Front Door exclusion lists・Azure Front Door WAF DRS rule groups and rules(Microsoft Learn):DRS が OWASP CRS をもとにしていること、点数のしくみと Matched・Blocked・Detected、マネージドルールが Detection モードで有効になる初期値、カスタムルールが先に評価されること、パラノイアレベル、除外の単位
- Troubleshoot Web Application Firewall (WAF) for Azure Application Gateway・Troubleshoot anomaly scoring behavior and rule ID 949110 in Azure Application Gateway WAF(Microsoft Learn):狭い除外を勧めること、949110 ではなく元のルールを除外すること
- Protect APIs by using Application Gateway and API Management・Integrate API Management in an internal virtual network with Application Gateway(Microsoft Learn):API Management の前に WAF を置く構成、開発者ポータルでの 942200 の注意
あとがき
実際に動かしてみて印象に残ったのは、エラーログには 949110(合計で止めた)しか出ず、本当の理由の 942100 は監査ログを見ないと分からないことでした。誤検知を直すときに 949110 を外したくなる気持ちも分かりますが、それでは WAF が何も止めなくなってしまいます。また、SQL の質問が止められたように、「API によっては正しい入力」が必ず出てくるので、記録だけのモードで様子を見る期間は省けないと感じました。次の記事では、WAF だけでは守れない「認可」の弱さも含めて、OWASP API Security Top 10 を見ていきます。
コメント