2026年5月29日

2026年5月29日

Apacheのmod_securityでWAFを設定する方法|Ubuntu・AlmaLinuxでの導入と独自ルールの書き方

ModSecurity(mod_security)は、Apache に組み込んで使う WAF(Web アプリケーションファイアウォール)のモジュールです。ModSecurity 自体は検査の仕組みで、何を攻撃とみなすかは「ルール」で決めます。一般的には、SQL インジェクションやクロスサイトスクリプティングなどの典型的な攻撃を検出する汎用ルール集「OWASP CRS(Core Rule Set)」を組み合わせて使います。

この記事では、VPS や専用サーバーの Apache に、OS のパッケージで ModSecurity と CRS を入れる手順を説明します。手順は、2026年10月時点の Ubuntu 24.04 と AlmaLinux 9 のコンテナで実際に確認しています。

2026年10月時点のバージョンと前提

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 は2024年に Trustwave から OWASP に移管され、OWASP のプロジェクトとして保守されています。Apache のモジュールとして動くのは v2 系です
  • CRS の最新版は 4 系(2026年10月時点で v4.30.0)ですが、どちらの OS のパッケージも 3.3.5 です。この記事はパッケージの 3.3.5 を前提にしています。CRS 4 では WordPress 向けの除外の仕組みなどが変わっているため、4 系を自分で入れる場合は CRS の公式ドキュメントに従ってください
  • 共用レンタルサーバーでは、ModSecurity 相当の機能が管理画面の「WAF」設定として用意されていることが多く、ルールを自分で編集できないのが普通です。この記事の手順は、root で操作できるサーバー向けです

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

コピーした直後の modsecurity.conf は SecRuleEngine DetectionOnly(検知してログに残すが、遮断はしない)になっています。最初はこのまま使います。

ファイルの読み込みは、/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 ファイル(作った modsecurity.conf など)と、CRS(/usr/share/modsecurity-crs/owasp-crs.load から、設定 /etc/modsecurity/crs/crs-setup.conf とルール本体 /usr/share/modsecurity-crs/rules/*.conf)が読み込まれます。

3. 設定を確かめて反映する

sudo apache2ctl configtest
sudo systemctl reload apache2

AlmaLinux 9での導入

sudo dnf install httpd mod_security mod_security_crs
sudo apachectl -M | grep security2

AlmaLinux のファイル構成は 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/
CRS より前・後に読む除外ファイル /etc/httpd/modsecurity.d/activated_rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf、RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf
独自ルールを置く場所 /etc/httpd/modsecurity.d/local_rules/
監査ログ /var/log/httpd/modsec_audit.log

AlmaLinux のパッケージは、mod_security.conf が最初から SecRuleEngine On(遮断モード)になっています。稼働中のサイトに入れる場合は、誤検知で正常なアクセスが遮断されないよう、先に DetectionOnly に変えてから 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 では上の表の場所に読み替えてください。

検知のみモードで動作を確かめる

検知のみ(DetectionOnly)の状態で、攻撃に見えるリクエストを送り、ログに記録されるか確かめます。

# 攻撃に見えるリクエスト(コマンド実行の試み)
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)に残ります。初期設定(SecAuditEngine RelevantOnly)では、ルールに反応したリクエストや、404 以外の 4xx・5xx のエラーになったリクエストが記録されます。

数日から1週間ほど運用し、正常な操作(記事の保存、プラグインの設定変更、問い合わせフォームの送信など)でルールが反応していないかを確認します。

遮断モードに切り替える

誤検知の対処が済んだら、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 ではなく、個々のルールの番号を見ます。

WordPress向けの除外を有効にする

CRS 3.3 には、WordPress の管理画面などで起きやすい誤検知を避ける除外ルールが含まれていますが、初期状態では無効です。/etc/modsecurity/crs/crs-setup.conf には、次のようにコメントアウトされた設定(id:900130)があります。

#SecAction \
# "id:900130,\
#  phase:1,\
#  nolog,\
#  pass,\
#  t:none,\
#  setvar:tx.crs_exclusions_cpanel=1,\
#  setvar:tx.crs_exclusions_drupal=1,\
#  setvar:tx.crs_exclusions_dokuwiki=1,\
#  setvar:tx.crs_exclusions_nextcloud=1,\
#  setvar:tx.crs_exclusions_wordpress=1,\
#  setvar:tx.crs_exclusions_xenforo=1"

WordPress だけを有効にするには、コメントを外して WordPress の行だけを残します。

SecAction \
 "id:900130,\
  phase:1,\
  nolog,\
  pass,\
  t:none,\
  setvar:tx.crs_exclusions_wordpress=1"

検証環境で、投稿の保存(/wp-admin/post.php)に <script> を含む本文を送ったところ、この設定がないと 403 で遮断され、有効にすると遮断されないことを確認しました。

独自のルールを書く

ルールは SecRule 対象 演算子 "アクション" の形で書きます。独自のルールには、CRS などと重ならない番号(id)を付けます。Ubuntu では /etc/modsecurity/ に .conf ファイルを作れば読み込まれます(AlmaLinux は /etc/httpd/modsecurity.d/local_rules/)。

例: wp-login.php へのアクセスをIPアドレスで制限する

# /etc/modsecurity/zz-local-rules.conf
SecRule REQUEST_FILENAME "@endsWith /wp-login.php" \
    "id:1000001,phase:1,deny,status:403,log,msg:'wp-login.php access from non-allowed IP',chain"
    SecRule REMOTE_ADDR "!@ipMatch 203.0.113.10,198.51.100.0/24"
  • 1行目で「要求されたファイルが /wp-login.php で終わる」ことを調べ、chain で次の行の条件とつなげています
  • 2行目は「接続元の IP アドレスが許可した一覧に含まれない」ことを調べます。両方に当てはまると、deny で 403 を返します
  • 203.0.113.10 などは例です。自分の IP アドレスに置き換えます
  • phase:1 は、リクエストのヘッダーを受け取った段階で検査します
  • Cloudflare などの CDN やロードバランサーの後ろにある場合、REMOTE_ADDR は利用者ではなく中継サーバーの IP アドレスになるため、この書き方では正しく制限できません

旧版の記事にあった ctl:requestBodyProcessor.absentCheck を含むルールは、試すと apache2ctl configtest が「Error parsing actions: Missing ctl value」というエラーになり、Apache を起動できませんでした。ルールを書いた後は、apache2ctl configtest で文法を確認してから反映します。

例: 特定のルールを除外する(誤検知の対処)

誤検知が特定の URL だけで起きる場合は、その URL でだけ、そのルールを無効にします。ルール番号はエラーログの [id "…"] で確認します。

# /etc/apache2/conf-available/modsec-exclusions.conf
<LocationMatch "^/wp-admin/admin-ajax\.php$">
    SecRuleRemoveById 942100
</LocationMatch>
sudo a2enconf modsec-exclusions
sudo apache2ctl configtest
sudo systemctl reload apache2

SecRuleRemoveById は、対象のルールが読み込まれた後に書く必要があります。上のように Apache の設定(conf-available)に書くと、CRS より後に読み込まれます。

ルール全体ではなく、特定の入力項目だけを検査の対象から外したい場合は、CRS より前に読み込まれる除外ファイル /etc/modsecurity/crs/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf(AlmaLinux は activated_rules の同名ファイル)に、ctl を使ったルールを書きます。次の例は、投稿の保存で送られる content だけを、XSS 系のルール(タグ attack-xss)の検査から外します。

SecRule REQUEST_FILENAME "@endsWith /wp-admin/post.php" \
    "id:1000010,phase:1,pass,nolog,ctl:ruleRemoveTargetByTag=attack-xss;ARGS:content"

除外は、範囲が広いほど守りが弱くなります。「どの URL の、どの入力項目の、どのルールか」をできるだけ絞って書きます。WordPress での誤検知の調べ方は Apacheのmod_security(ModSecurity)でWAFを設定する方法|OWASP CRSの導入と誤検知対策 でも詳しく説明しています。

運用での注意

  • パフォーマンス:すべてのリクエストを検査するため、サーバーの負荷が増えます。導入後は応答時間やCPU使用率を確認します
  • ログの容量:監査ログは大きくなりやすいため、logrotate の対象になっているか確認します(Ubuntu・AlmaLinux のパッケージは Apache のログと同じフォルダーに出力します)
  • ルールの更新: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 でログを確認し、誤検知を除外してから On にする
  • WordPress では crs-setup.conf の tx.crs_exclusions_wordpress=1 を有効にする
  • 独自ルールは SecRule で書き、重ならない id を付けて configtest で確認する

関連記事:

お気軽にご相談ください

お見積りへ お問い合わせへ
▲