Discourse + 网络应用防火墙(WAF)mod_security

在 Discourse 前面推荐使用哪种 WAF?

我最近成功修改了 Discourse Docker 容器中安装的 nginx 配置,加入了 ModSecurity 和 OWASP(开放 Web 应用程序安全项目)CRS(核心规则集)。

到目前为止,我的测试结果非常好,ModSecurity 似乎与 Discourse 开箱即用配合良好。

其他用户在使用 WAF 与 Discourse 方面有什么经验?除了 ModSecurity,您还有其他推荐吗?

关于 WAF 重要性的说明:它们不仅为 Web 应用程序本身提供广泛的防护,还能防护其整个依赖栈,有效抵御 0 日漏洞。

有人指出 Discourse 团队设有漏洞赏金计划,这非常好!

……但当然,不存在 100% 安全的代码。错误难免会发生,而且即便漏洞并非由您的团队引入,也可能使您的项目面临严重安全风险。

例如,回顾近期发生的 CVE-2019-11043:这是 php-fpm 中的一个漏洞,攻击者可在运行易受攻击版本的 php-fpm 和 nginx 的服务器上利用该漏洞执行远程代码。

然而,CVE-2019-11043 可以通过拦截包含回车符或换行符的请求来完全缓解:

这只是一个示例,说明即使 Web 应用程序自身的代码没有任何缺陷,其安装部署仍可能因严重漏洞而被利用。就 Discourse 而言,在 Docker 安装过程中会引入大量外部软件,这可能导致 Discourse 在未来面临安全风险。

而在上述案例中,ModSecurity CRS 已经能够拦截包含换行符和回车符的请求——从而在 0 日漏洞出现之前,有效保护其他本易受 CVE-2019-11043 攻击的 Web 服务器。

此类漏洞层出不穷。对于 Web 应用程序而言,使用如 ModSecurity 这样的 WAF 具有显著优势。

这是一个糟糕的想法,不建议采用。对于 JavaScript 应用而言,此类方案带来的好处极为有限,却会给您的托管环境带来显著的复杂性。

相关:我刚刚发现 @joelradon 发布的这篇指南,介绍了如何在 Discourse Docker 容器之前,在 nginx 中安装带有 NAXSI WAF 的 Discourse:

这和你上面要求的一样,无法提供支持。

如果有帮助的话,我也可以在那里添加一个“不支持”的标签吗?

我认为在 Discourse 部署中,期望某种能自动缓解问题的魔法设备,这种想法有些误导。我们设有赏金计划,会在问题报告后数小时内为 Discourse 发布补丁。站点默认运行 tests-passed,在当前的情况下,这包含了今天的提交。

当然,如果你运行的是多年前已被利用的软件,且由于某些原因无法升级,那么使用 WAF(Web 应用防火墙)是有意义的,因为它可能救你一命。但在 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

# 更新 configure 行以包含上面检出的 ModSecurity 模块
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 是在构建镜像时执行的。而该镜像仅由 Discourse 团队在上传到 Docker Hub 之前构建。当运行 Discourse 的 ./launcher rebuild app 命令时,它会触发(如果有可用更新)一个 docker pull,从 Docker Hub 获取最新的 Discourse Docker 镜像。同样,这不会重新构建镜像、执行 Dockerfile 或执行上面修改的 install-nginx 脚本。

据我所知,触发更新后的 install-nginx Bash 脚本运行(该脚本由 Dockerfile 执行)的唯一方法是让 Docker 构建一个镜像。例如,这会触发 Docker 构建一个名为 discourse_modsecurity 的新镜像——该镜像将使用本地修改后的 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

现在,我们添加一个新的模板文件,以设置我们的 nginx 配置,使其包含必要的 modsecurity 文件/块:

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 应用时,它将使用您新的 discourse_modsecurity Docker 镜像(该镜像使用带有 ModSecurity 的 nginx),并将配置 nginx 以使用 OWASP CRS。

/var/discourse/launcher rebuild app

我同意你的观点,ModSecurity 或类似的 WAF 并非万能灵药。

但我至少知道 一个在 RoR 中发现并影响 Discourse 的漏洞,该漏洞本可以通过类似 ModSecurity 的机制得到缓解。

在我们修复该漏洞时,从日志中发现,在 CVE 公开披露和修复之前,该漏洞至少已在我们的某个论坛中被实际利用。幸运的是,这并未导致任何信息泄露,但这仅仅是因为我们的部署方式与标准安装有所不同(即运气使然)。

不过,我尚不确定由此增加的复杂性是否超过了其带来的安全收益。

我将 WAF 比作基于签名的病毒扫描器,在我看来,在攻击面有限的环境中(例如您的非 Windows 服务器),它们的作用不大。

它们无法捕获所有威胁,但理论上能够识别常见的攻击模式(例如 SQL 注入)和已知漏洞利用(例如上述的 RoR 漏洞),并适用于多种软件。

我理解在某些环境(如企业环境)中部署 WAF 的价值:那里运行着大量应用程序,其具体行为可能难以完全掌握。在这种情况下,您需要确保每个应用程序都能抵御这些漏洞利用,而无需逐一排查每个应用。这将原本 N×M 规模的问题简化为 N+M 的规模。

不过,是否值得在您的网站上运行 WAF 则是另一回事,这需要您根据自身情况进行风险(可能导致故障)与收益(提供保护)的权衡分析。