2026年6月24日
2026年6月24日
Apacheのmod_security(ModSecurity)でWAFを設定する方法|OWASP CRSの導入と誤検知対策
ApacheでWAFを動かすなら、ModSecurity(Apacheモジュール名は mod_security2)にOWASP Core Rule Set(CRS)を組み合わせるのが定番の構成です。導入自体は数コマンドで済みますが、WordPressでは投稿の保存やプラグインの設定画面が誤って遮断されやすいため、「検知のみ」で始めてログを見ながら除外ルールを足し、最後に遮断を有効にする順番が大切です。この記事では、その流れをコマンドと設定例つきで説明します。
ModSecurityとCRSの現状
ModSecurityは長くTrustwaveが開発していましたが、2024年にOWASPへ移管され、現在はOWASPのプロジェクトとして保守されています。系統は2つあります。
- ModSecurity v2: Apacheのモジュールとして動く従来版。Apacheで使うのは基本的にこちら。
- ModSecurity v3(libmodsecurity): ライブラリ+コネクター方式。主にNginxで使われ、Apacheコネクターは成熟度が低いとされる。
ルールそのものはModSecurityに含まれておらず、OWASP CRSを別途読み込みます。CRSはSQLインジェクション、XSS、リモートコード実行などの典型的な攻撃パターンを検出する汎用ルール集で、現在は4系が主流です(2026年10月現在)。CRSは3系から4系で設定変数の名前やWordPress向け除外の仕組みが変わっているため、手元のバージョンに合ったドキュメントを見ながら作業してください。
なお、共用レンタルサーバーでは、コントロールパネルの「WAF」設定としてModSecurity相当の機能が提供されていることが多く、自分でルールを編集できないのが普通です。その場合は、誤検知が出たときに一時的に無効化できる場所と、サーバー会社への問い合わせ方法を把握しておけば十分です。以下はVPSや専用サーバーで自分で導入する場合の手順です。
インストール
Debian/Ubuntuでは、パッケージでModSecurityとCRSを入れられます。
sudo apt update
sudo apt install libapache2-mod-security2
# CRS は推奨パッケージ(modsecurity-crs)として一緒に入ることが多い
dpkg -l | grep -E 'modsecurity|mod-security'
# 推奨設定をコピーして有効化
sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
sudo apachectl -M | grep security2
RHEL系(AlmaLinux、Rocky Linuxなど)では mod_security と mod_security_crs というパッケージ名で提供されていることが多いものの、リポジトリやバージョンによって異なります。ディストリビューションのパッケージに含まれるCRSは最新版より古いことがあるため、新しいCRSを使いたい場合はOWASP CRSの公式リリースを取得して読み込む方法もあります。その場合は更新を自分で追いかける必要が出てきます。
最初は検知のみ(DetectionOnly)で動かす
コピー直後の modsecurity.conf は、SecRuleEngine DetectionOnly(検知してログに残すが遮断しない)になっています。この状態のまま再読み込みし、普段の運用をひととおり行います。
# /etc/modsecurity/modsecurity.conf(抜粋)
SecRuleEngine DetectionOnly
SecRequestBodyAccess On
# 監査ログ:問題のあったリクエストだけを詳しく記録
SecAuditEngine RelevantOnly
SecAuditLog /var/log/apache2/modsec_audit.log
sudo apachectl configtest && sudo systemctl reload apache2
検知のみの期間には、管理画面での投稿の作成・更新、メディアのアップロード、各プラグインの設定保存、問い合わせフォームの送信、会員登録やログインなど、実際に使う操作を一通り行います。1〜2週間ほど運用して検知ログを集めると、どのルールがWordPressの正常な操作に反応するのかが見えてきます。
監査ログに含まれる個人情報に注意
監査ログには、リクエスト本文(フォームの入力内容)が記録されることがあります。ログインのリクエストが検知された場合、パスワード欄の値まで残る可能性があります。監査ログはroot以外が読めないパーミッションにし、logrotateで保存期間を決めてください。ModSecurity v2には特定のパラメーターをログ上で伏せる sanitiseArg アクションがあるので、WordPressのパスワード欄(pwd)を伏せる設定も検討します。
SecAction "id:1000001,phase:2,nolog,pass,sanitiseArg:pwd"
ログの読み方
検知されると、Apacheのエラーログに ModSecurity: で始まる行が出ます。見るべき項目は次の3つです。
ModSecurity: Warning. ... [file ".../REQUEST-942-APPLICATION-ATTACK-SQLI.conf"]
[id "942100"] [msg "SQL Injection Attack Detected via libinjection"]
[data "Matched Data: ... found within ARGS:content: ..."] [uri "/wp-admin/post.php"]
- id: 反応したルールの番号。除外設定はこの番号で指定する。
- data(found within ...): どの入力項目に反応したか(例:
ARGS:contentは投稿本文)。 - uri: どのURLへのリクエストか。
CRSは「異常スコア方式」で動いており、個々のルールが加点し、合計が閾値を超えたときに遮断用のルール(受信側は949110)が動きます。遮断の原因を調べるときは、949110の行ではなく、同じリクエストで加点した個々のルールのidを探すのがコツです。
WordPress向けの誤検知対策
CRSのWordPress用除外を有効にする
CRSには、WordPressの正常な動作で反応しやすいルールをまとめて除外する仕組みが用意されています。CRS 3系では crs-setup.conf の設定変数で有効にする方式、CRS 4系では「WordPress Rule Exclusions」というプラグインを追加する方式に変わっています。まずはこれを有効にし、それでも残る誤検知を個別に除外します。
個別の除外ルールを書く
除外は「ルールを丸ごと止める」より「特定のURLの特定の項目だけ検査対象から外す」ほうが、防御を保ったまま誤検知を消せます。CRSの読み込み前に評価させる設定は REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf、読み込み後に評価させる設定は RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf という名前のファイルに書くのが慣例です。
# CRS より前:クラシックエディターの投稿保存で、本文だけルール 942100 の対象から外す
SecRule REQUEST_FILENAME "@streq /wp-admin/post.php" \
"id:1000010,phase:1,pass,nolog,ctl:ruleRemoveTargetById=942100;ARGS:content"
# CRS より後:特定ルールを全体で無効化する(最終手段)
SecRuleRemoveById 942100
独自ルールのidは、CRSと重ならない番号(CRSは9xxxxxを使用)を割り当て、他の独自ルールとも重複させないでください。ブロックエディターは投稿をREST API(/wp-json/wp/v2/...)経由でJSONとして送るため、URLも項目名もクラシックエディターとは異なります。除外を書くときは推測で項目名を書かず、ログの found within に出ている名前をそのまま使います。
パラノイアレベルとアップロード上限
CRSには検査の厳しさを決める「パラノイアレベル」があり、既定の1が誤検知の少ない設定です。上げるほど検知範囲は広がりますが、WordPressでは誤検知が急増するため、まずはレベル1で安定させます。
もう1つのつまずきが、リクエスト本文のサイズ上限です。推奨設定の SecRequestBodyLimit を超える大きなファイルをアップロードすると、ModSecurityが413エラーで拒否します。メディアやバックアップのインポートが失敗する場合は、PHPの upload_max_filesize と合わせてこの値を確認してください。
遮断モードに切り替える
検知ログで正常な操作が引っかからなくなったら、遮断を有効にします。
SecRuleEngine On
sudo apachectl configtest && sudo systemctl reload apache2
# 自分のサイトに対して、遮断されるべきリクエストを送って確認
curl -s -o /dev/null -w "%{http_code}\n" "https://example.com/?exec=/bin/bash"
403が返れば遮断が機能しています。こうした確認用のリクエストは、自分が管理するサイトに対してだけ送ってください。切り替え後しばらくは、管理画面で保存できない、フォームが送れないといった報告が来ていないかを意識し、エラーログの Access denied の行を定期的に確認します。急ぎで業務を止められない場合は、該当ルールを除外するまでの間だけ DetectionOnly に戻す判断もあります。
ModSecurityだけに頼らない
WAFは既知の攻撃パターンを入口で止める仕組みで、脆弱なプラグインをそのまま使ってよい理由にはなりません。WordPress本体・テーマ・プラグインの更新、不要なプラグインの削除、管理画面のアクセス制限、ログイン試行回数の制限と組み合わせて使います。サーバー負荷が気になる場合や、大量アクセスを手前で止めたい場合は、CDN型のクラウドWAFを併用する構成も選択肢になります。
まとめ
- ApacheではModSecurity v2(mod_security2)にOWASP CRSを組み合わせる。ModSecurityは2024年にOWASPへ移管されている。
- 導入直後は
SecRuleEngine DetectionOnlyで運用し、WordPressの日常操作を一通り行ってログを集める。 - ログはルールのid・
found withinの項目名・uriを読み、URLと項目を絞った除外(ctl:ruleRemoveTargetById)を優先する。 - CRSのWordPress用除外(3系は設定変数、4系はプラグイン)を有効にし、パラノイアレベルは1から始める。
- 監査ログには入力内容が残りうるため、権限・保存期間・
sanitiseArgで個人情報を守る。