Discourse + Web Application Firewall (WAF) mod_security

Discourse の前面で実行する推奨される WAF は何ですか?

最近、Discourse の Docker コンテナにインストールされている nginx 設定を修正し、ModSecurity と OWASP(Open Web Application Security Project)の CRS(Core Rule Set)を含めることに成功しました。

これまでのテストは非常に良好で、ModSecurity は Discourse と標準設定(out of the box)で非常にうまく連携しているようです。

他のユーザーは WAF と Discourse の組み合わせについてどのような経験をお持ちでしょうか?ModSecurity 以外に推奨する WAF はありますか?

WAF が重要な理由についての補足:WAF は、Web アプリケーション自体だけでなく、その依存関係のスタック全体に対して、0 日攻撃を含む広範な保護を提供します。

Discourse チームにはバグバウンティプログラムがあることが指摘されました。これは素晴らしいことです!

…しかし、もちろん 100% 安全なコードというものは存在しません。ミスは起こり得ますし、プロジェクトに重大な脆弱性が含まれる原因が必ずしも自チームのミスである必要もありません。

例えば、最近の CVE-2019-11043 の事例を考えてみてください。これは php-fpm のバグで、脆弱なバージョンの php-fpm と nginx を実行しているサーバー上でリモートコード実行を可能にするものでした。

しかし、CVE-2019-11043 は、キャリッジリターンや改行を含むリクエストをブロックすることで 完全に 緩和可能です。

これは、Web アプリケーション自体に欠陥がなくても、そのインストールが重大な脆弱性によって悪用され得る一例です。Discourse の場合、Docker でのインストール時に多数の外部ソフトウェアが取り込まれるため、将来 Discourse が脆弱になる可能性があります。

上記のケースでは、ModSecurity CRS が すでに 改行やキャリッジリターンを含むリクエストをブロックしています。これにより、0 日攻撃が発生する に、本来脆弱な Web サーバーを CVE-2019-11043 から効果的に保護しています。

このような脆弱性は 常に 発見されています。Web アプリケーションに対して ModSecurity のような WAF を使用することには 大きな 利点があります。

これは悪いアイデアであり、推奨されません。JavaScript アプリにとってこのようなことの利点は極めて限定的であり、ホスティング設定に大きな複雑さを加えます。

関連: @joelradon による、Discourse Docker コンテナの前に nginx で NAXSI WAF を使用して Discourse をインストールする方法に関するガイドを見つけました:

上記のご要望と同様に、サポート対象外となります。

よろしければ、そこに「サポート対象外」のタグを追加することもできますが、いかがでしょうか?

Discourse のセットアップにおいて、問題を自動的に軽減する魔法のようなデバイスへの願望は、やや誤った方向にあると思います。私たちはバウンティプログラムを導入しており、報告された数時間以内に Discourse の問題を修正しています。サイトはデフォルトで tests-passed を実行しており、これは本日のコミットを含んでいます。

確かに、数年前に悪用されたソフトウェアを実行しており、何らかの理由でアップグレードする自由がない場合、WAF はあなたを救う可能性があるため理にかなっています。しかし、Discourse の場合、それはせいぜい誤った方向にあると思います。

launcher rebuild app を実行しても、nginx の ModSecurity 設定変更が失われることなく永続化できることを確認しました。手順は以下の通りです。

まず、discourse_docker リポジトリ から取得し、/var/discourse/ にクローンされている install-nginx のローカルコピーを更新します。

cd /var/discourse/image/base
cp install-nginx install-nginx.`date "+%Y%m%d_%H%M%S"`.orig

# nginx ソースをダウンロードする直前に ModSecurity nginx モジュールをチェックアウトするブロックを追加
grep 'ModSecurity' install-nginx || sed -i 's%\(curl.*nginx\.org/download.*\)%# mod_security\napt-get install -y libmodsecurity-dev modsecurity-crs\ncd /tmp\ngit clone --depth 1 https://github.com/SpiderLabs/ModSecurity-nginx.git\n\n\1%' install-nginx

# 上記でチェックアウトした ModSecurity モジュールを含むように configure コマンド行を更新
sed -i '/ModSecurity/! s%^[^#]*./configure \(.*nginx.*\)%#./configure \1\n./configure \1 --add-module=/tmp/ModSecurity-nginx%' install-nginx

# クリーンアップセクションに行を追加
grep 'rm -fr /tmp/ModSecurity-nginx' install-nginx || sed -i 's%\(rm -fr.*/tmp/nginx.*\)%rm -fr /tmp/ModSecurity-nginx\n\1%' install-nginx

なお、install-nginx スクリプトを実行する Dockerfile は、イメージがビルドされる際に実行されます。このイメージは、docker hub にアップロードされる前に、Discourse チームによってのみビルドされます。Discourse の ./launcher rebuild app コマンドを実行すると(アップデートが利用可能な場合)、docker pull がトリガーされ、docker hub から最新の Discourse docker イメージが取得されます。繰り返しになりますが、これはイメージを再ビルドしたり、Dockerfile や上記で修正した install-nginx スクリプトを実行したりするものではありません。

(Dockerfile によって実行される)更新された install-nginx bash スクリプトを実行させる唯一の方法(私が知る限り)は、docker に新しいイメージをビルドさせることです。例えば、以下のコマンドは discourse_modsecurity という名前の新しいイメージを docker にビルドさせます。これは、ローカルで修正した install-nginx スクリプトを使用してビルドされます。

docker build --tag 'discourse_modsecurity' /var/discourse/image/base/

残念ながら、launcher にカスタムイメージを使用させる方法が見つかりませんでした(run-image を指定すると、実際に nginx を設定するために必要なテンプレートの適用を行わず、指定されたイメージが直接使用されます)。そのため、launcher スクリプトで定義されている image 変数を、新しいローカル docker イメージ discourse_modsecurity を使用するように置き換えます。

# "image="discourse/base:<version>" という行を 'image="discourse_modsecurity"' に置き換え
grep 'discourse_modsecurity' launcher || sed --in-place=.`date "+%Y%m%d_%H%M%S"` '/base_image/! s%^\(\s*\)image=\(.*\)$%#\1image=\2\n\1image="discourse_modsecurity"%' /var/discourse/launcher

次に、必要な modsecurity ファイル/ブロックを含む nginx 設定をセットアップするための新しいテンプレートファイルを追加します。

cat << EOF > /var/discourse/templates/web.modsecurity.template.yml
run:
  - exec:
     cmd:
       - sudo apt-get install -y modsecurity-crs
       - cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
       - sed -i 's/SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/modsecurity/modsecurity.conf
       - sed -i 's^\(\s*\)[^#]*SecRequestBodyInMemoryLimit\(.*\)^\1#SecRequestBodyInMemoryLimit\2^' /etc/modsecurity/modsecurity.conf
       - sed -i '/nginx/! s%^\(\s*\)[^#]*SecAuditLog \(.*\)%#\1SecAuditLog \2\n\1SecAuditLog /var/log/nginx/modsec_audit.log%' /etc/modsecurity/modsecurity.conf

  - file:
     path: /etc/nginx/conf.d/modsecurity.include
     contents: |
        ################################################################################
        # File:    modsecurity.include
        # Version: 0.1
        # Purpose: Defines mod_security rules for the discourse vhost
        #          This should be included in the server{} blocks nginx vhosts.
        # Author:  Michael Altfield <michael@opensourceecology.org>
        # Created: 2019-11-12
        # Updated: 2019-11-12
        ################################################################################
        Include "/etc/modsecurity/modsecurity.conf"
        
        # OWASP Core Rule Set, installed from the 'modsecurity-crs' package in debian
        Include /etc/modsecurity/crs/crs-setup.conf
        Include /usr/share/modsecurity-crs/rules/*.conf

  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /server.+{/
     to: |
       server {
         modsecurity on;
         modsecurity_rules_file /etc/nginx/conf.d/modsecurity.include;

EOF

そして、このテンプレート(templates/web.modsecurity.template.yml)を、アプリの yaml 設定ファイルの templates ブロックに追加します。以下のような形になります。

[root@osestaging1 discourse]# vim /var/discourse/containers/app.yml
...
[root@osestaging1 discourse]# grep -A 6 'templates:' /var/discourse/containers/app.yml
templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"
  - "templates/web.socketed.template.yml"
  - "templates/web.modsecurity.template.yml"
[root@osestaging1 discourse]# 

これで、Discourse docker アプリを再ビルドすると、modsecurity を搭載した nginx を使用する新しい discourse_modsecurity docker イメージが使用され、OWASP CRS を使用するよう nginx が設定されます。

/var/discourse/launcher rebuild app

ModSecurity や同様の WAF が万能薬ではないという点には、私も同意します。

しかし、RoR に見つかり Discourse に影響を与えた脆弱性については、ModSecurity 類似のメカニズムによって緩和されたと確信しています(少なくとも 1 つは)。

この脆弱性のパッチ適用時、CVE が一般に知られパッチが適用される前にも、私たちのログ上で少なくとも 1 つのフォーラムで実際に悪用されていたことが確認されました。情報漏洩には至りませんでしたが、それは標準的なインストールとは異なる設定をいくつか行っていたためであり、つまり「運が良かっただけ」でした。

ただし、セキュリティ向上のメリットが、追加される複雑さのデメリットを上回るかどうかはわかりません。

WAF はシグネチャベースのウイルススキャナーに似ていると考えています。私の見解では、攻撃対象領域が限られた環境(例えば、Windows 以外のサーバーなど)ではあまり役立ちません。

これらはすべてを検知するわけではありませんが、理論的には、SQL インジェクション(SQLI)のような一般的な攻撃パターンや、上記の RoR の脆弱性のような既知の脆弱性を、さまざまなソフトウェアにわたって検知できるはずです。

特に、神のみぞ知る動作をする多数のアプリケーションが稼働しているエンタープライズ環境では、WAF を運用する価値があると考えられます。個々のアプリケーションすべてを気にせず、各アプリケーションをこれらの脆弱性から保護できていることを確認できるからです。これにより、NxM の規模の問題が N+M に削減されます。

ご自身のサイトでも WAF を運用する価値があるかどうかは別問題であり、破損のリスクと保護のベネフィットを自分で分析して判断する必要があります。