CentOS Stream 9上のNginxにModSecurity v3とOWASP CRSを導入する手順

CentOS tutorial - IT technology blog
CentOS tutorial - IT technology blog

深夜2時のログ調査という悪夢

深夜2時、PagerDutyのアラートがけたたましく鳴り響きました。Grafanaのダッシュボードは真っ赤に染まり、サーバーのCPU使用率は100%に張り付き、わずか3分間で4万5,000件以上の不要なリクエストがエンドポイント/api/v1/searchへ一気に押し寄せていました。Nginxのアクセスログを開くと、見慣れた攻撃用ペイロードがずらりと並んでいました。

tail -f /var/log/nginx/access.log | grep -E "UNION|SELECT|<script>"

Node.jsのバックエンドではPostgreSQLへのコネクションプールが完全に枯渇していました。レイヤー3/4で動作するIPTablesやFirewalldはまったく役に立ちません。すべてのパケットは正規のポート443を経由し、SSLハンドシェイクも正常に完了していたからです。このような状況では、リクエストがバックエンドに到達する前に、URI、クエリ文字列、リクエストボディを解析・検査するアプリケーション層(レイヤー7)のフィルターが必要不可欠です。これこそが、ModSecurity v3とOWASP Core Rule Set(CRS)を導入すべき理由です。

ModSecurity v3とOWASP CRSはどのように連携するのか?

コマンドを実行する前に、各コンポーネントの役割を整理しておきましょう。

  • ModSecurity v3 (libmodsecurity): C++で記述されたスタンドアロンのトラフィック解析エンジンです。Apacheに密結合していたv2とは異なり、v3は独立したライブラリとして動作し、動的モジュールmodsecurity-nginx経由でNginxと連携します。HTTPパケットのパース、ヘッダーの読み取り、リクエストボディのバッファリング、および各種検証ルールの実行を担当します。
  • OWASP Core Rule Set (CRS): コミュニティによって継続的にメンテナンスされているオープンソースのセキュリティルールセットです。SQLインジェクション、クロスサイトスクリプティング(XSS)、ローカルファイルインクルージョン(LFI)、スキャナー検知など、OWASP Top 10の主要な脆弱性の多くを検知します。CRSは「アノマリスコアリング(Anomaly Scoring)」方式で動作し、不審なリクエストに対して違反スコアを加算していきます。累積スコアがしきい値(デフォルトは5)を超えると、Nginxは即座に接続を遮断し、ステータスコード403 Forbiddenを返します。

CentOS Stream 9での構築手順

以下の手順は、root権限またはsudo権限を持つユーザーで実行してください。

ステップ1: EPELリポジトリの有効化と依存パッケージのインストール

CentOS Stream 9で必要な開発用ライブラリを取得するために、EPELおよびCRB(CodeReady Builder)リポジトリを有効化します。

dnf install -y epel-release
dnf config-manager --set-enabled crb
dnf update -y

dnf install -y gcc-c++ make automake autoconf libtool libxml2-devel \
    pcre2-devel libcurl-devel yajl-devel geoip-devel zlib-devel git

ステップ2: NginxおよびModSecurityモジュールのインストール

EPEL 9リポジトリには、互換性のあるコネクターモジュールを含むNginxパッケージが用意されています。ソースコードから20分以上かけてNginxを再コンパイルする必要はありません。

dnf install -y nginx nginx-mod-modsecurity
systemctl enable --now nginx

Nginxが動的モジュールを認識しているか確認します。

nginx -V 2>&1 | grep -o "--with-compat"

ステップ3: ModSecurity設定ファイルの準備

SpiderLabsのリポジトリから推奨設定ファイルをダウンロードし、Nginxの設定ディレクトリに配置します。

mkdir -p /etc/nginx/modsec
cd /etc/nginx/modsec

curl -fsSL https://raw.githubusercontent.com/owasp-modsecurity/ModSecurity/v3/master/modsecurity.conf-recommended -o modsecurity.conf
curl -fsSL https://raw.githubusercontent.com/owasp-modsecurity/ModSecurity/v3/master/unicode.mapping -o unicode.mapping

監査ログの出力先パスを指定し、ユーザーのファイルアップロードが誤って拒否されないよう、リクエストボディの上限を20MBに引き上げます。

sed -i 's|SecAuditLog /var/log/modsec_audit.log|SecAuditLog /var/log/nginx/modsec_audit.log|' /etc/nginx/modsec/modsecurity.conf
sed -i 's/SecRequestBodyLimit 13107200/SecRequestBodyLimit 20971520/' /etc/nginx/modsec/modsecurity.conf

初期段階では、SecRuleEngine DetectionOnlyの行をそのまま維持してください。この設定により、WAFはトラフィックを即座に遮断せずログに記録(検知のみ)するため、正規ユーザーのサービス利用を中断させるリスクを防げます。

ステップ4: OWASP CRS v4ルールセットの導入

GitHubからCRS v4の安定版リリースを取得します。

cd /etc/nginx/modsec
git clone --branch v4.0.0 https://github.com/coreruleset/coreruleset.git owasp-crs
cd owasp-crs
cp crs-setup.conf.example crs-setup.conf

Nginxがすべてのルールを正しい順序で読み込めるよう、統合設定ファイル/etc/nginx/modsec/main.confを作成します。

cat << 'EOF' > /etc/nginx/modsec/main.conf
Include /etc/nginx/modsec/modsecurity.conf
Include /etc/nginx/modsec/owasp-crs/crs-setup.conf
Include /etc/nginx/modsec/owasp-crs/rules/*.conf
EOF

ステップ5: Nginx仮想ホストでのWAF有効化

仮想ホストの設定ファイル(例: /etc/nginx/conf.d/default.conf)を開き、serverブロック内にModSecurityディレクティブを追加します。

server {
    listen 80;
    server_name example.com;

    # ModSecurityを有効化
    modsecurity on;
    modsecurity_rules_file /etc/nginx/modsec/main.conf;

    location / {
        root /usr/share/nginx/html;
        index index.html;
    }
}

設定ファイルの構文をチェックします。

nginx -t

画面にsyntax is okと表示されたら、Nginxをリロードします。

systemctl reload nginx

ステップ6: 動作テストと監査ログの確認

まずは正常なリクエストを送信して接続を確認します。

curl -I http://localhost/

サーバーからHTTP/1.1 200 OKが返ってきたら、次に典型的なSQLインジェクションのペイロードを送信してみます。

curl -I "http://localhost/?id=1%27%20OR%20%271%27=%271"

監査ログファイルを開き、エンジンがこのリクエストをどのように判定したか詳細を確認します。

tail -n 35 /var/log/nginx/modsec_audit.log

ログには、ルールID 942100(libinjectionによるSQLインジェクション検知)がトリガーされ、違反スコアとして5ポイント加算されたことが記録されています。システムの稼働が安定し、固有の正当なリクエストに対するホワイトリスト登録が完了したら、/etc/nginx/modsec/modsecurity.conf内の該当行を次のように修正して実際のブロックモードへ移行します。

SecRuleEngine On

Nginxを再度リロードします。先ほどのSQLiペイロードを含むcurlコマンドを再実行すると、サーバーは即座にステータスコードHTTP/1.1 403 Forbiddenを返すようになります。

本番運用における実践的ノウハウ

十分な検証を行わずに、本番環境でいきなりSecRuleEngine Onを有効化してはいけません。最低でも48〜72時間はDetectionOnlyモードで運用してください。modsec_audit.logを分析し、CMSのMarkdown記述や特殊文字を含むJSONペイロードなど、正常なリクエストが誤ってスコアリングされていないか(誤検知/False Positive)を確認します。

誤検知が確認された場合は、ルールファイル全体を無効化するのではなく、専用の設定ファイル内でSecRuleRemoveByIdディレクティブを用いてピンポイントでルールを除外します。この運用により、システムの安全性を維持しながら正規ユーザーの利便性を損なわない防御体制を築くことができます。

Share: