1. 项目概述:为什么要在Nginx上集成ModSecurity?
如果你负责过线上Web服务器的运维或安全加固,大概率听过WAF(Web应用防火墙)这个词。它像一道安检门,站在你的应用服务器前面,过滤掉那些恶意的SQL注入、跨站脚本(XSS)、路径遍历等攻击请求。ModSecurity就是一个开源的、功能强大的WAF引擎,它有一整套规则(比如著名的OWASP Core Rule Set)来识别和阻断攻击。
那么问题来了,Nginx本身性能强悍、配置灵活,是很多高并发场景的首选,但它原生并不像Apache那样直接支持ModSecurity作为模块加载。在Ubuntu上为Nginx配置ModSecurity,本质上是一个“打补丁”的过程:我们需要获取ModSecurity的源码,将其编译成一个Nginx能识别的动态模块(ngx_http_modsecurity_module.so),然后在Nginx的配置中加载并启用它。
我之所以花时间折腾这个,是因为之前遇到过一次针对后台管理页面的撞库攻击,日志里全是各种奇怪的登录尝试。当时临时用fail2ban分析Nginx日志来封IP,虽然有效,但总觉得是“事后诸葛亮”,而且规则维护起来麻烦。ModSecurity能提供实时的、基于规则的内容检测,把防御动作前置,这正是我需要的。整个过程涉及源码编译、依赖处理、规则配置和调优,踩的坑不少,但最终搭建起来的防护层,心里踏实多了。
接下来,我会把从环境准备、编译安装、规则配置到优化排错的完整过程拆开揉碎了讲清楚。无论你是想为公司的业务加一道安全锁,还是纯粹出于学习目的,这篇记录都能给你一个清晰的路线图。
2. 环境准备与依赖梳理
工欲善其事,必先利其器。在开始编译之前,确保你的Ubuntu系统是干净且网络通畅的。我使用的是Ubuntu 22.04 LTS,这个版本比较稳定,软件源也新。理论上,20.04或24.04的步骤也大同小异。
2.1 系统更新与基础工具安装
第一步,永远是先更新系统并安装编译所需的工具链。这能避免很多因缺失基础组件导致的编译错误。
sudo apt update sudo apt upgrade -y sudo apt install -y build-essential autoconf automake libtool pkg-configbuild-essential:包含了gcc,g++,make等核心编译工具。autoconf,automake,libtool,pkg-config:这些是用于生成和运行编译配置脚本的工具,很多开源项目(包括ModSecurity)的构建系统依赖于它们。
2.2 安装Nginx的编译依赖和ModSecurity的运行时依赖
Nginx模块的编译需要Nginx本身的源码和其依赖库。同时,ModSecurity作为一个复杂的C++项目,也有自己的一堆依赖。我们需要一次性装好。
sudo apt install -y libpcre3 libpcre3-dev zlib1g zlib1g-dev libssl-dev libxml2-dev libyajl-dev liblmdb-dev libcurl4-openssl-dev libfuzzy-dev这里解释几个关键包:
libpcre3-dev:Perl兼容正则表达式库,Nginx用于location匹配等,ModSecurity的规则引擎也重度依赖它。zlib1g-dev:压缩库,用于Nginx的Gzip压缩功能。libssl-dev:提供SSL/TLS支持,如果你需要HTTPS,这个必不可少。libxml2-dev:XML解析库。ModSecurity的规则和部分解析功能需要处理XML格式数据。libyajl-dev:Yet Another JSON Library。现代Web应用传输JSON很多,ModSecurity需要它来解析JSON请求体。liblmdb-dev:一个轻量级的内存映射数据库。ModSecurity v3用它来持久化一些运行时数据(如IP地址的计数器),比纯内存更可靠。libcurl4-openssl-dev:cURL库的开发文件。ModSecurity的@remote规则功能(如从远程加载黑名单)可能需要它。libfuzzy-dev:提供模糊哈希(如SSDeep)功能,可用于恶意文件检测。
注意:
liblmdb-dev在有些较旧的Ubuntu版本(如18.04)的默认源里可能没有,可能需要添加第三方PPA或从源码编译。在22.04及以后,直接安装即可。
2.3 获取Nginx源码与ModSecurity源码
我们不通过apt安装Nginx,因为需要获取其源码来编译模块。同时,我们需要ModSecurity的源码。选择一个合适的目录,比如/usr/local/src来操作。
cd /usr/local/src # 1. 下载Nginx稳定版源码 (以nginx-1.24.0为例,请检查官网获取最新稳定版) sudo wget http://nginx.org/download/nginx-1.24.0.tar.gz sudo tar -xzvf nginx-1.24.0.tar.gz # 2. 下载ModSecurity v3源码 (libmodsecurity) sudo git clone --depth 1 https://github.com/SpiderLabs/ModSecurity cd ModSecurity # 初始化并更新子模块(非常重要!) sudo git submodule init sudo git submodule update这里有个关键点:一定要使用ModSecurity v3。v2版本是为Apache设计的,虽然有个nginx连接器,但维护状态不佳且复杂。v3版本被重构为独立的库(libmodsecurity),然后通过一个独立的Nginx连接模块(modsecurity-nginx)来工作,这种架构更清晰,也是官方主推的方向。
3. 编译与安装ModSecurity库 (libmodsecurity)
现在,我们先编译核心的WAF引擎——libmodsecurity。
cd /usr/local/src/ModSecurity # 运行构建配置脚本,这里我们选择安装到/usr/local/modsecurity目录,方便管理 sudo ./build.sh sudo ./configure --prefix=/usr/local/modsecurity --with-lmdb sudo make sudo make install./build.sh:这个脚本会调用autoconf、automake等工具生成configure脚本。如果这步报错,通常是因为前面“基础工具”没装全。--prefix=/usr/local/modsecurity:指定安装目录。将所有相关文件(库、头文件)集中放在这里,后续链接时路径清晰。--with-lmdb:显式启用LMDB支持,用于持久化存储。make和make install:编译并安装。
编译过程可能需要几分钟。完成后,你可以在/usr/local/modsecurity/lib/下找到libmodsecurity.so这个核心库文件。
实操心得:编译时如果遇到关于
C++11标准的错误,可能需要检查你的g++版本。Ubuntu 22.04默认的g++-11是没问题的。如果系统较老,可以尝试安装g++-9或更高版本,并通过sudo update-alternatives --config g++来切换默认版本。
4. 编译Nginx并集成ModSecurity模块
这是最关键的一步:我们需要将ModSecurity的Nginx连接器编译成动态模块,并集成到Nginx中。
4.1 下载Nginx连接器模块
这个模块是Nginx和libmodsecurity之间的桥梁。
cd /usr/local/src sudo git clone --depth 1 https://github.com/SpiderLabs/ModSecurity-nginx.git4.2 编译Nginx并添加ModSecurity模块
现在进入Nginx源码目录,执行配置和编译。这里我们采用“动态模块”的方式,这样以后升级Nginx或模块会更灵活。
cd /usr/local/src/nginx-1.24.0 # 查看当前系统已安装的Nginx配置(如果你之前通过apt安装过,这步很重要) nginx -V 2>&1 | grep arguments # 假设输出包含类似 --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx ... 的信息 # 我们需要在编译新Nginx时,复用大部分这些参数,尤其是路径,以避免配置混乱。 # 执行configure命令,这里是一个通用示例,你需要根据上一步的输出调整 sudo ./configure \ --prefix=/etc/nginx \ --sbin-path=/usr/sbin/nginx \ --modules-path=/usr/lib/nginx/modules \ --conf-path=/etc/nginx/nginx.conf \ --error-log-path=/var/log/nginx/error.log \ --http-log-path=/var/log/nginx/access.log \ --pid-path=/var/run/nginx.pid \ --lock-path=/var/run/nginx.lock \ --http-client-body-temp-path=/var/cache/nginx/client_temp \ --http-proxy-temp-path=/var/cache/nginx/proxy_temp \ --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \ --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \ --http-scgi-temp-path=/var/cache/nginx/scgi_temp \ --user=nginx \ --group=nginx \ --with-compat \ --with-file-aio \ --with-threads \ --with-http_addition_module \ --with-http_auth_request_module \ --with-http_dav_module \ --with-http_flv_module \ --with-http_gunzip_module \ --with-http_gzip_static_module \ --with-http_mp4_module \ --with-http_random_index_module \ --with-http_realip_module \ --with-http_secure_link_module \ --with-http_slice_module \ --with-http_ssl_module \ --with-http_stub_status_module \ --with-http_sub_module \ --with-http_v2_module \ --with-mail \ --with-mail_ssl_module \ --with-stream \ --with-stream_realip_module \ --with-stream_ssl_module \ --with-stream_ssl_preread_module \ --with-cc-opt='-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' \ --with-ld-opt='-Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie' \ --add-dynamic-module=/usr/local/src/ModSecurity-nginx参数解析与注意事项:
- 路径参数:
--prefix、--sbin-path等尽量与你系统现有Nginx的配置一致(通过nginx -V查看)。这样编译出来的新Nginx可以直接替换旧的,配置文件无需改动。如果这是全新安装,可以按你的喜好设置。 --with-compat:这个选项对于动态模块的兼容性非常重要,务必加上。--add-dynamic-module=/usr/local/src/ModSecurity-nginx:这是关键!它告诉Nginx的构建系统,将ModSecurity连接器编译为一个动态模块(.so文件),而不是静态链接进去。--with-cc-opt和--with-ld-opt:这些是编译器和链接器的优化与安全选项,直接复制使用即可,能增强Nginx的安全性。
配置完成后,开始编译:
sudo make编译成功后,不要运行make install。因为我们要的只是新编译出的模块文件,而不是覆盖安装整个Nginx(除非你确定要升级)。编译出的模块文件位于objs/目录下。
# 查看编译出的模块 ls -la objs/*.so # 你应该能看到一个名为 `ngx_http_modsecurity_module.so` 的文件4.3 安装动态模块并更新Nginx
现在,将模块文件复制到Nginx的标准模块目录,并替换Nginx主程序。
# 1. 复制动态模块 sudo cp objs/ngx_http_modsecurity_module.so /usr/lib/nginx/modules/ # 2. 备份旧的Nginx可执行文件(如果存在) sudo cp /usr/sbin/nginx /usr/sbin/nginx.backup.$(date +%Y%m%d) # 3. 复制新的Nginx可执行文件 sudo cp objs/nginx /usr/sbin/nginx # 4. 测试新Nginx配置是否正确 sudo nginx -t如果nginx -t显示“configuration file /etc/nginx/nginx.conf test is successful”,恭喜你,Nginx与ModSecurity模块的集成编译成功了。
踩坑记录:直接
make install会覆盖所有文件,包括默认的html目录和配置文件,可能导致你的站点文件被覆盖。所以采用只复制主程序和模块的方式更安全。如果nginx -t报错找不到模块,可能是因为/usr/lib/nginx/modules/不在默认的模块搜索路径。可以在nginx.conf最顶部用load_module指令显式指定路径,我们下一步就会做。
5. 配置Nginx启用ModSecurity
模块有了,接下来就是告诉Nginx如何使用它。
5.1 加载模块与基础配置
编辑Nginx的主配置文件/etc/nginx/nginx.conf,在events块之前,添加加载模块的指令。
# /etc/nginx/nginx.conf load_module modules/ngx_http_modsecurity_module.so; user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } ...然后,在http块内,添加ModSecurity的全局配置。我习惯在/etc/nginx/下创建一个单独的目录来管理ModSecurity的配置。
sudo mkdir -p /etc/nginx/modsec创建ModSecurity的主配置文件:
sudo vim /etc/nginx/modsec/modsecurity.conf写入以下基础内容:
# /etc/nginx/modsec/modsecurity.conf # 启用ModSecurity引擎 SecRuleEngine On # 请求体处理 SecRequestBodyAccess On # 限制请求体大小为128MB,可根据业务调整 SecRequestBodyLimit 131072000 SecRequestBodyNoFilesLimit 131072 # 响应体处理(通常建议关闭以提升性能,除非需要检测响应内容) SecResponseBodyAccess Off # SecResponseBodyLimit 524288 # 调试日志级别(生产环境建议设为0,调试时可设为9) SecDebugLog /var/log/nginx/modsec_debug.log SecDebugLogLevel 0 # 审计日志配置 SecAuditEngine RelevantOnly SecAuditLogRelevantStatus "^(?:5|4(?!04))" SecAuditLogParts ABIJDEFHZ SecAuditLogType Serial SecAuditLog /var/log/nginx/modsec_audit.log # 规则文件路径 Include /etc/nginx/modsec/crs-setup.conf Include /etc/nginx/modsec/rules/*.confSecRuleEngine On:开启规则引擎。DetectionOnly模式只记录不阻断,适合初期测试。SecRequestBodyAccess On:允许检查请求体(POST数据等)。SecRequestBodyLimit:单个请求体的最大大小,超过会返回413错误。SecAuditEngine RelevantOnly:审计引擎仅在规则匹配或发生错误时记录日志,节省资源。SecAuditLogRelevantStatus "^(?:5|4(?!04))":一个正则表达式,表示只记录服务器错误(5xx)和客户端错误(4xx,但不包括404)的请求。这可以过滤掉大量正常的日志。Include ...:引入规则文件。这里预设了OWASP CRS的配置和规则目录。
5.2 配置虚拟主机启用WAF
现在,在具体的站点配置(server块)中启用ModSecurity。假设你有一个站点配置文件/etc/nginx/conf.d/mysite.conf。
server { listen 80; server_name your_domain.com; # 启用ModSecurity并指定主配置文件 modsecurity on; modsecurity_rules_file /etc/nginx/modsec/modsecurity.conf; location / { # 你的常规代理或root配置 proxy_pass http://backend_server; # 或 root /var/www/html; } # 为审计日志和调试日志设置正确的权限(如果Nginx以非root用户运行) location /logs/ { internal; alias /var/log/nginx/; } }关键指令:
modsecurity on;:在该server或location作用域开启WAF。modsecurity_rules_file:指向我们刚才创建的主配置文件。
5.3 部署OWASP核心规则集(CRS)
没有规则的WAF就像没有子弹的枪。OWASP Core Rule Set (CRS) 是ModSecurity最著名、最常用的免费规则集。
cd /etc/nginx/modsec # 下载最新的OWASP CRS稳定版 sudo wget https://github.com/coreruleset/coreruleset/archive/refs/tags/v4.0.0-rc1.tar.gz sudo tar -xzvf v4.0.0-rc1.tar.gz sudo mv coreruleset-4.0.0-rc1 crs # 复制配置文件模板 sudo cp crs/crs-setup.conf.example crs-setup.conf # 复制规则文件 sudo mkdir -p rules sudo cp crs/rules/*.conf rules/现在,编辑/etc/nginx/modsec/crs-setup.conf。这个文件是CRS的“总控开关”,你可以在这里调整规则的敏感度、禁用某些规则组等。对于初次使用,我建议先做以下调整:
- 将
SecDefaultAction改为只记录不阻断,便于观察:SecDefaultAction "phase:1,log,auditlog,pass" SecDefaultAction "phase:2,log,auditlog,pass" - 设置规则检测级别(
tx.crs_setup_version等参数在文件里已有,主要是确认版本):# 设置偏执级别(Paranoia Level, PL)。PL越高,规则越严格,误报也可能越多。从PL1开始。 SecAction \ "id:900000,\ phase:1,\ nolog,\ pass,\ t:none,\ setvar:tx.paranoia_level=1" - 根据你的应用类型,可能需要在
crs-setup.conf中排除一些误报。例如,如果你的应用有大量的JSON API,可能需要调整REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example(复制并重命名为.conf)来排除对某些路径的检查。
5.4 创建日志目录并设置权限
ModSecurity需要写入日志文件。
sudo touch /var/log/nginx/modsec_audit.log /var/log/nginx/modsec_debug.log sudo chown nginx:nginx /var/log/nginx/modsec_*.log sudo chmod 644 /var/log/nginx/modsec_*.log确保Nginx进程用户(这里用的是nginx)对这些日志文件有写权限。
6. 测试、验证与性能调优
配置完成后,重载Nginx使配置生效。
sudo nginx -t # 再次测试配置 sudo systemctl reload nginx # 或 sudo nginx -s reload6.1 功能测试
发送一个简单的测试请求,触发一条规则。例如,经典的SQL注入探测:
curl -X GET "http://your_server_ip/?id=1' OR '1'='1"然后检查审计日志:
sudo tail -f /var/log/nginx/modsec_audit.log你应该能看到一条日志记录,其中包含Message: Warning. detected SQLi using libinjection之类的信息,并注明匹配的规则ID(如942100)。这证明ModSecurity已经成功检测到了攻击。
6.2 性能影响评估与基础调优
开启WAF必然带来性能开销,主要来自正则表达式匹配和请求体解析。以下是一些调优思路:
- 规则集裁剪:CRS非常全面,但你的应用可能只用到了其中一部分协议和功能。可以仔细分析
rules/目录下的文件,禁用与你的应用完全无关的规则组(如REQUEST-921-PROTOCOL-ATTACK.conf如果你没有文件上传功能)。 - 调整偏执级别(Paranoia Level):在
crs-setup.conf中,tx.paranoia_level从1到4。PL1是基础防护,误报少。生产环境可以从PL1开始,稳定后再考虑是否提高到PL2。 - 禁用响应体检查:除非你有检测响应内容泄露的特定需求,否则务必保持
SecResponseBodyAccess Off。检查响应体对性能影响巨大。 - 合理设置请求体限制:
SecRequestBodyLimit不要设置得过大,根据你实际的业务需求(如文件上传大小)来设定。 - 使用
SecRuleRemoveById或SecRuleUpdateActionById:在站点配置中,针对特定路径(如/api/)排除已知的、会产生大量误报的规则。这需要结合审计日志进行分析。 - 启用连接池和缓存:确保Nginx本身的
worker_processes、worker_connections设置合理,并考虑启用代理缓存等,从整体上缓解后端压力。
6.3 监控与维护
- 日志轮转:
modsec_audit.log可能会快速增长,需要配置日志轮转。编辑/etc/logrotate.d/nginx,添加对ModSecurity日志的处理。 - 规则更新:安全规则需要定期更新。可以编写一个定时任务(cron job),定期从GitHub拉取最新的CRS规则,并重载Nginx。但务必在测试环境先验证。
- 误报分析:定期查看审计日志,分析误报(false positive)。将合法的请求路径或参数添加到排除规则(exclusion rule)中,这是一个持续的过程。
7. 常见问题与排查技巧实录
在实际部署和运行中,你肯定会遇到各种问题。这里记录几个我踩过的坑和解决方法。
7.1 编译阶段问题
问题1:configure时报错,提示找不到libmodsecurity。
checking for ModSecurity ... not found configure: error: ngx_http_modsecurity_module requires the ModSecurity library.解决:这是因为configure脚本找不到libmodsecurity的库文件和头文件。你需要通过环境变量告诉它路径。
# 在运行Nginx的configure命令之前,设置环境变量 export MODSECURITY_INC=/usr/local/modsecurity/include export MODSECURITY_LIB=/usr/local/modsecurity/lib # 然后重新运行./configure命令,并在最后加上: # --add-dynamic-module=/usr/local/src/ModSecurity-nginx更稳妥的做法是,将库路径添加到系统配置中:
# 创建modsecurity库配置文件 sudo sh -c "echo /usr/local/modsecurity/lib > /etc/ld.so.conf.d/modsecurity.conf" # 更新动态链接库缓存 sudo ldconfig之后,再重新运行Nginx的configure和make。
问题2:Nginx启动失败,错误日志显示module “/usr/lib/nginx/modules/ngx_http_modsecurity_module.so” is not binary compatible
解决:这通常是因为动态模块与当前运行的Nginx版本不兼容。可能的原因:
- 你编译模块时使用的Nginx源码版本,与系统当前安装的Nginx二进制版本不一致。
- 编译时缺少
--with-compat选项。
确保:编译模块的Nginx源码版本,必须与你最终要运行的Nginx二进制版本完全一致。最安全的方法就是像我前面写的,用同一份源码编译出新的Nginx主程序来替换旧的。
7.2 运行阶段问题
问题3:Nginx启动或重载成功,但审计日志modsec_audit.log里没有任何记录,即使发送攻击payload。
排查步骤:
- 检查配置语法:
sudo nginx -t确保无误。 - 检查
modsecurity指令:确认在server或location块中设置了modsecurity on;。 - 检查规则引擎状态:确认
modsecurity.conf中SecRuleEngine是On或DetectionOnly。 - 检查日志路径和权限:确保
/var/log/nginx/modsec_audit.log文件存在,且Nginx进程用户(如nginx)对其有写权限。可以尝试sudo -u nginx touch /var/log/nginx/test.log来测试。 - 降低日志级别:临时将
SecDebugLogLevel设为1或3,查看modsec_debug.log,里面会有更详细的加载和执行信息。 - 检查规则文件路径:确认
modsecurity.conf中的Include指令路径正确,且规则文件(如crs-setup.conf)没有语法错误。一个快速测试方法是,在modsecurity.conf最前面加一条简单的规则:SecRule ARGS "@contains test" "id:1000,phase:2,log,deny,status:403",然后访问http://yourserver/?arg=test,看是否触发403和日志记录。
问题4:大量误报,阻塞了正常用户请求。
解决:这是使用WAF最常见的问题。
- 从DetectionOnly开始:初期一定要将
SecRuleEngine设为DetectionOnly,并设置SecAuditEngine RelevantOnly,只观察不阻断。运行你的业务一段时间,收集日志。 - 分析审计日志:使用工具(如
modsec-clamfi)或自己写脚本分析modsec_audit.log,找出触发规则的正常请求。重点关注规则ID(如942100)和触发的参数。 - 添加排除规则(Exclusion Rules):这是最精准的方法。在
modsecurity.conf中(或专门的自定义规则文件里,并在主配置中Include),在CRS规则加载之前,使用SecRuleRemoveById或SecRuleUpdateActionById。- 按路径排除:如果
/api/v1/upload这个路径总是误报,可以:SecRule REQUEST_URI "@beginsWith /api/v1/upload" \ "id:10000,\ phase:1,\ nolog,\ pass,\ ctl:ruleRemoveById=942100" - 按参数排除:如果参数
search_term容易触发SQLi规则,可以:SecRule REQUEST_FILENAME "@rx ^/search$" \ "id:10001,\ phase:2,\ nolog,\ pass,\ ctl:ruleRemoveTargetById=942100;ARGS:search_term"
- 按路径排除:如果
- 调整CRS配置:仔细阅读
crs-setup.conf中的注释,调整tx.paranoia_level、tx.anomaly_score_threshold等参数,或启用/禁用特定的规则文件。
问题5:性能明显下降,服务器负载升高。
解决:
- 定位瓶颈:使用
top或htop观察,是CPU高还是I/O高?ModSecurity主要是CPU密集型。 - 检查规则数量:
grep -r "SecRule" /etc/nginx/modsec/rules/ | wc -l。规则数过多会直接影响性能。 - 实施调优措施:
- 确保
SecResponseBodyAccess Off。 - **降低
SecRequestBodyLimit和SecRequestBodyNoFilesLimit**到合理值。 - 在
location块中精细控制:只为需要防护的路径(如登录、表单提交)开启modsecurity on;,对静态文件(如图片、CSS、JS)可以关闭。 - 考虑硬件或架构升级:如果经过优化后性能仍不满足,可能需要更强大的CPU,或者考虑将WAF功能卸载到专门的硬件设备或云WAF服务。
- 确保
整个过程从准备到调优,确实需要一些耐心。但一旦搭建完成并稳定运行,它为你Web应用带来的主动防护能力,是单纯依赖系统防火墙或网络ACL无法比拟的。尤其是在应对自动化扫描和常见Web漏洞攻击时,ModSecurity能帮你过滤掉绝大部分噪音,让你更专注于真正的业务逻辑和安全威胁。