2026年7月18日
2026年7月18日
Apacheのmod_securityでWAFを設定する方法|導入・動作確認・SecRuleの書き方
ModSecurity(mod_security)は、Apache に組み込んで使う WAF(Web アプリケーションファイアウォール)のモジュールです。ModSecurity 自体はリクエストを検査する仕組みで、何を攻撃とみなすかは「ルール」で決めます。通常は、SQL インジェクションやクロスサイトスクリプティングなどの典型的な攻撃を検出する汎用のルール集「OWASP CRS(Core Rule Set)」を組み合わせます。
この記事では、root で操作できる VPS や専用サーバーの Apache に、OS のパッケージで ModSecurity と CRS を入れ、動作を確かめ、独自のルールを書くところまでを説明します。手順とログの例は、2026年10月時点の Ubuntu 24.04 と AlmaLinux 9 のコンテナで実際に確かめたものです。
この記事の前提
| Ubuntu 24.04 | AlmaLinux 9 | |
|---|---|---|
| ModSecurity のパッケージ | libapache2-mod-security2(2.9.7) |
mod_security(2.9.6) |
| CRS のパッケージ | modsecurity-crs(3.3.5) |
mod_security_crs(3.3.5) |
| 入れた直後の動作 | 設定ファイルが無く、modsecurity.conf を作る必要がある |
SecRuleEngine On(遮断モード)で動く |
- CRS はパッケージに含まれているので、GitHub などから別にダウンロードする必要はありません。CRS の最新版は 4 系ですが、両 OS のパッケージは 3.3.5 です。この記事は 3.3.5 を前提にしています
- 旧版の記事にあった CentOS 向けの
yumの手順や、mods-enabled/security2.loadを手で編集する手順は不要です。パッケージを入れるとモジュールの読み込み設定も入ります - 共用レンタルサーバーでは、ModSecurity 相当の機能が管理画面の「WAF」設定として用意されていることが多く、ルールを自分で編集できないのが普通です
Ubuntu 24.04 での導入
1. パッケージを入れる
sudo apt update
sudo apt install apache2 libapache2-mod-security2 modsecurity-crs
インストールすると security2 モジュールが有効になります。読み込まれているかを確認します。
sudo apache2ctl -M | grep security2
security2_module (shared) と表示されれば読み込まれています。
2. 設定ファイルを作る
Ubuntu には、ModSecurity の設定の見本(modsecurity.conf-recommended)だけが入っています。コピーして modsecurity.conf を作ります。
sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
見本の主な初期値は次のとおりです。
| 設定 | 初期値 | 意味 |
|---|---|---|
SecRuleEngine |
DetectionOnly |
検知してログに残すが、遮断はしない |
SecRequestBodyAccess |
On |
POST の本文も検査する |
SecAuditEngine |
RelevantOnly |
ルールに反応したリクエストなどだけを監査ログに残す |
SecAuditLog |
/var/log/apache2/modsec_audit.log |
監査ログの場所 |
3. 読み込まれるファイルを確かめる
読み込みの設定は /etc/apache2/mods-enabled/security2.conf にあります(コメント行は省略)。
<IfModule security2_module>
SecDataDir /var/cache/modsecurity
IncludeOptional /etc/modsecurity/*.conf
IncludeOptional /usr/share/modsecurity-crs/*.load
</IfModule>
/etc/modsecurity/ にある .conf ファイルと、CRS(設定は /etc/modsecurity/crs/crs-setup.conf、ルール本体は /usr/share/modsecurity-crs/rules/)が読み込まれます。独自のルールも /etc/modsecurity/ に .conf ファイルとして置けば読み込まれます。
4. 文法を確かめて反映する
sudo apache2ctl configtest
sudo systemctl reload apache2
Syntax OK と表示されてから反映します。
AlmaLinux 9 での導入
sudo dnf install httpd mod_security mod_security_crs
sudo httpd -M | grep security2
ファイルの置き場所は Ubuntu と違います。
| 内容 | 場所 |
|---|---|
| ModSecurity の設定 | /etc/httpd/conf.d/mod_security.conf |
| モジュールの読み込み | /etc/httpd/conf.modules.d/10-mod_security.conf |
| CRS の設定 | /etc/httpd/modsecurity.d/crs-setup.conf |
| CRS のルール | /etc/httpd/modsecurity.d/activated_rules/ |
| 独自ルールを置く場所 | /etc/httpd/modsecurity.d/local_rules/ |
| 監査ログ | /var/log/httpd/modsec_audit.log |
AlmaLinux のパッケージは、入れた直後から SecRuleEngine On(遮断モード)です。稼働中のサイトに入れる場合は、誤検知で正常なアクセスが止まらないよう、先に検知のみに変えてから Apache を起動し直します。
sudo sed -i 's/SecRuleEngine On/SecRuleEngine DetectionOnly/' /etc/httpd/conf.d/mod_security.conf
grep -n "SecRuleEngine" /etc/httpd/conf.d/mod_security.conf
sudo apachectl configtest
sudo systemctl restart httpd
以下は Ubuntu のパスで説明します。AlmaLinux では上の表の場所に読み替えてください。
検知のみモードで動作を確かめる
攻撃に見えるリクエストを送り、ログに記録されるかを確かめます。
curl -s -o /dev/null -w '%{http_code}\n' 'http://localhost/?exec=/bin/bash'
検知のみのモードでは、通常どおり 200 などが返ります。Apache のエラーログ(Ubuntu は /var/log/apache2/error.log、AlmaLinux は /var/log/httpd/error_log)に、次のような行が残ります。
ModSecurity: Warning. ... [id "932160"] [msg "Remote Command Execution: Unix Shell Code Found"] ...
[id "…"] は反応したルールの番号、[msg "…"] はその説明です。どのルールがよく反応しているかは、次のように数えられます。
sudo grep ModSecurity /var/log/apache2/error.log | grep -o '\[id "[0-9]*"\]' | sort | uniq -c | sort -rn | head
リクエストの詳しい内容(ヘッダーや本文)は監査ログ /var/log/apache2/modsec_audit.log に残ります。
この状態で数日から1週間ほど運用し、正常な操作(記事の保存、プラグインの設定変更、問い合わせフォームの送信など)でルールが反応していないかを確認します。
WordPress 向けの除外を有効にする
CRS 3.3 には、WordPress の管理画面で起きやすい誤検知を避けるための除外ルールが入っていますが、初期状態では無効です。/etc/modsecurity/crs/crs-setup.conf(AlmaLinux は /etc/httpd/modsecurity.d/crs-setup.conf)にある id:900130 の設定のコメントを外し、WordPress の行だけを残します。
SecAction \
"id:900130,\
phase:1,\
nolog,\
pass,\
t:none,\
setvar:tx.crs_exclusions_wordpress=1"
これで誤検知がすべてなくなるわけではありません。残った誤検知の調べ方と除外の書き方は、Apacheのmod_security(ModSecurity)でWAFを設定する方法|OWASP CRSの導入と誤検知対策 で説明しています。
遮断モードに切り替える
誤検知の対処が済んだら、SecRuleEngine を On にします。
sudo sed -i 's/^SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/modsecurity/modsecurity.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
# 攻撃に見えるリクエストが 403 になることを確かめる
curl -s -o /dev/null -w '%{http_code}\n' 'http://localhost/?exec=/bin/bash'
CRS 3.3 は、反応したルールの点数を合計して、しきい値を超えたら遮断する方式(anomaly scoring)です。遮断されたときは、エラーログに [id "949110"](Inbound Anomaly Score Exceeded)の行が残ります。誤検知を調べるときは 949110 ではなく、同じリクエストで記録された個々のルールの番号を見ます。
SecRule の書き方
独自のルールは SecRule で書きます。形は次のとおりです。
SecRule 変数 "演算子" "アクション,アクション,..."
| 部分 | 例 | 意味 |
|---|---|---|
| 変数 | REQUEST_URI、REQUEST_FILENAME、ARGS、ARGS:名前、REMOTE_ADDR、REQUEST_HEADERS:User-Agent |
何を調べるか |
| 演算子 | @contains、@beginsWith、@endsWith、@rx(正規表現)、@ipMatch |
どう調べるか。先頭に ! を付けると「当てはまらない」 |
id |
id:1000100 |
ルールの番号。必須で、ほかのルールと重ねられない |
phase |
phase:1(ヘッダー)、phase:2(本文) |
どの段階で調べるか。POST の値(ARGS の本文側)を見るなら 2 |
| 処置 | deny、pass、status:403 |
当てはまったときにどうするか |
| ログ | log、nolog、msg:'説明' |
ログに残すかと、残す説明文 |
| 変換 | t:none、t:lowercase、t:urlDecodeUni |
調べる前に値を変換する |
| 連結 | chain |
次の行の SecRule とつなぎ、両方に当てはまったときだけ処置する |
独自ルールの id は、CRS などが使っていない範囲から選びます。同じ id が2つあると、configtest が「Found another rule with the same id」というエラーになり、Apache を起動できません。
例1: /admin への接続を IP アドレスで制限する
旧版の記事の /admin のルールは、試すと apache2ctl configtest が「Error parsing actions: Missing ctl value for name: requestBodyProcessor.absentCheck」というエラーになり、Apache が起動できなくなるものでした。同じ目的のルールは次のように書けます。
# /etc/modsecurity/zz-local-rules.conf
SecRule REQUEST_URI "@beginsWith /admin" \
"id:1000100,phase:1,deny,status:403,log,msg:'Access to /admin from non-allowed IP',chain"
SecRule REMOTE_ADDR "!@ipMatch 203.0.113.10,198.51.100.0/24"
- 1行目で「URL が
/adminで始まる」ことを調べ、chainで2行目につなげます - 2行目で「接続元の IP アドレスが許可した一覧に含まれない」ことを調べ、両方に当てはまれば 403 を返します
203.0.113.10などは例です。自分の IP アドレスに置き換えます- Cloudflare などの CDN やロードバランサーの後ろにある場合、
REMOTE_ADDRは中継サーバーの IP アドレスになるため、この書き方では正しく制限できません - WordPress のログイン画面(
wp-login.php)を制限する場合は、REQUEST_FILENAME "@endsWith /wp-login.php"のように対象を変えます
例2: 特定のパラメーターの値を遮断する
SecRule ARGS:test "@contains blockme" \
"id:1000101,phase:2,deny,status:403,log,msg:'test parameter blocked'"
test というパラメーターの値に blockme が含まれていれば 403 を返します。phase:2 にしているので、URL のパラメーターだけでなく POST の本文の値も対象になります。ルールの動きを確かめるときの練習用に使えます。
curl -s -o /dev/null -w '%{http_code}\n' 'http://localhost/?test=blockme'
遮断モードでは 403、検知のみモードでは 200 が返り、エラーログに [id "1000101"] の行が残ります。
ルールを足したときの確認
sudo apache2ctl configtestでSyntax OKを確認する(文法の誤りがあると、再読み込みの後に Apache が起動しなくなります)sudo systemctl reload apache2で反映するcurlで、遮断したいリクエストが 403 になり、通常のページ(http://localhost/など)が 200 のままであることを確かめる- エラーログで、そのルールの
idが記録されていることを確かめる
ログの量と運用での注意
- ログの容量:Ubuntu の監査ログは
/var/log/apache2/に出力され、/etc/logrotate.d/apache2の/var/log/apache2/*.logの対象として毎日ローテートされます。アクセスが多いサイトでは容量の増え方を確認します - パフォーマンス:すべてのリクエストを検査するため、サーバーの負荷が増えます。導入後に応答時間や CPU 使用率を確認します
- ルールの更新:CRS はパッケージの更新で新しくなります。OS の更新を適用した後は、誤検知が増えていないかをエラーログで確認します
- WAF だけに頼らない:WordPress 本体・プラグイン・テーマの更新や、管理画面のログイン対策もあわせて行います
まとめ
- Ubuntu 24.04 は
libapache2-mod-security2とmodsecurity-crs、AlmaLinux 9 はmod_securityとmod_security_crsを入れる。CRS は別にダウンロードしなくてよい - Ubuntu は
modsecurity.conf-recommendedをコピーして設定ファイルを作る。AlmaLinux は最初から遮断モードなので、稼働中のサイトでは先に検知のみにする - 最初は
DetectionOnlyでエラーログを確認し、WordPress 向けの除外(tx.crs_exclusions_wordpress=1)と誤検知の対処をしてからOnにする - 独自ルールは
SecRule 変数 "演算子" "アクション"の形で、重ならないidを付け、configtestとcurlで確かめてから反映する
関連記事: