WAFルールと例外設定
ルール一致から最終actionまでの順序と、例外・閾値を変えた影響範囲を説明します。
説明
例外を追加する場合、どのルールのどの対象を、どの要求だけ除外するかを狭く定めます。例外の順序が遅ければ、先に遮断されて適用されないことがあります。
同じphase 2の中で例外を前後へ置き、qだけを除いた設定と別引数への一致を比較します。後半では二つの検知点を加算し、閾値5と6で最終動作が変わる様子を追います。
ルール一致から最終actionまでの順序と、例外・閾値を変えた影響範囲を説明します。
1限定例外をルールより前へ置く
/echoの要求に限り、id1001の対象からARGS:qを外します。
from sp01 import *
reset()
print(rules("exclude")条件付きの対象除外と実行順を読む)ruleRemoveTargetByIdはルール全体を無効にせず、その要求のqだけを外します。ほかの引数や別パスは引き続き検査します。
2例外ルールを前後に配置して同じ要求を比較する
lateは同じ例外を遮断ルールの後へ置いています。
print(request("exclude"除外を先に適用する設定を試す,"/echo?q=demo-block")["status"])
print(request("late"遮断後に例外を置いた設定を試す,"/echo?q=demo-block")["status"])
print(summary())前に置いた例外は200、後ろは403です。後ろの例外へ到達する前に、id1001のdenyで処理が中断されました。
3例外に含めない引数とパスを試す
q以外のnoteと、/echo以外の/searchを比較します。
print(request("exclude","/echo?note=demo-block除外対象外の引数を試す")["status"])
print(request("exclude","/search?q=demo-block"除外条件外のパスを試す)["status"])両方403です。必要な範囲だけ除外し、想定外の入口で検査が消えていないことを確認します。なお試料のパス条件はbeginsWithなので、実運用では/echoの接頭辞全体を許す意図も確認します。
4加点と最終遮断を別ルールで行う
score5ではaの一致が3点、bが2点です。最後のルールが合計5以上で遮断します。
print(rules("score5")初期化・加点・閾値の順を読む)
print(request("score5","/echo?a=demo"一つの加点条件だけを満たす)["status"])
print(summary())aだけは3点なので200です。一致id2001は記録されても、最終遮断のid2003はありません。
5二つの一致が合計閾値へ達する
aとbを両方demoにします。
print(request("score5","/echo?a=demo&b=demo3点と2点の条件を両方満たす")["status"])
print(summary()最終遮断ルールと合計値を照合する)403で、id2001、2002に続いて2003が記録され、score=5です。個々の一致はpassでも、合計を評価する後段で遮断できます。
6閾値だけを6へ上げる
検知条件は同じで最終閾値を変えます。
print(request("score6"同じ入力を閾値6の設定へ送る,"/echo?a=demo&b=demo")["status"])
print(summary())200で加点ルール二つの一致は残り、interrupted=Falseです。閾値変更は検知を消す操作とは異なりますが、遮断しなくなる要求が増えます。
7正常値を含めた組合せで影響を確認する
a/bの片方・両方・どちらもなしを比較します。
for query in二条件の全4組を比較する ("a=ok&b=ok","a=demo&b=ok","a=ok&b=demo","a=demo&b=demo"):
print(query,request("score5","/echo?"+query)["status"],request("score6","/echo?"+query)["status"])違いが出るのは両方demoの要求です。変更した例外や閾値を、正常系と検知対象の回帰ケースで照合します。
まとめ
例外は対象・入口・適用順を明示します。
一致と加点、最終遮断を分けて読みます。
設定変更後は正常系と検知対象の組合せを再試験します。
操作履歴と解説を振り返れます。
Python HTTP client / Coraza 3.7.0 / coraza-caddy 2.5.0 / Caddy 2.11.4
from sp01 import *
reset()
print(rules("exclude"))SecRuleEngine On
SecRequestBodyAccess On
SecAuditEngine On
SecAuditLog /tmp/audit.log
SecAuditLogFormat JSON
SecAuditLogParts ABFHZ
SecRule REQUEST_URI "@beginsWith /echo" "id:1200,phase:2,pass,nolog,ctl:ruleRemoveTargetById=1001;ARGS:q"
SecRule ARGS "@contains demo-block" "id:1001,phase:2,deny,status:403,log,t:none,msg:'fixed marker',logdata:'%{MATCHED_VAR_NAME}=%{MATCHED_VAR}'"
SecRule REQUEST_URI "@beginsWith /vulnerable" "id:1100,phase:2,deny,status:403,log,chain,msg:'quote in search'"
SecRule ARGS:q "@contains '" "t:none"print(request("exclude","/echo?q=demo-block")["status"])
print(request("late","/echo?q=demo-block")["status"])
print(summary())200
403
{'uri': '/echo?q=demo-block', 'engine': 'On', 'interrupted': True, 'matches': [(1001, 'ARGS:q=demo-block')]}print(request("exclude","/echo?note=demo-block")["status"])
print(request("exclude","/search?q=demo-block")["status"])403 403
print(rules("score5"))
print(request("score5","/echo?a=demo")["status"])
print(summary())SecRuleEngine On
SecRequestBodyAccess On
SecAuditEngine On
SecAuditLog /tmp/audit.log
SecAuditLogFormat JSON
SecAuditLogParts ABFHZ
SecAction "id:2000,phase:1,pass,nolog,setvar:tx.score=0"
SecRule ARGS:a "@streq demo" "id:2001,phase:2,pass,log,setvar:tx.score=+3,msg:'marker a'"
SecRule ARGS:b "@streq demo" "id:2002,phase:2,pass,log,setvar:tx.score=+2,msg:'marker b'"
SecRule TX:score "@ge 5" "id:2003,phase:2,deny,status:403,log,msg:'threshold',logdata:'score=%{TX.score}'"
200
{'uri': '/echo?a=demo', 'engine': 'On', 'interrupted': False, 'matches': [(2001, '')]}print(request("score5","/echo?a=demo&b=demo")["status"])
print(summary())403
{'uri': '/echo?a=demo&b=demo', 'engine': 'On', 'interrupted': True, 'matches': [(2001, ''), (2002, ''), (2003, 'score=5')]}print(request("score6","/echo?a=demo&b=demo")["status"])
print(summary())200
{'uri': '/echo?a=demo&b=demo', 'engine': 'On', 'interrupted': False, 'matches': [(2001, ''), (2002, '')]}for query in ("a=ok&b=ok","a=demo&b=ok","a=ok&b=demo","a=demo&b=demo"):
print(query,request("score5","/echo?"+query)["status"],request("score6","/echo?"+query)["status"])a=ok&b=ok 200 200 a=demo&b=ok 200 200 a=ok&b=demo 200 200 a=demo&b=demo 403 200
演習完了