NginxにModSecurity + OWASP CRSを導入する:オープンソースWAFでWebサイトを全方位に守る

Security tutorial - IT technology blog
Security tutorial - IT technology blog

先月、なかなか厳しい案件を対処したばかりだ。クライアントのECサイトが攻撃者にSQL injectionの脆弱性を突かれ、データベース全体がダンプされてしまった。NginxサーバーもUFWファイアウォールもちゃんと動いていたのに、それでもやられた。原因は呆れるほど単純だった:どちらも、HTTP requestの中に悪意あるペイロードが含まれているということを「理解」できないのだ。

現実の問題:ファイアウォールがあるのになぜWebサイトが攻撃される?

10台以上のサーバーのセキュリティ監査をした結果、ほとんどが同じ脆弱性を抱えていることに気づいた:UFWが有効で、fail2banが動いて、SSHポートも変えてある——しかしHTTPの内容を検査するものが何もない。これが最も危険な死角であるにもかかわらず、見落とされがちだ。

こう考えてみてほしい:ネットワークファイアウォールはビルの入口の警備員のようなものだ——有効な入館証を持っているかどうか(正しいIP、正しいポート)を確認するだけ。しかし、カバンの中身まで検査することはない。攻撃者はポート80/443という正規の入口から堂々と侵入し、URLパラメータに'; DROP TABLE users; --というペイロードを仕込むことができる。

ネットワークファイアウォールでは完全に防ぎきれない代表的な攻撃:

  • SQL Injection — フォームやURLにSQLコードを埋め込み、データベースへの不正アクセスや削除を行う
  • Cross-Site Scripting (XSS) — 悪意あるスクリプトをページに埋め込み、ユーザーのCookieを盗み取る
  • Path Traversal — Webディレクトリ外のシステムファイルを読み取る(../../etc/passwd)
  • Remote File Inclusion (RFI) — 外部サーバーからファイルを読み込んで実行する

原因の分析:アプリケーション層での制御レイヤーの欠如

従来のセキュリティモデルには、ネットワークファイアウォールとアプリケーションコードの2層しかない。コードに脆弱性があった場合——そしてほぼすべてのコードには脆弱性がある——攻撃がデータベースに到達する前に何も遮断できない。

Web Application Firewall(WAF)はまさにその空白を埋める存在だ。WAFはユーザーとアプリケーションの間に位置し、すべてのHTTP requestを読み取り、ルールセットと照合して、怪しいものをあなたのコードに到達する前に遮断する。

最も実用的な点:WAFはコードの即時修正を必要としない。アプリケーションにSQL injectionの脆弱性が残っていても、WAFはペイロードが害を与える前に遮断してくれる——深夜に慌ててパッチを当てるのではなく、きちんと修正するための時間を稼げる。

解決策:どのWAFを選ぶべきか?

実際に3つのアプローチをすべて試した結果、率直な感想を述べる:

1. Cloud WAF(Cloudflare、AWS WAF、Sucuri) — 有効化は簡単でサーバー設定不要だが、費用がかかる(プランによって月$20〜$200)、requestデータが第三者を経由する、ベンダー依存になる。

2. 商用WAFのハードウェア/ソフトウェア — F5、Imperva、Fortiweb。強力だがエンタープライズ価格で、個人VPSや小規模スタートアップには向かない。

3. セルフホスト型ModSecurity + OWASP CRS — 完全無料、Nginxサーバー上で直接動作、完全な制御権を持てる。3つのアプローチをすべて試した結果、ほとんどのサーバーで採用しているのがこれだ。

ModSecurityはNginxのモジュールとして動作するオープンソースのWAFエンジンだ。OWASP Core Rule Set(CRS)はグローバルなセキュリティコミュニティが維持管理するルールセットで、現在10,000以上のルールを持ち、最新のバイパス手法に対応して継続的に更新されている。

NginxにModSecurity + OWASP CRSを導入する(Ubuntu 22.04)

ステップ1:ModSecurityとNginxモジュールのインストール

sudo apt update
sudo apt install -y nginx libnginx-mod-security2 git

インストール後、Nginxでモジュールを有効化する:

# モジュールが存在するか確認する
ls /etc/nginx/modules-enabled/ | grep security

# シンボリックリンクがなければ手動で作成する
sudo ln -s /usr/share/nginx/modules-available/mod-http-modsecurity.conf \
  /etc/nginx/modules-enabled/50-mod-http-modsecurity.conf

ステップ2:ModSecurityの基本設定

# サンプル設定ファイルをコピーする
sudo cp /etc/nginx/modsecurity.conf-recommended /etc/nginx/modsecurity.conf

# ブロックモードを有効化する(デフォルトはDetectionOnly——ログのみ、ブロックなし)
sudo sed -i 's/SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/nginx/modsecurity.conf

注意: 初めてデプロイする場合、まず1週間DetectionOnlyで運用してfalse positiveがないか確認してから、Onに切り替えることを勧める。切り替え方法は以下の通り:

# ログのみモード(導入直後は安全)
SecRuleEngine DetectionOnly

# 実際のブロックモード
SecRuleEngine On

ステップ3:OWASP CRSのダウンロードと設定

# OWASP CRSの最新版をダウンロードする
cd /etc/nginx
sudo git clone https://github.com/coreruleset/coreruleset.git modsecurity-crs

# サンプルから設定ファイルを作成する
sudo cp /etc/nginx/modsecurity-crs/crs-setup.conf.example \
  /etc/nginx/modsecurity-crs/crs-setup.conf

# 統合インクルードファイルを作成する
sudo mkdir -p /etc/nginx/modsec
sudo tee /etc/nginx/modsec/main.conf <<'EOF'
Include /etc/nginx/modsecurity.conf
Include /etc/nginx/modsecurity-crs/crs-setup.conf
Include /etc/nginx/modsecurity-crs/rules/*.conf
EOF

ステップ4:Nginx設定でWAFを有効化する

保護が必要な各serverブロックに/etc/nginx/sites-available/your-site.confで追加する:

server {
    listen 80;
    server_name example.com;

    # このserver blockでModSecurityを有効化する
    modsecurity on;
    modsecurity_rules_file /etc/nginx/modsec/main.conf;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
# リロード前に設定を確認する
sudo nginx -t

# Nginxをリロードする
sudo systemctl reload nginx

WAFが正しく動作しているか確認する

インストール後、定番のペイロードを送信してすぐにテストする:

# SQL Injectionをテストする
curl -v "http://your-server/?id=1'%20OR%20'1'='1"
# 期待値:HTTP 403 Forbidden

# XSSをテストする
curl -v "http://your-server/?q=<script>alert(1)</script>"
# 期待値:HTTP 403 Forbidden

# パストラバーサルをテストする
curl -v "http://your-server/?file=../../etc/passwd"
# 期待値:HTTP 403 Forbidden

ブロックされたrequestを確認するためにログを見る:

# ModSecurityのログはNginxのエラーログに出力される
sudo tail -f /var/log/nginx/error.log | grep -i modsecurity

# または専用の監査ログを確認する
sudo tail -f /var/log/modsec_audit.log

False positiveへの対応:WAFが誤ってブロックする場合

False positiveは初期デプロイ時に最も頭を悩ませる部分だ。以前、WordPressのエディターフォームが、記事にHTMLコードが含まれているためにブロックされたことがあった——CRSがXSSと誤認したのだ。

対処方法:CRS全体を無効にするのではなく、false positiveを引き起こす特定のルールだけを無効化する。/etc/nginx/modsec/main.confのrulesをincludeする行の前に以下を追加する:

# 特定のルールをIDで無効化する(IDはエラーログで確認)
SecRuleRemoveById 941100 941110

# または特定パスをホワイトリストに追加する
SecRule REQUEST_URI "@beginsWith /wp-admin/post.php" \
  "id:1000,phase:1,pass,nolog,ctl:ruleRemoveById=941100"

私がよく実践する手順:1週間DetectionOnlyで運用し、ログをエクスポートして実際のサイトトラフィックでよく発火するルールIDを絞り込み、それぞれが本当に攻撃なのか評価する。複雑なエディターフォームのないサイトでは、false positiveの割合は通常1%未満だ。

運用時に覚えておくべきこと

  • CRSを定期的に更新する — OWASPは最新のバイパス手法に対応するため頻繁に新しいルールをリリースする。/etc/nginx/modsecurity-crsディレクトリでgit pullしてNginxをリロードする。
  • パフォーマンスを監視する — ModSecurityはすべてのrequestをパースするためCPUを消費する。小規模サーバー(<2 vCPU)では、有効化後にCPU負荷を監視すること。
  • fail2banと組み合わせる — 継続的な攻撃に対して毎回403を返すだけでなく、ModSecurityのログをパースしてIPをbanする。
  • 変更前にconfigをバックアップする — 以前main.confを誤って編集してサイト全体が403になり、バックアップからの復元に20分かかったことがある。

ModSecurity + OWASP CRSは銀の弾丸ではない——コードのパッチ適用、依存関係の更新、定期的な監査も引き続き必要だ。しかし、コストゼロで数時間のセットアップで、アプリケーションのコードを1行ずつ修正する前から本物の防御層を手に入れられる。私にとって、それはすべてのサーバーに導入するに十分な理由だ。

Share: