+ 作るログイン
図を読み込んでいます…
1 / 29

WAF ― 封筒を開けて読む門番のしくみ

発行
アニメモ公式
版
第3版
改訂
コマ
全29

WAF の中で何が起きているのかを、図を動かして追います。リクエストの分解・正規化・ルールの照合と採点・判定の4段、誤検知と例外、パラノイアレベル、肯定モデルと否定モデル、読める長さの上限や読み方のずれによるすり抜け、オリジンへの直撃、仮想パッチ、最近の動きまで。

1本のふつうのリクエストに、攻撃が混ざる

大量の通信で押しつぶす攻撃は、量を見れば見分けられます。けれども、たった1本のリクエストの中身で仕掛ける攻撃もあります。検索窓に検索語のふりをして SQL の断片を入れる SQL インジェクション、入力欄に JavaScript を混ぜる XSS、../ で見せてはいけないファイルを読むパストラバーサル、公開済みの脆弱性を突くリクエストなどです。

こうした攻撃は、送り方も宛先もふつうのアクセスと変わりません。違うのは、封筒の中に入っている文字だけです。

このアニメモでは、その中身を読んで止める WAF(Web Application Firewall)が中で何をしているのかを、図を動かしながら追っていきます。特定の製品ではなく、多くの WAF に共通する考え方を扱い、例としてオープンソースの WAF で広く使われているルール集 OWASP CRS の仕組みをよく参照します。

  • WAF NOTES
  • 1 / 29
  • WAF ― 封筒を開けて読む門番
  • ノートPC
  • サーバー
  • HTTPS
  • クライアント
  • アプリ
  • 見た目はふつうの1本でも、中身が攻撃のことがある
  • SQL インジェクション
  • XSS(スクリプト注入)
  • パストラバーサル
  • 既知の脆弱性を突く
  • 入力に SQL を混ぜる
  • 入力に JS を混ぜる
  • ../ で上の階層を読む
  • 公開済みの穴を狙う
  • 量ではなく、1本の中身で仕掛ける攻撃がある。

ファイアウォールは、封筒の宛先しか見ない

ふつうのファイアウォールは、通信の送り元と宛先(IP アドレスとポート番号)を見て、通すかどうかを決めます。Web サイトなら「443番(HTTPS)への通信は許可」と設定するので、その番号宛ての通信はすべて通ります。

封筒にたとえると、ファイアウォールは表に書かれた宛先を確かめるだけで、封筒を開けません。中に SQL インジェクションの文字列が入っていても、宛先が正しければそのままアプリまで届きます。

ネットワークの層でいうと、ファイアウォールが見るのは主に第3層(IP)と第4層(TCP・UDP)です。HTTP の中身は第7層(アプリケーション層)にあり、そこを読むには別の仕組みが要ります。

  • WAF NOTES
  • 2 / 29
  • ファイアウォールは、封筒の宛先を見る
  • ノートPC
  • ファイアウォール
  • 注意
  • サーバー
  • HTTPS
  • クライアント
  • 宛先と番号を見る
  • アプリ
  • ファイアウォールが見るもの
  • 見ていないもの(封筒の中身)
  • 送り元 IP:203.0.113.5
  • q=' OR 1=1 --
  • 宛先ポート:443(HTTPS)
  • → 許可した番号なので通す
  • → そのままアプリに届く
  • 番号で許可した通信は、中身が何でも通る。

WAF は封筒を開けて、HTTP の中身を読む

WAF は、アプリの手前に立って HTTP のリクエストを開けて読みます。読む場所は、URL のパスとクエリ、ヘッダー、Cookie、そしてフォームや JSON で送られる本文です。そこに攻撃の型が含まれていれば止め、問題がなければアプリへ渡します。

HTTPS の通信は暗号化されているので、WAF が中身を読むには、WAF の位置で暗号をいったん解く必要があります。CDN やロードバランサーに組み込まれた WAF が多いのは、そこがもともと暗号を解く場所だからでもあります。

WAF はファイアウォールの代わりではなく、見る層が違う別の守りです。ファイアウォールが番号で絞り、WAF が中身で絞ります。

  • WAF NOTES
  • 3 / 29
  • WAF は封筒を開けて、HTTP の中身を読む
  • ノートPC
  • ファイアウォール
  • WAF
  • サーバー
  • 開封
  • クライアント
  • 宛先と番号を見る
  • 中身を読む
  • アプリ
  • WAF が読むもの(HTTP リクエストの中身)
  • POST /search HTTP/1.1
  • Host: shop.example
  • Cookie: session=8f3a…
  • q=' OR 1=1 --
  • WAF は第7層の中身を読んで、通すかを決める。

WAF の中は、4つの段の流れ作業

WAF の中で起きていることは、大きく4つの段に分けられます。

段やること
① 分解文字の並びとして届いたリクエストを、メソッド・パス・引数・ヘッダー・Cookie・本文などの項目に分ける
② 正規化URL エンコードや大文字小文字などの「変装」をほどき、ルールが比べやすい形にそろえる
③ 照合と採点項目ごとにルールを当て、当たったルールの重さに応じて点を足す
④ 判定合計点をしきい値と比べ、止める・通す・記録だけにするを決める

ここから先は、この4段を1つずつ開けて見ていきます。次のページから上に並ぶ帯が、いまどの段にいるかの目印です。

※③と④を点数で分ける作りは OWASP CRS の方式です。製品によっては、1本のルールが当たった時点で止める作りもあります。

  • WAF NOTES
  • 4 / 29
  • WAF の中は、4つの段の流れ作業
  • WAF の中
  • 書類
  • 更新
  • 検索
  • 天秤
  • ① 分解
  • ② 正規化
  • ③ 照合と採点
  • ④ 判定
  • 項目に分ける
  • 変装をほどく
  • ルールに当てる
  • 止めるか決める
  • パス・引数・本文など
  • デコード・小文字化など
  • 当たれば点を足す
  • 合計点としきい値を比べる
  • 入力:HTTP リクエスト → 出力:通す/止める/記録する
  • 分解 → 正規化 → 照合と採点 → 判定。

① 分解:届くのは、ただの文字の並び

WAF に届くリクエストは、改行で区切られた1本の文字の並びです。1行目にメソッドとパスとプロトコル、続いてヘッダーが並び、空行のあとに本文が続きます。

ルールで調べるには、まずこれを項目に分けなければなりません。どこまでがパスでどこからがクエリか、本文はフォームなのか JSON なのか。本文の形式は Content-Type ヘッダーが告げるので、WAF はそれを見て本文の読み方を切り替えます。

この「読み方」が、後で出てくるすり抜けの大きな原因になります。WAF とアプリが同じリクエストを違うふうに読めば、WAF が調べた中身と、アプリが実際に受け取る中身がずれてしまうからです。

  • WAF NOTES
  • 5 / 29
  • ① 分解:届くのは、ただの文字の並び
  • ① 分解
  • ② 正規化
  • ③ 照合と採点
  • ④ 判定
  • 届いたリクエスト(実際は1本の文字の並び)
  • パスの範囲も、本文の区切りも、まだ不明
  • POST
  • /search
  • ?page=2
  • HTTP/1.1
  • Host: shop.example
  • 本文の形式は Content-Type が告げる
  • Content-Type: application/x-www-form-urlencoded
  • Cookie: session=8f3a…
  • フォーム/JSON/multipart/XML …
  • (空行のあとが本文)
  • q=%27%20oR%201%3D1%20--
  • &sort=new
  • → 区切りを解釈して、項目ごとに分ける
  • 区切りを解釈して項目に分けなければ、読めない。

① 分解:項目ごとの表になる

分解が終わると、リクエストは項目と値の表になります。パスは /search、本文の引数 q の値は %27%20oR%201%3D1%20--、というように、どの値がどこに入っていたかが分かる形です。

WAF のルールは、この表の項目を指定して書かれます。「すべての引数の値を見る」「Cookie を見る」「ヘッダー User-Agent を見る」といった具合です。ModSecurity 系の WAF では、引数をまとめて ARGS、ヘッダーを REQUEST_HEADERS のように呼びます。

ただし、表にした段階の値は、まだ送られてきたままの形です。図の q は %27 のような記号の並びで、このままでは SQL の断片には見えません。そこで次の段で、変装をほどきます。

  • WAF NOTES
  • 6 / 29
  • ① 分解:項目ごとの表になる
  • ① 分解
  • ② 正規化
  • ③ 照合と採点
  • ④ 判定
  • 届いたリクエスト
  • メソッド
  • POST
  • 本文はフォーム形式として読み、
  • パス
  • /search
  • & と = で引数に分けた
  • クエリの引数 page
  • 2
  • プロトコル
  • HTTP/1.1
  • ヘッダー Host
  • shop.example
  • ヘッダー Content-Type
  • フォーム形式
  • ルールは項目を指定して当てる
  • Cookie の session
  • 8f3a…
  • 本文の引数 q
  • %27%20oR%201%3D1%20--
  • 本文の引数 sort
  • new
  • ルールは、この表の項目ごとに当てていく。

② 正規化:変装をほどく

同じ攻撃でも、書き方はいくらでも変えられます。' を %27 と URL エンコードする、OR を oR と大文字小文字を混ぜる、空白の代わりに /**/ のようなコメントを挟む、といった具合です。アプリ側は受け取ったあとでこれらを元に戻して解釈するので、攻撃としては同じように効きます。

ルールをすべての変装パターンについて書くのは不可能です。そこで WAF は、ルールに当てる前に値を決まった形にそろえます。これを正規化と呼びます。ModSecurity 系では、URL デコード(urlDecodeUni)、HTML の文字参照の解除(htmlEntityDecode)、小文字化(lowercase)、空白の圧縮などの変換関数を、ルールごとに指定して順に掛けます。

順番には意味があります。たとえば先に小文字にしてから URL デコードすると、%4F が後から大文字の O に戻って小文字化をすり抜けます。デコードしてから小文字にするのが基本です。

  • WAF NOTES
  • 7 / 29
  • ② 正規化:変装をほどく
  • ① 分解
  • ② 正規化
  • ③ 照合と採点
  • ④ 判定
  • URL デコード
  • 小文字にそろえる
  • %27%20oR%201%3D1%20--
  • %27 → ' %20 → 空白
  • oR → or
  • ルールが見る形
  • 同じ攻撃の、いろいろな書き方
  • ' OR 1=1
  • %27%20OR%201%3D1
  • ' oR 1=1
  • '/**/OR/**/1=1
  • ルールを1つの形で書けるよう、比べる前に形をそろえる
  • 攻撃者は同じ攻撃を、いろいろな書き方に変装させる。

② 正規化:ルールが比べる形になる

2つの変換を通ると、%27%20oR%201%3D1%20-- は ' or 1=1 -- になりました。ここまで来れば、「引用符のあとに or と常に真になる比較が続く」という SQL インジェクションの型にそのまま当てられます。

変換するのは、WAF の中で作った検査用の写しです。ModSecurity の説明でも、変換関数はメモリ上の写しに掛けられ、元のリクエストは書き換えないとされています。アプリには、送られてきたままのリクエストが渡ります。

ここに WAF の難しさが1つあります。WAF の戻し方とアプリの戻し方が食い違うと、WAF は無害だと判断したのに、アプリでは攻撃として働く、ということが起こります。正規化は、アプリがどう解釈するかを WAF が真似する作業でもあるのです。

  • WAF NOTES
  • 8 / 29
  • ② 正規化:ルールが比べる形になる
  • ① 分解
  • ② 正規化
  • ③ 照合と採点
  • ④ 判定
  • URL デコード
  • 小文字にそろえる
  • %27%20oR%201%3D1%20--
  • ' or 1=1 --
  • %27 → ' %20 → 空白
  • oR → or
  • ルールが見る形
  • 元のリクエストは書き換えず、検査用の写しだけを変換する
  • アプリと同じ戻し方をしないと、WAF とアプリで見える中身がずれる
  • 形をそろえるから、少ないルールで多くの変装を捕まえられる。

③ 照合と採点:ルールは止めずに、点を足す

正規化した値に、ルールを順番に当てていきます。OWASP CRS では、ルールが当たってもその場では止めません。代わりに、そのリクエストの異常スコアに点を足し、次のルールへ進みます。CRS はこれを「協調検知(collaborative detection)」と呼んでいます。

足す点は、ルールの重さ(severity)で決まっています。

重さ既定の点
CRITICAL5
ERROR4
WARNING3
NOTICE2

図では、最初のルール(引用符のあとに常に真になる比較)が当たり、5点が入りました。図のルール名と点の付き方は、説明のための例です。

  • WAF NOTES
  • 9 / 29
  • ③ 照合と採点:ルールは止めずに点を足す
  • ① 分解
  • ② 正規化
  • ③ 照合と採点
  • ④ 判定
  • 正規化した値
  • SQL:引用符のあとに or と真の比較
  • CRITICAL
  • +5
  • 合計点
  • ' or 1=1 --
  • SQL:行末のコメント記号 --
  • CRITICAL
  • 当たっても、その場では止めない。
  • 記号の多い値
  • WARNING
  • 点を足して、次のルールへ。
  • XSS:<script> などのタグ
  • CRITICAL
  • しきい値 5
  • パストラバーサル:../
  • CRITICAL
  • 5
  • ルールは「検知」だけ。止めるかは最後にまとめて決める。

③ 照合と採点:当たったルールの点が積み重なる

ほかのルールも当たり、合計は13点になりました。本物の攻撃は、いくつものルールに同時に引っかかることが多いので、点は大きくなりがちです。CRS の説明では、SQL インジェクションだけで50本以上のルールがあり、実際の攻撃は15点や20点を超えることもあるとされています。

一方、XSS やパストラバーサルのルールには当たっていません。ルールは攻撃の種類ごとに束になっていて、どのリクエストにもすべての束が当てられます。

点数方式のよいところは、検知と判定を切り離せることです。1本のルールだけでは決め手に欠ける小さな兆候も点として残しておき、最後に合計で判断できます。

  • WAF NOTES
  • 10 / 29
  • ③ 照合と採点:点が積み重なる
  • ① 分解
  • ② 正規化
  • ③ 照合と採点
  • ④ 判定
  • 正規化した値
  • SQL:引用符のあとに or と真の比較
  • CRITICAL
  • +5
  • 合計点
  • ' or 1=1 --
  • SQL:行末のコメント記号 --
  • CRITICAL
  • +5
  • 記号の多い値
  • WARNING
  • +3
  • 種類ごとの束を、すべて当てる。
  • XSS:<script> などのタグ
  • 当たらない
  • しきい値 5
  • 当たった分だけ点が増える。
  • パストラバーサル:../
  • 当たらない
  • 13
  • 当たったルールの点が積み重なっていく。

④ 判定:合計点をしきい値と比べる

リクエストを調べるルールがすべて終わったところで、合計点をしきい値と比べます。CRS の既定では、リクエストのしきい値は5点です。13点のリクエストは止められ、アプリには届かずに 403 などのエラーが返ります。ふつうの検索のように0点なら、そのままアプリへ渡ります。

既定値が5点なのは、CRITICAL のルールが1本当たっただけで止めるためです。CRS は、しきい値を上げると攻撃をすり抜けさせることになるので、上げるのは誤検知を調整する間の一時的な措置にとどめるよう注意しています。

判定は応答(レスポンス)にもあります。アプリが返す中身に情報漏れなどの兆候があれば点を足し、既定では4点以上で応答を止めます。止め方は「止める」だけでなく、「記録だけして通す」や、ブラウザに確認を挟む方式を選べる製品も多くあります。

  • WAF NOTES
  • 11 / 29
  • ④ 判定:合計点をしきい値と比べる
  • ① 分解
  • ② 正規化
  • ③ 照合と採点
  • ④ 判定
  • 拒否:403 を返す
  • 合計点
  • ' or 1=1 --
  • 13≧5
  • アプリには届かない
  • 通過:アプリへ渡す
  • しきい値 5
  • q=ワイヤレスイヤホン
  • 合計 0 点 < しきい値 5
  • 13
  • しきい値以上なら止め、未満なら通す。応答にも同じ判定がある。

誤検知:正しい利用まで止めてしまう

WAF は「攻撃に見えるか」を判断しているので、攻撃ではない正しいリクエストを止めてしまうことがあります。これを誤検知(false positive)と呼びます。

図は、技術ブログの管理画面で、SQL の例を含む記事を保存しようとした場面です。本文に SELECT * FROM users -- が入っているので SQL インジェクションのルールが当たり、合計がしきい値を超えて止められました。利用者から見ると「記事が保存できない」という、原因の分かりにくい不具合です。

ほかにも、外部サービスから届く Webhook の本文、HTML を含む入力欄、長いトークンを入れた Cookie などが誤検知を起こしやすい場所です。CRS は、誤検知を本物の攻撃と混ぜたまま放っておくのは危ないとして、きちんと取り除くよう求めています。

  • WAF NOTES
  • 12 / 29
  • 誤検知:正しい利用まで止めてしまう
  • 技術ブログの記事を保存
  • POST /admin/posts の content
  • SQL:SELECT … FROM の並び
  • +5
  • SELECT * FROM users --
  • CRITICAL
  • 合計点
  • SQL:行末のコメント記号 --
  • CRITICAL
  • +5
  • 記号の多い値
  • 当たらない
  • 403:記事を保存できない
  • XSS:<script> などのタグ
  • 当たらない
  • しきい値 5
  • 誤検知
  • パストラバーサル:../
  • 当たらない
  • 10
  • 攻撃ではない書き込みが、攻撃と同じ型に見えることがある。

例外は、ルール・場所・項目を絞って作る

誤検知は、ルールの例外を作って取り除きます。CRS の資料では、例外はできるだけ狭く書き、ルールをまるごと外すより、パスや引数を限った例外を選ぶよう勧めています。

図では、「パス /admin/posts の引数 content にだけ、当たっていた2本の SQL のルールを当てない」という例外を作りました。同じルールは、ほかのページやほかの引数では引き続き働きます。記事は保存できるようになり、合計点は0点に戻りました。

反対に「SQL のルールをサイト全体でオフにする」ような広い例外は、誤検知と一緒に本物の攻撃まで通してしまいます。例外を作るときは、どのルールを、どのパスの、どの項目で外すのかを必ずそろえて書きます。WordPress などよく使われるアプリ向けには、CRS が例外の詰め合わせ(プラグイン)を用意しています。

  • WAF NOTES
  • 13 / 29
  • 例外は、ルール・場所・項目を絞って作る
  • 技術ブログの記事を保存
  • POST /admin/posts の content
  • SQL:SELECT … FROM の並び
  • 例外
  • 合計点
  • SELECT * FROM users --
  • → 保存できた
  • SQL:行末のコメント記号 --
  • 例外
  • 記号の多い値
  • 当たらない
  • ○ 狭い例外
  • /admin/posts の content に
  • XSS:<script> などのタグ
  • 当たらない
  • だけ、この2本を当てない
  • しきい値 5
  • パストラバーサル:../
  • 当たらない
  • × 広すぎる例外
  • SQL のルールを全体でオフ
  • 0
  • 例外を広げるほど、そこが攻撃者の抜け道になる。

導入:しきい値を高くして始め、少しずつ下げる

WAF を新しく入れるとき、いきなり既定のしきい値で止め始めると、誤検知で正しい利用者まで止めてしまうおそれがあります。よく使われる進め方は2つあります。

1つは、止めずに記録だけする検知モードでしばらく動かし、ログを見て誤検知に例外を作ってから止め始める方法です。もう1つは CRS の資料が紹介している方法で、最初から止める設定にしつつ、しきい値を10000のような非常に高い値にしておき、誤検知を取り除きながら段階的に下げていきます。

後者のよいところは、止める仕組みそのものが最初から本番で動いていることです。しきい値を下げる各段で、ログに出た誤検知を例外で消し、静かになったら次の段へ進みます。最後に既定の5点まで下げれば完了です。図の途中の値は一例です。

  • WAF NOTES
  • 14 / 29
  • 導入:しきい値を高くして始め、下げていく
  • しきい値の下げ方(例)
  • 合計点
  • 10000
  • 10000
  • 100
  • 20
  • 10
  • 5(既定)
  • 各段で、ログに出た誤検知を例外で取り除いてから、次へ下げる
  • 記録だけ
  • 止める仕組みは最初から動かし、しきい値だけを下げていく。

パラノイアレベル:どこまで疑うか

CRS には、ルールの厳しさを4段階で切り替えるパラノイアレベル(PL)があります。レベルを上げるほど、より細かく疑い深いルールが有効になります。

レベル中身
PL1(既定)主なルールが有効。誤検知はまれで、どのサイトにも勧められる
PL2正規表現による SQL インジェクションや XSS の検出などが加わる。誤検知への対応が必要になる
PL3あまり見られない攻撃向けのルールや語のリストが加わる
PL4特殊な文字をさらに強く制限する

レベルを上げると攻撃は逃しにくくなりますが、誤検知が増え、それを取り除く手間も増えます。CRS は、レベルを下げれば誤検知は必ず減る作りになっていると説明しています。図の棒の長さは傾向を示すためのもので、実測値ではありません。

  • WAF NOTES
  • 15 / 29
  • パラノイアレベル:どこまで疑うか
  • PL1
  • PL2
  • PL3
  • PL4
  • 既定・全サイト向け
  • SQLi・XSS を広く
  • 珍しい攻撃まで
  • 特殊文字も強く制限
  • 有効なルールの数
  • すり抜ける攻撃
  • 誤検知(止まる正しい利用)
  • PL1:誤検知はまれ。どのサイトもまずここから
  • 疑うほど攻撃を逃しにくいが、正しい利用も疑ってしまう。

パラノイアレベルを上げるときは、検知だけ先に

つまみを PL3 まで回すと、有効なルールが増え、すり抜ける攻撃は減ります。その代わり、誤検知がぐっと増えます。CRS も、十分な調整なしに高いレベルで止め始めるのは非常に危険だとしています。

そこで CRS には、止めるレベルと検知するレベルを分けて設定する仕組みがあります。たとえば止めるのは PL1 のまま、検知だけ PL2 まで広げておくと、PL2 のルールの当たり方をログで観察できます。誤検知に例外を作り終えてから、止めるレベルを PL2 に上げます。

どのレベルが正しいかは、サイトの重要さと、誤検知に向き合える人手で決まります。ログを見る人がいないまま高いレベルにすると、守りではなく障害の原因になります。

  • WAF NOTES
  • 16 / 29
  • 上げるときは、検知だけ先に
  • PL1
  • PL2
  • PL3
  • PL4
  • 既定・全サイト向け
  • SQLi・XSS を広く
  • 珍しい攻撃まで
  • 特殊文字も強く制限
  • 有効なルールの数
  • すり抜ける攻撃
  • 誤検知(止まる正しい利用)
  • PL3:攻撃は逃しにくいが、誤検知を取り除く手間が大きい
  • 上げるなら、まず検知だけ上げて誤検知を片づけてから。

悪い型を止めるか、正しい形だけ通すか

ここまで見てきたルールは、「SQL インジェクションはこういう型」という悪い型の一覧でした。これを否定モデル(negative security model)と呼びます。どのサイトにもそのまま使える反面、一覧に載っていない新しい攻撃は通してしまいます。

反対の考え方が肯定モデル(positive security model)です。「この API には、この形の JSON しか来ない」という正しい形の定義を持ち、それに合わないものをすべて止めます。API なら OpenAPI の仕様書を使って、パス・メソッド・引数の型や長さを検査する方法がよく使われます。

図の四角はふつうのリクエスト、丸は一覧に載っている既知の攻撃、ひし形はまだ誰も知らない新しい攻撃です。次のページで、それぞれを門に通してみます。

  • WAF NOTES
  • 17 / 29
  • 悪い型を止めるか、正しい形だけ通すか
  • 否定モデル:知っている悪い型を止める
  • ふつう
  • 既知の攻撃
  • 未知の攻撃
  • 攻撃の型の一覧
  • 肯定モデル:決めた正しい形だけを通す
  • ふつう
  • 既知の攻撃
  • 未知の攻撃
  • API の仕様の形
  • 否定モデルは「悪い型の一覧」、肯定モデルは「正しい形の定義」。

未知の攻撃には、肯定モデルが強い

動かしてみると違いが分かります。否定モデルは既知の攻撃を止めましたが、一覧にない新しい攻撃は通しました。肯定モデルは、形が仕様に合わないので、新しい攻撃も止めています。

その代わり肯定モデルは、アプリが変わるたびに定義を直さないと、新しく作った正しい機能まで止めてしまいます。そのため実際の WAF は両方を組み合わせることが多く、汎用のルール(否定モデル)を土台に、API の仕様による検査(肯定モデル)を重ねます。

最近は、ルールに一致するかどうかではなく、機械学習で攻撃らしさを点数にする検出を併用する WAF も増えています。たとえば Cloudflare は、既存のルールをすり抜ける変種を捕まえる目的で、1〜99の攻撃スコアをルールと並べて使えるようにしています。

  • WAF NOTES
  • 18 / 29
  • 未知の攻撃には、肯定モデルが強い
  • 否定モデル:知っている悪い型を止める
  • 止めた
  • 通す
  • すり抜け
  • 攻撃の型の一覧
  • 肯定モデル:決めた正しい形だけを通す
  • 止めた
  • 止めた
  • 通す
  • API の仕様の形
  • 未知の攻撃には肯定モデル。ただし定義を直し続ける手間が要る。

WAF が読める本文の長さには、上限がある

WAF は本文を調べるために、届いた本文をいったんメモリに溜めてから読みます。すべてのリクエストの本文を無制限に溜めることはできないので、多くの WAF には読む長さの上限があります。

たとえば AWS WAF のドキュメントでは、Application Load Balancer に付けた場合は本文の先頭 8KB まで(変更不可)、CloudFront や API Gateway では既定で 16KB までで、追加料金で引き上げられるとされています。Cloudflare も、2025年12月に 1MB へ広げるまでは、本文を溜めて読む範囲が 128KB でした。

上限の内側に攻撃の文字列があれば、これまで見てきた流れで見つけて止められます。問題は、上限の外側にあるときです。

  • WAF NOTES
  • 19 / 29
  • WAF が読める本文の長さには、上限がある
  • リクエストの本文
  • 無害な水増しデータ(何 KB も)
  • 攻撃の文字列
  • WAF が読む範囲(例:先頭 8KB)
  • アプリは本文を最後まで読む →
  • → 読む範囲の中にあるので、見つけて止められる
  • 本文はいったんメモリに溜めてから読むので、どこかで長さを区切る
  • 例:AWS WAF は ALB で先頭 8KB(固定)、CloudFront は既定 16KB(引き上げ可)
  • 本文はメモリに溜めて読むので、読む長さに上限がある。

水増しで、攻撃を読む範囲の外へ押し出す

攻撃者が、攻撃の文字列の前に無害なデータを大量に詰めると、攻撃の部分が WAF の読む範囲の外へ押し出されます。WAF は読めた範囲に何もないので通し、アプリは本文を最後まで読んで攻撃を受け取ってしまいます。

対策は、上限を超えた本文の扱いを決めておくことです。AWS WAF には、上限を超えた本文を「読めた分だけ検査する(既定)」「ルールに当たったものとみなす」「当たらなかったものとみなす」から選ぶ設定があり、ルールと組み合わせて上限を超えた本文を止められます。

ファイルのアップロードなど大きな本文が正当な場所だけを例外にし、それ以外では長すぎる本文を止めるのが基本です。既定のままだと「読めた分だけ検査」になる製品があるので、使っている WAF の設定を確かめておきましょう。

  • WAF NOTES
  • 20 / 29
  • 水増しで、攻撃を読む範囲の外へ押し出す
  • リクエストの本文
  • 無害な水増しデータ(何 KB も)
  • 攻撃の文字列
  • WAF が読む範囲(例:先頭 8KB)
  • アプリは本文を最後まで読む →
  • → 攻撃は読む範囲の外。WAF は何も見つけずに通してしまう
  • 対策:上限を超えた本文の扱いを決める。大きな本文が要る場所だけを例外に
  • AWS WAF では「読めた分だけ検査(既定)」「当たり扱い」「外れ扱い」から選べる
  • 上限を超えた本文をどう扱うかを、先に決めておく。

読み方のずれ:WAF とアプリが別のものを見る

①の分解で見たように、WAF は本文の形式を解釈してから中身を調べます。もし WAF とアプリで解釈が食い違えば、WAF が調べた中身と、アプリが受け取る中身が別のものになります。

2025年の ACSAC で発表された研究「WAFFLED」は、この食い違いを系統的に探しました。攻撃の文字列そのものではなく、ヘッダーや本文の区切りといった無害に見える部分を少しずつ崩す方法で、application/json・multipart/form-data・application/xml を対象に、AWS・Azure・Cloud Armor・Cloudflare・ModSecurity の5つの WAF で計1207件のすり抜けを確認しています。また、調べたサイトの9割以上が、フォーム形式と multipart 形式を区別せずに受け付けていたと報告しています。論文(arXiv)

対策の方向は、あいまいなリクエストは通さないことです。WAF は形式が崩れたものを止め、アプリは想定した形式以外を受け付けないようにします。図は仕組みを単純にしたものです。

  • WAF NOTES
  • 21 / 29
  • 読み方のずれ:WAF とアプリが別のものを見る
  • 同じ 1 本のリクエスト
  • Content-Type: multipart/form-data
  • (区切りの書き方を少し崩してある)
  • WAF の読み方
  • アプリの読み方
  • 区切りを見つけられない
  • 区切りを寛容に読む
  • → 調べる値が取り出せない
  • → q=' OR 1=1 -- を受け取る
  • 判定:問題なし(通す)
  • 結果:攻撃が届く
  • 図は単純化した例。実際の手口は、ヘッダーや区切りの細部での解釈の違いを突く
  • 解釈が食い違えば、WAF が見た中身は意味を失う。

回り道:WAF を通らずにアプリへ行く

CDN やクラウドの WAF は、利用者とアプリの間に立つ通り道に置かれます。逆に言えば、WAF が見ているのは、そこを通ってきたリクエストだけです。

もしアプリのサーバー(オリジン)の IP アドレスが知られていれば、攻撃者は WAF を通らず、オリジンへ直接リクエストを送れます。そうなると、どれほどルールを整えても意味がありません。

オリジンの IP は、思わぬところから漏れます。WAF を入れる前の古い DNS の記録、同じサーバーで動いているメール用のサブドメイン、IP アドレスを表示するエラーページ、サーバーから外部へのアクセスなどです。

  • WAF NOTES
  • 22 / 29
  • 回り道:WAF を通らずにアプリへ行く
  • 注意
  • 人
  • WAF
  • 正面から送ると、WAF に止められる
  • サーバー
  • NG
  • 攻撃者
  • 中身を読む
  • オリジン
  • オリジンの IP に直接送る
  • IP が漏れる例:WAF 導入前の古い DNS 記録、メール用のサブドメイン、エラーページ
  • WAF が見ているのは、WAF を通ってきたリクエストだけ。

オリジンは、WAF を通ったものだけを受け付ける

対策は、オリジン側で WAF からの接続以外を断ることです。よく使われるのは2つの組み合わせです。

1つは、WAF の事業者が公開している IP アドレスの範囲からの接続だけを、オリジンのファイアウォールで許可する方法です。もう1つは、WAF からオリジンへの接続でクライアント証明書を提示させ、証明書のない接続を断る方法(mTLS)です。Cloudflare では Authenticated Origin Pulls という名前で提供されています。IP の制限だけでは、同じ事業者を使う別の利用者からの接続も通ってしまうので、自分専用の証明書と組み合わせるとより確実です。

最後に、攻撃者と同じようにオリジンの IP へ直接接続して、本当に断られるかを確かめます。

  • WAF NOTES
  • 23 / 29
  • オリジンは、WAF を通ったものだけ受け付ける
  • 人
  • WAF
  • 証明書を提示
  • 注意
  • 正面から送ると、WAF に止められる
  • NG
  • 鍵
  • サーバー
  • 攻撃者
  • 中身を読む
  • NG
  • オリジン
  • WAF 以外は断る
  • 直接送っても、断られる
  • WAF からだけ受け付ける:事業者の IP 範囲で絞る+クライアント証明書(mTLS)
  • オリジンは、WAF を通ったものだけを受け付ける。

WAF には見えない攻撃:形が正しい不正

WAF が見ているのは、リクエストの形です。形としてはまったくふつうなのに、意味として不正なリクエストは止められません。

代表例が、URL の番号を書き換えて他人のデータを見る攻撃です。注文 1001 の持ち主が /orders/1002 を開いても、リクエストに怪しい文字は1つもありません。これを止められるのは、「この人は注文 1002 を見てよいか」を知っているアプリだけです。同じように、クーポンの使い回しや、決済の手順を飛ばすといった業務の流れの悪用も、WAF の守備範囲の外です。

OWASP Top 10 で長く上位にある「アクセス制御の不備」は、まさにこの種類の問題です。WAF は、アプリの認可やロジックの代わりにはなりません。

  • WAF NOTES
  • 24 / 29
  • WAF には見えない攻撃:形が正しい不正
  • 人
  • WAF
  • OK
  • GET /orders/1002
  • サーバー
  • 注文 1001 の持ち主
  • 形はふつう → 通す
  • アプリ
  • 認可を確かめないと、他人の注文を返してしまう
  • 他人の ID に書き換える
  • クーポンを何度も使う
  • 手順を飛ばす
  • /orders/1002 を開く
  • 同じ値を、正しい形で送り直す
  • 決済の前に完了の URL を開く
  • → 見てよいかはアプリが判断
  • → 回数の管理はアプリの仕事
  • → 手順の確認はアプリの仕事
  • 「誰が何をしてよいか」は、アプリにしか分からない。

仮想パッチ:直すまでの時間を稼ぐ

ソフトウェアの脆弱性が公表されてから、すべてのアプリが更新を終えるまでには時間がかかります。その間、攻撃の型が分かっていれば、WAF にルールを足して攻撃を手前で止めることができます。これを仮想パッチと呼びます。

2025年12月3日に公表された React Server Components の脆弱性(CVE-2025-55182、通称 React2Shell)は、認証なしでサーバー上のコードを実行できる深刻なものでした。Cloudflare や AWS などは公表前後に対応するルールを配り、アプリの更新までのつなぎとして使われました。

ただし、仮想パッチは穴を塞いだわけではありません。React2Shell でも、配られたルールをすり抜ける攻撃の書き方が次々に報告され、単純な文字の一致だけでは足りないことが分かりました。ルールで時間を稼ぎつつ、アプリの更新を急ぐのが正しい使い方です。図の横軸の長さは目安です。

  • WAF NOTES
  • 25 / 29
  • 仮想パッチ:直すまでの時間を稼ぐ
  • 電気
  • 電気
  • すり抜ける書き方が見つかり、ルールを足す
  • WAF のルールで止める(仮想パッチ)
  • 12月3日 公表
  • 12月5日
  • アプリを更新
  • WAF がなければ、更新までずっと無防備
  • いま
  • 例:React2Shell(CVE-2025-55182)― 認証なしでサーバーのコードを動かせる脆弱性
  • 仮想パッチは穴を塞いだわけではない。アプリの更新を急ぐ
  • ルールはすぐ配れるが、穴を塞ぐのはアプリの更新。

WAF も通り道の一部:変更が障害になることもある

WAF はすべてのリクエストが通る場所にあるので、WAF 自体の不具合は、そのままサイト全体の障害になります。

React2Shell への対応中の2025年12月5日、Cloudflare は本文を溜めて読む範囲を 128KB から 1MB(Next.js の既定の上限)に広げる変更を配っていました。その途中で、内部の WAF テスト用の仕組みを全体設定で止めたところ、古い世代のプロキシ(FL1)で Managed Ruleset を使っていた利用者でエラーが起き、約25分間、Cloudflare が扱う HTTP 通信の約28%に影響しました。攻撃ではなく、守るための変更が原因でした。Cloudflare の報告

WAF のルールや設定の変更も、アプリの変更と同じく本番への変更です。Cloudflare 自身も、設定も少しずつ配る、すぐ戻せるようにする、設定が壊れていても通信を落とさずに通す方向へ寄せる、といった対策を挙げています。自分で WAF を運用するときも、WAF が応答しないときに通すか止めるかを決めておきます。

  • WAF NOTES
  • 26 / 29
  • WAF も通り道の一部:変更が障害になる
  • 注意
  • 電気
  • 電気
  • すり抜ける書き方が見つかり、ルールを足す
  • WAF のルールで止める(仮想パッチ)
  • 12月3日 公表
  • 12月5日
  • アプリを更新
  • WAF がなければ、更新までずっと無防備
  • ここで障害
  • 12月5日:Cloudflare が本文の検査範囲を 128KB→1MB に広げる対応中、約25分の障害
  • WAF が止まれば全部止まる。少しずつ配る・すぐ戻す・止まったときの扱いを決める
  • WAF の変更も、本番への変更として扱う。

どこに置くか:3つの置き場所

WAF の置き場所は、大きく3つに分かれます。

置き場所例特徴
CDN のエッジCloudflare・Akamai・Fastly など世界中の拠点で止めるので、オリジンに届く前に攻撃を減らせる。オリジンの直撃対策が前提
クラウドの入口AWS WAF・Azure WAF・Google Cloud Armor などロードバランサーや CDN に付け、クラウドの構成と一緒に管理できる
Web サーバーの中ModSecurity・Coraza に OWASP CRS を載せる自分の環境で細かく調整できる。更新や調整は自分で担う

CRS は特定の製品ではなく、ModSecurity や Go で書かれた Coraza などの互換エンジンで動くルール集です。商用の WAF の中にも、CRS を元にしたルールセットを提供しているものがあります。ただし CRS は、しきい値を既定より高くした実装はすり抜けを生むと注意しています。どこに置いても、中で起きているのはこれまで見た4段の流れです。

  • WAF NOTES
  • 27 / 29
  • どこに置くか:3つの置き場所
  • クラウド
  • ネットワーク
  • サーバー
  • CDN のエッジ
  • クラウドの入口
  • Web サーバーの中
  • WAF
  • WAF
  • WAF
  • Cloudflare・Akamai
  • AWS WAF・Azure WAF
  • ModSecurity・Coraza
  • Fastly など
  • Google Cloud Armor など
  • + OWASP CRS(ルール集)
  • + オリジンの手前で攻撃を減らす
  • + クラウドの構成と一緒に管理
  • + 自分の環境で細かく調整できる
  • - オリジン直撃の対策が前提
  • - 守れるのはそのクラウドの資源
  • - 更新と調整は自分で担う
  • 置き場所が違っても、中の4段の考え方は同じ。

最近の動き(2024〜2026年)

オープンソースの WAF では、体制が大きく変わりました。ModSecurity は2024年1月に管理が Trustwave から OWASP へ移り、Trustwave のサポートは2024年7月に終わりました。CRS は ModSecurity に加えて、Go で書かれた互換エンジン Coraza でも動きます。CRS 4.25 は CRS 4 で初めての長期サポート版になり、CRS 3.3 系のサポートは2026年第3四半期で終わります。次の大きな版では、ルールの書き方を YAML ベースの CRSLang に切り替える計画も発表されています。OWASP CRS

ルール自体も、すり抜けとの追いかけっこです。2026年10月2日には CRS 4.30.0 と 4.25.2 LTS が出て、すり抜けにつながる3件の問題が修正されました。WAF は入れて終わりではなく、更新し続けるソフトウェアです。

規格の面では、カード決済を扱う事業者向けの PCI DSS 4.0 で、要件 6.4.2 が2025年3月31日から必須になりました。公開している Web アプリの前に、攻撃を継続的に検知・防止する自動の仕組みを置くことを求めるもので、WAF はその例として挙げられています。

  • WAF NOTES
  • 28 / 29
  • 最近の動き(2024〜2026年)
  • 2024年1月
  • ModSecurity の管理が Trustwave から OWASP へ
  • 2024年7月
  • Trustwave による ModSecurity のサポートが終了
  • 2025年3月
  • PCI DSS 4.0 の要件 6.4.2 が必須に(公開 Web アプリの自動防御)
  • 2025年
  • 研究「WAFFLED」:読み方のずれで 1207 件のすり抜け
  • 2025年12月
  • React2Shell で仮想パッチと、そのすり抜けが相次ぐ
  • 2026年
  • CRS 4.25 が初の長期サポート版に(3.3 系は第3四半期で終了)
  • 2026年10月
  • CRS 4.30.0/4.25.2 LTS がすり抜け 3 件を修正
  • WAF のルールも、更新し続けるソフトウェア。

まとめ:WAF は、時間を稼ぐ門番

WAF は、ファイアウォールが開けない封筒を開けて、HTTP の中身を読む門番です。中では、分解 → 正規化 → 照合と採点 → 判定の4段で、リクエストを攻撃らしさの点数に変えて判断しています。

うまく使うには、誤検知を狭い例外で取り除きながらしきい値やパラノイアレベルを調整すること、読める長さの上限と超えた分の扱いを決めること、オリジンへの直撃を塞ぐことが欠かせません。

そして WAF は、アプリの穴そのものを無くすものではありません。形が正しい不正は見えず、ルールをすり抜ける書き方も見つかり続けます。WAF で時間を稼ぎ、その間にアプリを直す。両方を組み合わせて、はじめて守りになります。

  • WAF NOTES
  • 29 / 29
  • まとめ:WAF は、時間を稼ぐ門番
  • ① 分解
  • ② 正規化
  • ③ 照合と採点
  • ④ 判定
  • できること
  • 苦手なこと
  • ・既知の型の攻撃を手前で止める
  • ・形が正しい不正(認可・業務の流れ)
  • ・公表から修正までの時間を稼ぐ
  • ・WAF を通らない道(オリジン直撃)
  • ・攻撃の記録を残す
  • ・読める範囲の外と、読み方のずれ
  • 調整:狭い例外・しきい値・PL・上限の扱い
  • → アプリの修正と組み合わせる
  • WAF とアプリの修正は、どちらか一方ではなく両方で。

0 件のコメント

読み込み中…

権利侵害・不適切な内容の連絡: [email protected]