ModSecurity(mod_security) の Core Rule Set(CRS)を読み解く【その2:検知ルール】

ModSecurity Core Rule Set(CRS)を読み解く【その1:global_config, config】に続いて、Core Rule Set(CRS)の検知ルールを見てみた。

  • 確認したバージョン
    • modsecurity-crs v2.0.2(2009年10月2日現在)

CRS の base_rules 以下の .conf ファイルが ModSecurity が HTTP/HTTPS 通信を検査するための検知ルールである。この日記では、modsecurity_crs_41_sql_injection_attacks.conf を例として、どのようにルールが定義されているか確認した。

./base_rules:
modsecurity_40_generic_attacks.data
modsecurity_41_sql_injection_attacks.data
modsecurity_46_et_sql_injection.data
modsecurity_46_et_web_rules.data
modsecurity_50_outbound.data
modsecurity_crs_20_protocol_violations.conf
modsecurity_crs_21_protocol_anomalies.conf
modsecurity_crs_23_request_limits.conf
modsecurity_crs_30_http_policy.conf
modsecurity_crs_35_bad_robots.conf
modsecurity_crs_40_generic_attacks.conf
modsecurity_crs_41_phpids_filters.conf
modsecurity_crs_41_sql_injection_attacks.conf
modsecurity_crs_41_xss_attacks.conf
modsecurity_crs_45_trojans.conf
modsecurity_crs_46_et_sql_injection.conf
modsecurity_crs_46_et_web_rules.conf
modsecurity_crs_47_common_exceptions.conf
modsecurity_crs_48_local_exceptions.conf
modsecurity_crs_49_enforcement.conf
modsecurity_crs_50_outbound.conf
modsecurity_crs_60_correlation.conf

modsecurity_crs_41_sql_injection_attacks.conf

SQLインジェクションの検知ルールを見てみると、大体以下の流れで SQL インジェクション攻撃の有無を検査しているようだ。

  1. ルール(1)で HTTP リクエストにおいて、SQL インジェクション攻撃に含まれるキーワードが含まれるか検査する。含まれている場合、pm_sqli_score 変数に 1 を加算する
  2. ルール(2)で、pm_sqli_score 変数の値が 0 であるか検査する。0 であった場合(特定のキーワードが HTTP リクエストに含まれない)、以降の RegEx Rules で HTTP リクエストを検査する。含まれていない場合、RegEx Rules で HTTP リクエストを検査せずにマーカ:END_SQL_INJECTION_PM まで移動する。
  3. ルール(3)、(4)でHTTPリクエスト・ヘッダを検査する
  4. ルール(5)で、ここまで検査したルールが 1 つでも合致したか検査する。合致していた場合、ルール(6)〜(9)で合致した HTTP 通信を検査する。合致していない場合、ルール(6)〜(9)で HTTP 通信を検査せずにマーカ:END_SQL_INJECTION_WEAK まで移動する

まず特定のキーワードが含まれているか検査することで、正規表現を使用する RegEx Rules で検査する HTTP 通信を最小限に抑えるように工夫しているようだ。

#
# Prequalify Request Matches
#
### ルール(1)
SecRule REQUEST_URI|REQUEST_BODY|REQUEST_HEADERS|XML:/*|!REQUEST_HEADERS:Referer \
"@pmFromFile modsecurity_41_sql_injection_attacks.data" \
"phase:2,t:none,t:urlDecodeUni,t:htmlEntityDecode,t:lowercase,t:replaceComments,
 t:compressWhiteSpace,nolog,pass,setvar:tx.pm_sqli_score=+1,
 setvar:tx.pm_sqli_data_%{matched_var_name}=%{matched_var}"

### ルール(2)
SecRule TX:PM_SQLI_SCORE "@eq 0" "phase:2,t:none,pass,skipAfter:END_SQL_INJECTION_PM,nolog"

#
# Begin RegEx Checks for target locations that matched the prequalifier checks
#

### RegEx Rules Start -----------------------------------
#
# Blind SQL injection
#
### RegEx ルール(A)
SecRule TX:/^PM_SQLI_DATA_*/ "\bsys\.user_catalog\b" \
"phase:2,capture,t:none,ctl:auditLogParts=+E,block,nolog,auditlog,
 msg:'Blind SQL Injection Attack',id:'959517',tag:'WEB_ATTACK/SQL_INJECTION',
 logdata:'%{TX.0}',severity:'2',setvar:'tx.msg=%{rule.msg}',
 setvar:tx.sqli_score=+1,setvar:tx.anomaly_score=+20,
 setvar:tx.%{rule.id}-WEB_ATTACK/SQL_INJECTION-%{matched_var_name}=%{matched_var}"

...

#
# SQL injection
#
SecRule TX:/^PM_SQLI_DATA_*/ "\'msdasql\'" \
"phase:2,capture,t:none,ctl:auditLogParts=+E,block,nolog,auditlog,
 msg:'SQL Injection Attack',id:'959020',tag:'WEB_ATTACK/SQL_INJECTION',
 logdata:'%{TX.0}',severity:'2',setvar:'tx.msg=%{rule.msg}',
 setvar:tx.sqli_score=+1,setvar:tx.anomaly_score=+20,
 setvar:tx.%{rule.id}-WEB_ATTACK/SQL_INJECTION-%{matched_var_name}=%{matched_var}"

...

### RegEx Rules End -------------------------------------

### マーカ: END_SQL_INJECTION_PM
SecMarker END_SQL_INJECTION_PM

### ルール(3)
SecRule REQUEST_HEADERS|XML:/*|!REQUEST_HEADERS:Referer \
"\b(\d+) ?= ?\1\b|[\'\"](\w+)[\'\"] ?= ?[\'\"]\2\b" \
"phase:2,capture,t:none,t:urlDecodeUni,t:htmlEntityDecode,t:replaceComments,
 t:compressWhiteSpace,t:lowercase,ctl:auditLogParts=+E,block,nolog,auditlog,
 msg:'SQL Injection Attack',id:'950001',tag:'WEB_ATTACK/SQL_INJECTION',
 logdata:'%{TX.0}',severity:'2',setvar:'tx.msg=%{rule.msg}',
 setvar:tx.sqli_score=+1,setvar:tx.anomaly_score=+20,
 setvar:tx.%{rule.id}-WEB_ATTACK/SQL_INJECTION-%{matched_var_name}=%{matched_var}"

### ルール(4)
SecRule REQUEST_HEADERS|XML:/*|!REQUEST_HEADERS:Referer|!REQUEST_HEADERS:via \
"\b(?:coalesce\b|root\@)" \
"phase:2,capture,t:none,t:urlDecodeUni,t:htmlEntityDecode,t:replaceComments,
 t:compressWhiteSpace,t:lowercase,ctl:auditLogParts=+E,block,nolog,auditlog,
 id:'950908',tag:'WEB_ATTACK/SQL_INJECTION',setvar:'tx.msg=%{rule.msg}',
 setvar:tx.sqli_score=+1,setvar:tx.anomaly_score=+20,
 setvar:tx.%{rule.id}-WEB_ATTACK/SQL_INJECTION-%{matched_var_name}=%{matched_var}"

### マーカ: BEGIN_SQL_INJECTION_WEAK
SecMarker BEGIN_SQL_INJECTION_WEAK

### ルール(5)
SecRule &TX:/SQL_INJECTION/ "@eq 0" "phase:2,t:none,nolog,pass,skipAfter:END_SQL_INJECTION_WEAK"

### ルール(6)
SecRule TX:/SQL_INJECTION/ "\b(?:rel(?:(?:nam|typ)e|kind)|a(?:ttn(?:ame|um)|scii)|
c(?:o(?:nver|un)t|ha?r)|s(?:hutdown|elect)|to_(?:numbe|cha)r|u(?:pdate|nion)|
d(?:elete|rop)|group\b\W*\bby|having|insert|length|where)\b" \
"phase:2,chain,capture,t:none,ctl:auditLogParts=+E,block,nolog,auditlog,
 msg:'SQL Injection Attack',id:'950001',tag:'WEB_ATTACK/SQL_INJECTION',
 logdata:'%{TX.0}',severity:'2'"

### ルール(7)
SecRule MATCHED_VAR "(?:[\\\(\)\%#]|--)" \
"t:none,setvar:'tx.msg=%{rule.msg}',setvar:tx.sqli_score=+1,setvar:tx.anomaly_score=+20,
 setvar:tx.%{rule.id}-WEB_ATTACK/SQL_INJECTION-%{matched_var_name}=%{matched_var}"

### ルール(8)
SecRule TX:/SQL_INJECTION/ "\b(?:benchmark|encode)\b" \
"phase:2,chain,capture,t:none,ctl:auditLogParts=+E,block,nolog,auditlog,
 msg:'Blind SQL Injection Attack',id:'950007',tag:'WEB_ATTACK/SQL_INJECTION',
 logdata:'%{TX.0}',severity:'2'"

### ルール(9)
SecRule MATCHED_VAR "(?:[\\\(\)\%#]|--)" \
"t:none,setvar:'tx.msg=%{rule.msg}',setvar:tx.sqli_score=+1,setvar:tx.anomaly_score=+20,
 setvar:tx.%{rule.id}-WEB_ATTACK/SQL_INJECTION-%{matched_var_name}=%{matched_var}"

### マーカ: END_SQL_INJECTION_WEAK
SecMarker END_SQL_INJECTION_WEAK

ModSecurity Reference Manual*1 によると、SecRule は以下の通り 3 つの引数で構成される。

引数 必須/任意 意味
VARIABLES 必須 検査対象とする HTTP 通信の特定データを格納した変数を指定する
OPERATOR 必須 検査項目を指定する
ACTIONS 任意 OPERATOR の条件が真となった場合に実行する処理を指定する

この日記では、ルール(1)、(3)を例に検知ルールをみていく。

ルール(1)

ルール(1): VARIABLES

ルール(1)は、HTTP リクエストにおける次の項目を検査対象とする。各変数を OR(「|」)で連結しているため、検査対象としたどれかで OPERATOR の条件に真となれば、ACTION が実行される。

変数 意味
REQUEST_URI URI。クエリストリングを含む
REQUEST_BODY HTTP リクエスト・ボディ。POST メソッドにおけるパラメータが該当する
REQUEST_HEADERS HTTP リクエスト・ヘッダ
XML:/* XML のすべてのエンティティ
!REQUEST_HEADERS:Referer Referer ヘッダを除外する
ルール(1): OPERATORS

ルール(1)では、modsecurity_41_sql_injection_attacks.data に保存されているキーワードが検査対象である VARIABLES に合致するか検査する。ルール(1)は pmFromFiles*2 OPERATOR キーワード(?)を指定することで、引数で指定したファイルに含まれる文字列が検査対象に合致するか検査する。OPERATOR キーワードを OPERATORS で指定する場合、キーワードの語頭に「@」を付与する*3

modsecurity_41_sql_injection_attacks.data に保存されているキーワードは、RDBMS におけるシステムテーブル・アカウント、SQL 文の予約語等である。SQL インジェクション攻撃の HTTP リクエストに含まれる可能性の高いキーワードを選別しているようだ。

sys.user_objects
sys.user_triggers
@@spid
msysaces
instr
...
sp_makewebtask
utl_http
dbms_java
benchmark
xp_regread
xp_regwrite
ルール(1): ACTIONS

ModSecurity のルールの Action は、実行処理に関する設定だけではなく、ルールそのものに関する設定も定義される。ルール(1)の Action をまとめてみると、「OPERATOR が真になった回数、真になった時の変数名および値を記録する」という感じだろうか。ルール(1)の Action の詳細を以下の通りである。

Action 意味
phase:2 ModSecurity における処理フェーズ 2 でこのルールを適用する。フェーズ 2 は HTTP リクエスト・ボディを処理するフェーズ
t:none 変換関数をすべて無効化する。ルール(1)では他の変換関数より先に定義しているため、変換関数の初期化と考えられる
t:urlDecodeUni 変換関数:URI を URL デコードする。%xx だけでなく、%uXXXX もデコードする
t:htmlEntityDecode 変換関数:エスケープしたHTML 特殊文字(「<」、「>」等)を元に戻す
t:lowercase 変換関数:英大文字を英小文字に変換する
t:replaceComments 変換関数:/* ... */ を 1 スペースに変換する
t:compressWhiteSpace 変換関数:改行コード等や連続したスペースを 1 スペースに変換する
nolog このルールに関してログに記録しない
pass OPERATOR が真となっても、以降のルールで検査を継続する
setvar:tx.pm_sqli_score=+1 OPERATOR が真となる度に tx.pm_sqli_score を 1 増加する
setvar:tx.pm_sqli_data_%{matched_var_name}=%{matched_var} OPERATOR が真となったら、真となった時の VARIABLE の値をtx.pm_sqli_data_ に格納する

ルール(3)

ルール(3): VARIABLES

ルール(3)は、Referer ヘッダを除くHTTP リクエスト・ヘッダを検査対象とする。ルール(3)で定義された VARIABLES については、ルール(1): VARIABLES でまとめているため、割愛する。

ルール(3): OPERATOR

正規表現にて、次のどちらかの文字列を含むか否かを検査する。「=」前後に空白(スペース)はあってもなくてもよい。SQL インジェクションの脆弱性があるか否かを調べる際に、WHERE を必ず真とするために使用される文字列だ。SQL インジェクションの検査通信を検出するルールといったところだろう。

  • <数字> = <数字>
    • 例:1 = 1、1=1、234=234
  • "文字列" = "文字列"、または '文字列' = '文字列'
    • 例:"a"="a"、'a' = 'a'
ルール(3): ACTIONS

OPERATOR が真となった場合、SQL Injection の攻撃があったと判断し、audit log に HTTP レスポンス・ボディ等を記録し、該当 HTTP トランザクションを遮断する。

Action 意味
capture OPERATOR の正規表現で合致したパターンの後方参照を有効にする。TX.0-TX.9 まで
ctl:auditLogParts=+E 一時的にHTTPレスポンス・ボディを audit log に記録するように設定を変更する
block 合致した HTTP トランザクションを遮断する
auditlog audit log を記録する
msg:'SQL Injection Attack' ルール固有メッセージに 'SQL Injection Attack' を設定する
id:'950001' ルール番号として、950001 を割り当てる。Core Rule Set には、900,000-999,999 が予約されているようだ*4
tag:'WEB_ATTACK/SQL_INJECTION' ルールに 'WEB_ATTACK/SQL_INJECTION' タグを設定する
logdata:'%{TX.0}' ログに指定したデータを記録する。ここでは OPERATOR の正規表現全体に合致した文字列(TX.0)を記録する
severity:'2' Severity に CRITICAL を設定する
setvar:'tx.msg=%{rule.msg}' tx.msg に msg に設定した 'SQL Injection Attack' を格納する
setvar:tx.sqli_score=+1 tx.sqli_score に 1 を加算する
setvar:tx.anomaly_score=+20 tx.anomaly_score に 20 を加算する。modsecurity_crs_60_correlation.conf でアノーマリ判定に利用するようだ
setvar:tx.%{rule.id}-WEB_ATTACK/SQL_INJECTION-%{matched_var_name}=%{matched_var} tx.950001-WEB_ATTACK/SQL_INJECTION-<変数> に <変数値> を格納する

検知ルールを読んでみて

ModSecurity の検知ルールをきちんと読んだのは初めてだが、パフォーマンスを向上する工夫があって興味深かった。ModSecurity で最小限のルールを利用するだけなら、パフォーマンス低下を最小限を抑えられるのではないだろうか。インターネット上で ModSecurity について調べていると、トラックバック・スパムを防ぐ用途でも利用されているようだ(今もそうなのか?)。*5

CRS の検知ルールを読んでみると、オープンソースの IDS である Snort を思い出した。検知ルールの定義方法がよく似ている。だから Snort を触ったことがある人なら、キーワードだけ理解すればすぐに検知ルールが書けそうだ。

【収集用メールアドレス】:q1w2e3w2@gmail.com