Linux
Apache
Security

Apache WAF mod_security の導入

想定環境

CentOS 6 または 7

# rpm -q mod_security mod_security_crs
mod_security-2.7.3-5.el6.x86_64
mod_security_crs-2.2.6-3.el6.noarch

# rpm -q mod_security mod_security_crs
mod_security-2.9.2-1.el7.x86_64
mod_security_crs-2.2.9-1.el7.noarch

はじめに、導入方針など

  • デフォルトでインストールされるルールでは、誤検知が発生するので、最小限のルールのみ有効にします。
  • 現状、本番環境での初期導入時はSQLインジェクションとクロスサイトスクリプティングに関するルールのみ残し、他は無効にします。
  • 最初は、検出オンリーモードで様子をみるのが良さそうです。
  • 開発環境では全てのルールを有効にしてみるなど、いろいろ試してみてください。問題ないと確認できれば、本番環境で随時ルールを有効にしていきます。
  • デフォルトではログ出力が多いので、ディスク容量に余裕のない環境では設定を調整してください。
  • 本番環境での誤検知発生にそなえて、特定のURLでルールを無効にする方法を確認してください。

rpmパッケージの導入

epelリポジトリからrpmパッケージをインストールします。

# yum -y install mod_security mod_security_crs

mod_security が Apache モジュール、mod_security_crs がWAFのルールです。

初回導入時は、必ず設定を調整してから Apache を再起動して反映します。

# service httpd restart

設定変更時は、リロードで反映します。

# service httpd reload

SQLインジェクション・クロスサイトスクリプティングのルールを残し、その他は無効化する

デフォルトで読み込まれるWAFルールは、/etc/httpd/modsecurity.d/activated_rule 下にあります。
あらかじめバックアップを取得しておきましょう。

# cd /etc/httpd/modsecurity.d
# cp -rp activated_rule activated_rule.orig
# cd activated_rule
# ls -l
total 88
lrwxrwxrwx 1 root root 64 Jul  3 16:21 modsecurity_35_bad_robots.data -> /usr/lib/modsecurity.d/base_rules/modsecurity_35_bad_robots.data
lrwxrwxrwx 1 root root 62 Jul  3 16:21 modsecurity_35_scanners.data -> /usr/lib/modsecurity.d/base_rules/modsecurity_35_scanners.data
lrwxrwxrwx 1 root root 69 Jul  3 16:21 modsecurity_40_generic_attacks.data -> /usr/lib/modsecurity.d/base_rules/modsecurity_40_generic_attacks.data
lrwxrwxrwx 1 root root 75 Jul  3 16:21 modsecurity_41_sql_injection_attacks.data -> /usr/lib/modsecurity.d/base_rules/modsecurity_41_sql_injection_attacks.data
lrwxrwxrwx 1 root root 62 Jul  3 16:21 modsecurity_50_outbound.data -> /usr/lib/modsecurity.d/base_rules/modsecurity_50_outbound.data
lrwxrwxrwx 1 root root 70 Jul  3 16:21 modsecurity_50_outbound_malware.data -> /usr/lib/modsecurity.d/base_rules/modsecurity_50_outbound_malware.data
lrwxrwxrwx 1 root root 77 Jul  3 16:21 modsecurity_crs_20_protocol_violations.conf -> /usr/lib/modsecurity.d/base_rules/modsecurity_crs_20_protocol_violations.conf
lrwxrwxrwx 1 root root 76 Jul  3 16:21 modsecurity_crs_21_protocol_anomalies.conf -> /usr/lib/modsecurity.d/base_rules/modsecurity_crs_21_protocol_anomalies.conf
lrwxrwxrwx 1 root root 72 Jul  3 16:21 modsecurity_crs_23_request_limits.conf -> /usr/lib/modsecurity.d/base_rules/modsecurity_crs_23_request_limits.conf
lrwxrwxrwx 1 root root 69 Jul  3 16:21 modsecurity_crs_30_http_policy.conf -> /usr/lib/modsecurity.d/base_rules/modsecurity_crs_30_http_policy.conf
lrwxrwxrwx 1 root root 68 Jul  3 16:21 modsecurity_crs_35_bad_robots.conf -> /usr/lib/modsecurity.d/base_rules/modsecurity_crs_35_bad_robots.conf
lrwxrwxrwx 1 root root 73 Jul  3 16:21 modsecurity_crs_40_generic_attacks.conf -> /usr/lib/modsecurity.d/base_rules/modsecurity_crs_40_generic_attacks.conf
lrwxrwxrwx 1 root root 79 Jul  3 16:21 modsecurity_crs_41_sql_injection_attacks.conf -> /usr/lib/modsecurity.d/base_rules/modsecurity_crs_41_sql_injection_attacks.conf
lrwxrwxrwx 1 root root 69 Jul  3 16:21 modsecurity_crs_41_xss_attacks.conf -> /usr/lib/modsecurity.d/base_rules/modsecurity_crs_41_xss_attacks.conf
lrwxrwxrwx 1 root root 72 Jul  3 16:21 modsecurity_crs_42_tight_security.conf -> /usr/lib/modsecurity.d/base_rules/modsecurity_crs_42_tight_security.conf
lrwxrwxrwx 1 root root 65 Jul  3 16:21 modsecurity_crs_45_trojans.conf -> /usr/lib/modsecurity.d/base_rules/modsecurity_crs_45_trojans.conf
lrwxrwxrwx 1 root root 75 Jul  3 16:21 modsecurity_crs_47_common_exceptions.conf -> /usr/lib/modsecurity.d/base_rules/modsecurity_crs_47_common_exceptions.conf
lrwxrwxrwx 1 root root 82 Jul  3 16:21 modsecurity_crs_48_local_exceptions.conf.example -> /usr/lib/modsecurity.d/base_rules/modsecurity_crs_48_local_exceptions.conf.example
lrwxrwxrwx 1 root root 74 Jul  3 16:21 modsecurity_crs_49_inbound_blocking.conf -> /usr/lib/modsecurity.d/base_rules/modsecurity_crs_49_inbound_blocking.conf
lrwxrwxrwx 1 root root 66 Jul  3 16:21 modsecurity_crs_50_outbound.conf -> /usr/lib/modsecurity.d/base_rules/modsecurity_crs_50_outbound.conf
lrwxrwxrwx 1 root root 75 Jul  3 16:21 modsecurity_crs_59_outbound_blocking.conf -> /usr/lib/modsecurity.d/base_rules/modsecurity_crs_59_outbound_blocking.conf
lrwxrwxrwx 1 root root 69 Jul  3 16:21 modsecurity_crs_60_correlation.conf -> /usr/lib/modsecurity.d/base_rules/modsecurity_crs_60_correlation.conf

拡張子 .data は、WAFルールが参照する定数などが定義されています。.conf がWAFルールの定義です。

SQLインジェクションmodsecurity_crs_41_sql_injection_attacks.conf、クロスサイトスクリプティングmodsecurity_crs_41_xss_attacks.confを除いて、他のconfファイルを無効化します。
単純にシンボリックリンクを削除すると、yumアップデート時に再度有効になってしまうので、同名の空ファイルに置き換えます。

# find . -type l -name '*_crs_*' | grep -Ev 'sql|xss' | xargs -I% echo rm % \; touch % | bash
# ls -l
total 48
lrwxrwxrwx 1 root root  64 Jul  3 19:56 modsecurity_35_bad_robots.data -> /usr/lib/modsecurity.d/base_rules/modsecurity_35_bad_robots.data
lrwxrwxrwx 1 root root  62 Jul  3 19:56 modsecurity_35_scanners.data -> /usr/lib/modsecurity.d/base_rules/modsecurity_35_scanners.data
lrwxrwxrwx 1 root root  69 Jul  3 19:56 modsecurity_40_generic_attacks.data -> /usr/lib/modsecurity.d/base_rules/modsecurity_40_generic_attacks.data
lrwxrwxrwx 1 root root  75 Jul  3 19:56 modsecurity_41_sql_injection_attacks.data -> /usr/lib/modsecurity.d/base_rules/modsecurity_41_sql_injection_attacks.data
lrwxrwxrwx 1 root root  62 Jul  3 19:56 modsecurity_50_outbound.data -> /usr/lib/modsecurity.d/base_rules/modsecurity_50_outbound.data
lrwxrwxrwx 1 root root  70 Jul  3 19:56 modsecurity_50_outbound_malware.data -> /usr/lib/modsecurity.d/base_rules/modsecurity_50_outbound_malware.data
-rw-r--r-- 1 root root   0 Jul  3 19:56 modsecurity_crs_20_protocol_violations.conf
-rw-r--r-- 1 root root   0 Jul  3 19:54 modsecurity_crs_21_protocol_anomalies.conf
-rw-r--r-- 1 root root   0 Jul  3 19:54 modsecurity_crs_23_request_limits.conf
-rw-r--r-- 1 root root   0 Jul  3 19:54 modsecurity_crs_30_http_policy.conf
-rw-r--r-- 1 root root   0 Jul  3 19:54 modsecurity_crs_35_bad_robots.conf
-rw-r--r-- 1 root root   0 Jul  3 19:54 modsecurity_crs_40_generic_attacks.conf
lrwxrwxrwx 1 root root  79 Jul  3 19:56 modsecurity_crs_41_sql_injection_attacks.conf -> /usr/lib/modsecurity.d/base_rules/modsecurity_crs_41_sql_injection_attacks.conf
lrwxrwxrwx 1 root root  69 Jul  3 19:56 modsecurity_crs_41_xss_attacks.conf -> /usr/lib/modsecurity.d/base_rules/modsecurity_crs_41_xss_attacks.conf
-rw-r--r-- 1 root root   0 Jul  3 19:54 modsecurity_crs_42_tight_security.conf
-rw-r--r-- 1 root root   0 Jul  3 19:54 modsecurity_crs_45_trojans.conf
-rw-r--r-- 1 root root   0 Jul  3 19:54 modsecurity_crs_47_common_exceptions.conf
-rw-r--r-- 1 root root   0 Jul  3 19:54 modsecurity_crs_48_local_exceptions.conf.example
-rw-r--r-- 1 root root   0 Jul  3 19:54 modsecurity_crs_49_inbound_blocking.conf
-rw-r--r-- 1 root root   0 Jul  3 19:54 modsecurity_crs_50_outbound.conf
-rw-r--r-- 1 root root   0 Jul  3 19:54 modsecurity_crs_59_outbound_blocking.conf
-rw-r--r-- 1 root root   0 Jul  3 19:54 modsecurity_crs_60_correlation.conf

無効にしたルールを有効にするには、シンボリックリンクを復活させてください。

# ln -sf /usr/lib/modsecurity.d/base_rules/modsecurity_crs_30_http_policy.conf .

バックアップから戻してもいいでしょう。

# cp -P ../activated_rules.orig/modsecurity_crs_30_http_policy.conf .

デフォルトに戻すには、バックアップした activated_rules に戻します。

# cd /etc/httpd/modsecurity.d
# mv activated_rule activated_rule.bk && mv activated_rule.orig activated_rule

誤って上書き・削除してしまったなどデフォルトの状態に戻したい場合は、上書きしたファイルを削除してから mod_security_crs パッケージを再インストールします。

# rm /usr/lib/modsecurity.d/base_rules/*.conf
# yum reinstall mod_security_crs

ログ出力の調整

ログはデフォルトで Apache の error_log/var/log/httpd/modsec_audit.log に出力されます。

modsec_audit.log には、WAFに関係なく404を除く4xx,5xxエラーの詳細な情報が出力されるので、ログが短時間で肥大化しやすく、ディスク容量に余裕がない環境では危険です。

error_logだけで検知された事は判断できるので、心配な環境では通常modsec_audit.logへの出力をオフにしておき、必要なタイミングでオンにすると良いでしょう。

/etc/httpd/conf.d/mod_security.conf
    SecAuditEngine Off

動作確認

SQLインジェクション検知の確認は、GETパラメータに?select+unionを付けてアクセスしてみて、403 Forbidden となるか確認します。
例: https://example.com/hoge/?union+select

error_logの出力例を挙げます。

[Thu Jul 05 13:57:50 2018] [error] [client 192.168.0.100] ModSecurity: Access denied with code 403 (phase 2). Operator GE matched 3 at TX:sqli_select_statement_count. [file "/etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_41_sql_injection_attacks.conf"] [line "108"] [id "981317"] [rev "2"] [msg "SQL SELECT Statement Anomaly Detection Alert"] [data "Matched Data: Connection found within TX:sqli_select_statement_count: 3"] [ver "OWASP_CRS/2.2.6"] [maturity "8"] [accuracy "8"] [tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"] [tag "WASCTC/WASC-19"] [tag "OWASP_TOP_10/A1"] [tag "OWASP_AppSensor/CIE1"] [tag "PCI/6.5.2"] [hostname "example.com"] [uri "/hoge/"] [unique_id "Wz2lTsCoAa4AAEuRGe0AAAAA"]

WAFの検知があったかどうかは、文字列'ModSecurity: Access denied with code 'をgrepすれば調査できます。

# grep 'ModSecurity: Access denied with code ' /var/log/httpd/ssl_error_log

[name "value"] の形式で検知情報が出力されています。適当に改行コードを入れます。

# grep 'ModSecurity: Access denied with code ' /var/log/httpd/ssl_error_log | tail -1 | sed 's/ \[/\n[/g' 
[Thu Jul 05 13:57:50 2018]
[error]
[client 192.168.0.100] ModSecurity: Access denied with code 403 (phase 2). Operator GE matched 3 at TX:sqli_select_statement_count.
[file "/etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_41_sql_injection_attacks.conf"]
[line "108"]
[id "981317"]
[rev "2"]
[msg "SQL SELECT Statement Anomaly Detection Alert"]
[data "Matched Data: Connection found within TX:sqli_select_statement_count: 3"]
[ver "OWASP_CRS/2.2.6"]
[maturity "8"]
[accuracy "8"]
[tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"]
[tag "WASCTC/WASC-19"]
[tag "OWASP_TOP_10/A1"]
[tag "OWASP_AppSensor/CIE1"]
[tag "PCI/6.5.2"]
[hostname "example.com"]
[uri "/hoge/"]
[unique_id "Wz2lTsCoAa4AAEuRGe0AAAAA"]

ここで重要なのは、[id "981317"]です。このルールを無効化する際に SecRuleRemoveById に指定します。

  • id: 検知ルールID
  • client: 検知されたクライアントIP
  • file,line: 検知ルール定義場所
  • msg: 検知ルールの概要
  • data: 検知された問題のデータ
  • hostname,uri: 検知されたURL

クロスサイトスクリプティングは、適当なテキストフォームにjavascript:alert(document.cookie)を入力してサブミットし、403 Forbidden となるか確認します。
error_logの出力例を挙げます。

[Thu Jul 05 14:55:33 2018] [error] [client 192.168.0.100] ModSecurity: Access denied with code 403 (phase 2). Pattern match "\\\\bdocument\\\\b\\\\s*\\\\.\\\\s*\\\\bcookie\\\\b" at ARGS:login_email. [file "/etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_41_xss_attacks.conf"] [line "107"] [id "958001"] [rev "2"] [msg "Cross-site Scripting (XSS) Attack"] [data "Matched Data: document.cookie found within ARGS:login_email: javascript:alert(document.cookie)"] [severity "CRITICAL"] [ver "OWASP_CRS/2.2.6"] [maturity "8"] [accuracy "8"] [tag "OWASP_CRS/WEB_ATTACK/XSS"] [tag "WASCTC/WASC-8"] [tag "WASCTC/WASC-22"] [tag "OWASP_TOP_10/A2"] [tag "OWASP_AppSensor/IE1"] [tag "PCI/6.5.1"] [hostname "example.com"] [uri "/frontparts/login_check.php"] [unique_id "Wz2y1cCoAa4AAEuTGnsAAAAC"]

適当に改行コードを入れます。

# grep 'ModSecurity: Access denied with code ' /var/log/httpd/ssl_error_log | tail -1 | sed 's/ \[/\n[/g'
[Thu Jul 05 14:55:33 2018]
[error]
[client 192.168.0.100] ModSecurity: Access denied with code 403 (phase 2). Pattern match "\\\\bdocument\\\\b\\\\s*\\\\.\\\\s*\\\\bcookie\\\\b" at ARGS:login_email.
[file "/etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_41_xss_attacks.conf"]
[line "107"]
[id "958001"]
[rev "2"]
[msg "Cross-site Scripting (XSS) Attack"]
[data "Matched Data: document.cookie found within ARGS:login_email: javascript:alert(document.cookie)"]
[severity "CRITICAL"]
[ver "OWASP_CRS/2.2.6"]
[maturity "8"]
[accuracy "8"]
[tag "OWASP_CRS/WEB_ATTACK/XSS"]
[tag "WASCTC/WASC-8"]
[tag "WASCTC/WASC-22"]
[tag "OWASP_TOP_10/A2"]
[tag "OWASP_AppSensor/IE1"]
[tag "PCI/6.5.1"]
[hostname "example.com"]
[uri "/frontparts/login_check.php"]
[unique_id "Wz2y1cCoAa4AAEuTGnsAAAAC"]

POSTパラメータ ARGS:login_email に入力された値 javascript:alert(document.cookie) が検知された事が読み取れます。

一行が長いので要約します。

# tail -1 /var/log/httpd/ssl_error_log | perl -nle '/^(?:.*? ){3}(.*?) .*id "(.*?)".*msg "(.*?)"/ && print "$1 $2 $3"'

WAFルールの無効化

利用者からの正当なリクエストを誤って検知してしまう偽陽性(false positive)が発生した場合、取り急ぎWAFルールを無効にして対応します。
正しい検知であり、アプリケーション側の問題であれば、修正して再度ルールを有効にします。

WAFに検知された場合、デフォルトでは 403 Forbidden となります。ベーシック認証やIP制限でも発生するので、WAFによるものかはログで確認します。

カスタムルールファイル

ルールを完全にオフにする場合は SecRuleEngine を、個別にオフにする場合は SecRuleRemoveById を指定します。.htaccess以外の任意のコンテキストに記述できます。バーチャルホスト以外のコンテキストに記述する場合は、下記 z_customrules.conf を使用してください。SecRuleRemoveById は SecRule より後に評価される必要があるため、このファイルに記述するのが安全です。

原則、設定のカスタマイズは。下記ファイルを新規作成して記述してください。
- /etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_15_customrules.conf
- /etc/httpd/modsecurity.d/activated_rules/z_customrules.conf

modsecurity_crs_15_customrules.conf は、定数初期化後かつルール定義前に読み込まれます。SecRuleより前に記述する必要があるSecDefaultActionやデフォルトルールより優先したいルールなどを記述します。
z_customrules.conf は、ルール定義後に読み込まれます。

検出オンリーモード

検知しても遮断せず、ログのみ記録します。

/etc/httpd/conf.d/mod_security.conf
    SecRuleEngine DetectionOnly

WAF機能を完全オフ

最終手段として機能を完全オフにするには、次のように設定します。

/etc/httpd/conf.d/mod_security.conf
    SecRuleEngine Off

特定のURLでWAF機能を無効化

/admin 以下でWAFを無効化する設定は次の通りです。

z_customrules.conf
<Directory /var/www/html/admin>
    SecRuleEngine Off
</Directory>

URLにファイルが対応していないならば、Locationを使用します。

z_customrules.conf
<Location /admin>
    SecRuleEngine Off
</Location>

または、ルール個別に無効化する場合は、SecRuleRemoveById を記述します。引数のルールIDはスペース区切りで複数指定可能です。SecRuleRemoveById 自体を複数記述しても問題ありません。

z_customrules.conf
<Directory /var/www/html/admin>
    SecRuleRemoveById 981317 950001 959073 981255 981245
    SecRuleRemoveById 950901 960024 981173 973300
</Directory>

バーチャルホスト単位でWAF機能を無効化

バーチャルホストコンテキストに、特定のURLでWAF機能を無効化する設定を記述します。

<VirtualHost *:443>
    <IfModule mod_security2.c>
        SecRuleEngine Off
    </IfModule>
</VirtualHost>

mod_security.confの管理外のコンテキストに記述する場合は、モジュールをアップロードしてもエラーにならないよう IfModule を指定しましょう。

POSTサイズの調整

phpのpost_max_size,upload_max_filesizeに合わせて、WAFのSecRequestBodyLimit,SecRequestBodyNoFilesLimitを調整する必要がありそうです。
https://qiita.com/70_10/items/3cfa76710c2321fa0d63

参考情報

本家wiki
本家Reference Manual v2.x
本家Log Data Format