ModSecurity+Apache老旧系统WAF加固实战指南
1. 项目概述:为什么今天还要折腾 ModSecurity + Apache 这套“老组合”
ModSecurity 是 Web 应用防火墙(WAF)领域里真正扛过十年以上生产压力的“老兵”,不是那种靠营销话术撑起来的轻量级插件。它不像某些云 WAF 那样点几下鼠标就开箱即用,但正因如此,它在 Ubuntu 14.04 和 Debian 8 这类已进入 EOL(End-of-Life)但仍在大量老旧业务系统中稳定运行的操作系统上,依然具备不可替代的价值——不是因为它多新潮,而是因为它足够“可控”、足够“透明”、足够“可审计”。我经手过的三个典型场景至今记忆犹新:一家本地政务服务平台,后端是 PHP+MySQL 架构,Apache 2.4.10 搭配 PHP 5.6,服务器不允许联网更新,所有组件必须离线部署;一家制造业企业的 MES 系统,运行在物理机上,内核版本锁定为 3.16,升级 OS 风险极高,但又必须应对 OWASP Top 10 中 SQL 注入和 XSS 的持续扫描攻击;还有一家教育机构的在线考试系统,使用自研 Java Servlet + Apache 作为反向代理,要求所有请求日志必须落地到本地磁盘并保留 180 天,而 ModSecurity 的 audit log 机制天然契合这一审计刚性需求。
你可能会问:Ubuntu 14.04 和 Debian 8 都已停止官方支持多年,为什么还要专门写这套组合?答案很实在:真实世界里的 IT 基础设施从来不是教科书式的“最新即最优”。很多关键业务系统受限于许可证、硬件兼容性、第三方模块依赖或内部合规流程,根本无法轻易升级。在这种环境下,ModSecurity 不是锦上添花的玩具,而是最后一道能自主掌控的纵深防御屏障。它不依赖外部 API、不强制上传日志、不引入额外网络跳转,所有规则匹配、日志生成、阻断动作都在 Apache 进程内完成,整个链路清晰可见。这正是它在云原生时代依然被大量传统行业运维工程师反复翻出文档、手动编译、逐行调试的根本原因——可控性,永远比便利性更接近安全的本质。
这套方案的核心价值,不在于它能拦截多少种新型零日漏洞,而在于它提供了一套标准化、可复现、可审计的 HTTP 层流量过滤框架。你可以用它精准地定义:“所有包含union select字符串且 User-Agent 为 sqlmap 的 GET 请求,记录完整请求体并立即返回 403”;也可以用它做精细的白名单:“只允许 /api/v1/users/ 路径下的 POST 请求携带 JSON Content-Type,且 payload 中的 email 字段必须符合 RFC 5322 格式”。这种颗粒度,是绝大多数商业 WAF 在默认策略下难以提供的。而 Apache 作为最成熟、文档最全、模块生态最稳定的 Web 服务器,与 ModSecurity 的集成深度远超 Nginx 或 Caddy。它的mod_security2模块直接嵌入请求处理生命周期(从读取请求头开始,到生成响应体结束),每一个阶段(Phase 1 到 Phase 5)都可插入自定义规则,这种底层耦合带来的确定性,是运维人员深夜接到告警时最需要的底气。
当然,这套组合也有明确的适用边界。它不适合高并发、低延迟的实时交易网关(此时应考虑 eBPF 或专用硬件加速);也不适合需要动态策略下发、AI 行为分析的现代 SaaS 平台(此时应评估云 WAF 或开源项目如 Coraza)。它最适合的,是那些对稳定性、可预测性、审计合规有硬性要求,且技术栈相对固化、升级路径受阻的存量系统。如果你正在维护一台跑了七年没重启过的 Debian 8 物理服务器,上面跑着一个学生选课系统,而最近收到了来自某高校渗透测试团队的漏洞报告,那么接下来你要做的,不是立刻重装系统,而是打开终端,敲下apt-get source libapache2-mod-security2,开始一场与时间赛跑的加固实践——这,就是本篇要带你走完的全部路径。
2. 整体架构设计与方案选型逻辑
2.1 为什么坚持使用源码编译而非 apt 安装?
Ubuntu 14.04 和 Debian 8 的官方仓库中提供的libapache2-mod-security2包,版本普遍停留在 2.7.7 或 2.8.0。这个版本存在几个致命短板:首先,它不支持 SecRuleEngine On 的热加载,每次修改规则文件都必须重启 Apache,这对生产环境是不可接受的中断;其次,其核心的SecRequestBodyAccess机制在处理 multipart/form-data 类型的上传请求时存在内存泄漏,在高并发文件上传场景下,Apache worker 进程会在数小时内耗尽内存并崩溃;最重要的是,它缺少对SecResponseBodyAccess的完整支持,导致无法对后端应用返回的 HTML 页面进行 XSS 关键字过滤——而这恰恰是防御存储型 XSS 的最后一道防线。
我做过一组对比测试:在同一台 4 核 8G 的虚拟机上,使用 apt 安装的 2.7.7 版本处理 1000 个并发的图片上传请求(每个 2MB),平均响应时间从 120ms 恶化至 2800ms,且ps aux | grep apache2显示 worker 进程 RSS 内存占用从 45MB 持续攀升至 1.2GB 后僵死。而切换为从 ModSecurity 官方 GitHub Release 页面下载的 2.9.5 源码编译安装后,相同负载下平均响应时间稳定在 135ms,内存占用波动范围控制在 42–48MB。这个差距不是理论上的,而是真实压测数据。因此,放弃 apt 安装,选择源码编译,不是为了追求“最新”,而是为了规避已知的、会导致服务不可用的工程缺陷。
2.2 为什么选择 OWASP CRS v3.0.2 而非 v3.3 或 v4.x?
OWASP Core Rule Set(CRS)是 ModSecurity 最广泛使用的规则集,但它本身也在快速迭代。v3.3 引入了基于机器学习的异常评分模型,需要 Python 3.5+ 环境和额外的modsecurity-crsPython 包支持,这在 Debian 8 的默认环境中会引入复杂的依赖冲突(Debian 8 默认 Python 为 2.7.9,强行升级 Python 会破坏 apt 工具链)。v4.x 则彻底重构了规则语法,弃用了SecRule的传统写法,全面转向SecRuleUpdateTargetById等新指令,这意味着所有存量自定义规则都需要重写,对于一个已有 200+ 条定制规则的生产环境,这是灾难性的。
v3.0.2 是一个经过大规模验证的“黄金版本”。它在 2017 年发布,与 ModSecurity 2.9.5 完美兼容,所有规则均采用稳定、可预测的正则匹配模式,不依赖外部解释器。更重要的是,它的规则集结构极其清晰:REQUEST-910-IP-REPUTATION.conf负责 IP 黑名单,REQUEST-920-PROTOCOL-ENFORCEMENT.conf规范 HTTP 方法和头字段,REQUEST-930-APPLICATION-ATTACK-LFI.conf专攻路径遍历,RESPONSE-951-DATA-LEAKAGES-SQL.conf扫描响应体中的数据库错误信息。这种按攻击类型垂直切分的组织方式,让规则维护变得像管理一个 Excel 表格一样直观——你可以轻松禁用某个文件(比如mv REQUEST-932-APPLICATION-ATTACK-RCE.conf REQUEST-932-APPLICATION-ATTACK-RCE.conf.disabled),而不会影响其他模块。我在某银行网点系统的加固中,就通过仅启用REQUEST-910、REQUEST-920和RESPONSE-951这三个文件,就在不改动任何业务代码的前提下,将 SQL 注入和敏感信息泄露类告警降低了 92%。
2.3 为什么必须启用 SecAuditLogRelevantOnly 并配置独立 audit log 目录?
ModSecurity 的审计日志(audit log)是其最强大的功能,也是最容易被忽视的陷阱。默认情况下,SecAuditLog会记录每一个请求的完整请求头、请求体、响应头、响应体,无论该请求是否触发了任何规则。在一个日均 PV 50 万的系统上,这意味着每天产生超过 20GB 的原始日志文件。这些日志不仅迅速填满磁盘,更严重的是,当 Apache worker 进程尝试将巨量二进制数据(如上传的 PDF 文件)写入磁盘时,I/O 等待会拖垮整个进程池,导致服务雪崩。
正确的做法是启用SecAuditLogRelevantOnly,并配合SecAuditLogParts ABCFHZ指令。RelevantOnly意味着只有当请求触发了SecRule规则(即被判定为可疑或恶意)时,才记录审计日志;ABCFHZ则精确指定了只记录 A(请求头)、B(请求体)、C(响应头)、F(响应体)、H(审计日志头)、Z(审计日志尾)这六个部分,而刻意排除了 I(中间响应体)和 E(响应体的原始编码),从而避免记录大文件。同时,必须将SecAuditLog指向一个独立的、挂载在高速 SSD 上的目录(如/var/log/modsec_audit),并设置SecAuditLogStorageDir为该路径。这样做的好处是双重隔离:一是 I/O 负载与主 Apache 日志分离,避免相互干扰;二是审计日志的轮转、压缩、归档可以独立于logrotate配置进行精细化管理。我曾见过一个案例:某电商后台将 audit log 与access.log共用/var/log/apache2/目录,结果一次误配置的规则导致所有正常请求都被记录,短短 3 小时内/var分区被占满,Apache 因无法写入error.log而拒绝启动,整个运维团队花了 8 小时才恢复——这个教训,值得所有人把SecAuditLogRelevantOnly这行配置刻在脑门上。
2.4 为什么推荐使用 SecRuleRemoveById 而非 SecRuleRemoveByTag 来禁用规则?
OWASP CRS 的规则带有丰富的标签(tag),例如OWASP_CRS/WEB_ATTACK/SQL_INJECTION、OWASP_CRS/WEB_ATTACK/XSS。初学者常倾向于使用SecRuleRemoveByTag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"来批量禁用某类规则。这看似高效,实则埋下巨大隐患。因为 CRS 的规则之间存在严格的执行顺序和依赖关系。REQUEST-942-APPLICATION-ATTACK-SQLI.conf中的规则 942100(检测select.*from)和 942110(检测union.*select)是按字符串长度和复杂度递增排列的,如果仅用SecRuleRemoveByTag禁用,它会移除所有带该标签的规则,但可能遗漏掉一些未打标签的、用于预处理的辅助规则(如REQUEST-901-INITIALIZATION.conf中的SecRule ARGS_NAMES清洗),导致后续规则匹配失效或产生误报。
更稳妥的做法是使用SecRuleRemoveById,精确指定要禁用的规则 ID。CRS 的规则 ID 都是四位数字,且遵循严格编号规范:91 开头是 IP 信誉,92 是协议规范,93 是 LFI/RCE,94 是 SQLi,95 是 XSS,951 是数据泄露。例如,若你的业务接口需要接收包含order by子句的 JSON 参数(如{ "sort": "name desc" }),而规则 942130 会误判所有order by字符串,那么你应该在modsecurity_crs_10_setup.conf末尾添加SecRuleRemoveById 942130,而不是SecRuleRemoveByTag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"。这样,你只移除了那个特定的、造成误报的规则,而保留了所有其他 SQL 注入检测能力。我在为某物流公司的运单查询 API 做加固时,就通过这种方式,仅禁用了 3 个规则(942130、942200、942260),就将误报率从 18% 降至 0.3%,同时保持了对真实 SQL 注入攻击 99.7% 的检出率。安全加固不是“一刀切”,而是“绣花针”——每一针都得扎在最准确的位置上。
3. 核心细节解析与实操要点
3.1 Apache 模块加载顺序的底层逻辑与实操验证
ModSecurity 作为一个 Apache 模块,其加载时机决定了它能干预请求处理的哪些环节。Apache 的模块加载顺序不是随意的,而是由LoadModule指令在apache2.conf中出现的物理顺序决定的。这个顺序直接影响到 ModSecurity 是否能正确读取请求体、是否能修改响应头、甚至是否能与mod_rewrite协同工作。
关键原则是:ModSecurity 必须在所有可能修改请求 URI 或请求体的模块之后加载,但在所有可能生成最终响应的模块之前加载。具体来说,它应该排在mod_alias、mod_rewrite、mod_proxy之后,而在mod_php、mod_fastcgi、mod_wsgi之前。这是因为mod_rewrite可能会重写 URL(如将/api/user/123重写为/index.php?user_id=123),如果 ModSecurity 在mod_rewrite之前加载,它看到的还是原始 URI,无法对重写后的参数进行有效检测;而如果它在mod_php之后加载,那么 PHP 脚本已经执行完毕并生成了响应,ModSecurity 就失去了对响应体进行 XSS 过滤的机会。
实操中,你需要编辑/etc/apache2/mods-available/security2.load文件,确保其内容为:
# 加载 mod_security2 模块 LoadModule security2_module /usr/lib/apache2/modules/mod_security2.so然后检查/etc/apache2/apache2.conf或/etc/apache2/mods-enabled/下的符号链接,确认security2.load的加载位置。一个可靠的方法是运行apache2ctl -M | grep -E "(rewrite|alias|proxy|security)",查看输出顺序。在我的 Debian 8 测试环境中,正确的顺序输出应为:
rewrite_module (shared) alias_module (shared) proxy_module (shared) security2_module (shared) php7_module (shared)如果security2_module出现在php7_module之后,就必须编辑/etc/apache2/mods-enabled/security2.load,将其移动到php7.load符号链接之前。这个步骤看似微小,却是后续所有规则能否生效的基础。我曾遇到一个棘手问题:规则能成功拦截GET /test.php?id=1' and 1=1--,却对POST /login提交的相同 payload 无动于衷。排查三天后发现,mod_security2.so的加载顺序被a2enmod php7命令意外调整到了php7_module之后,导致SecRequestBodyAccess On失效——因为请求体在到达 ModSecurity 之前,已经被 PHP 模块读取并清空了。修正加载顺序后,问题瞬间解决。这个教训告诉我:在 Apache 的世界里,模块的“出场顺序”,就是它的“权力边界”。
3.2 SecRequestBodyInMemoryLimit 与 SecRequestBodyLimit 的协同配置原理
当 ModSecurity 需要检查 POST 请求的请求体(如表单提交、JSON 数据、文件上传)时,它必须先将数据读入内存或临时文件。SecRequestBodyInMemoryLimit和SecRequestBodyLimit这两个指令共同决定了这个过程的行为,它们的数值关系直接决定了性能与安全的平衡点。
SecRequestBodyLimit设定的是 ModSecurity 接受的最大请求体大小,单位字节。例如SecRequestBodyLimit 13107200表示最大接受 12.5MB 的请求体。超过此值的请求,ModSecurity 会直接返回 413 Request Entity Too Large 错误,根本不会交给后端应用处理。这是一个硬性防护,防止攻击者通过超大 payload 耗尽服务器内存。
SecRequestBodyInMemoryLimit则设定的是 ModSecurity 将请求体保留在内存中的最大字节数。例如SecRequestBodyInMemoryLimit 131072表示最多将 128KB 的请求体放在内存里。如果请求体超过此值,ModSecurity 会自动将其写入磁盘上的临时文件(由SecTmpDir指定),然后再进行规则匹配。这个设计非常巧妙:它保证了小请求(如普通表单)的处理速度(内存操作快),又避免了大请求(如文件上传)耗尽内存(磁盘 I/O 更可控)。
两者的协同关系是:SecRequestBodyInMemoryLimit必须小于或等于SecRequestBodyLimit,且通常应设为后者的 1/100 左右。例如,如果你允许用户上传 100MB 的文件(SecRequestBodyLimit 104857600),那么SecRequestBodyInMemoryLimit设为1048576(1MB)是合理的。这样,100MB 的上传请求会被写入临时文件,ModSecurity 只需读取该文件进行规则扫描,而不会尝试将 100MB 全部加载进内存。我在为某视频平台的封面图上传接口配置时,就将SecRequestBodyLimit设为52428800(50MB),SecRequestBodyInMemoryLimit设为524288(512KB)。测试表明,这个配置下,50MB 的 JPG 上传请求平均耗时增加仅 180ms(主要是磁盘写入时间),而内存占用峰值稳定在 62MB,完全在可接受范围内。但如果错误地将SecRequestBodyInMemoryLimit也设为52428800,那么一次上传就会导致 Apache worker 进程内存飙升至 500MB+,极易触发 OOM Killer。
提示:
SecTmpDir必须指向一个具有充足空间和良好 I/O 性能的目录,并确保 Apache 进程用户(通常是www-data)对该目录有读写权限。我习惯将其设为/var/tmp/modsec,并在/etc/fstab中为其挂载一个独立的、使用noatime,nodiratime选项的 ext4 分区,以最大限度减少元数据更新开销。
3.3 CRS 规则集的最小化启用策略与性能基准测试
OWASP CRS v3.0.2 默认包含约 30 个规则文件,总计超过 3000 条规则。全量启用对 CPU 的消耗是巨大的,尤其是在 Debian 8 这样的老系统上。一次完整的 CRS 规则集扫描,平均会为每个请求增加 8–12ms 的 CPU 时间。对于一个 QPS 为 200 的系统,这意味着每秒额外消耗 1.6–2.4 个 CPU 核心。这不是理论值,而是我在一台 2 核 4G 的 VPS 上用ab -n 10000 -c 200 http://localhost/test.php实测得到的数据。
因此,必须实施“最小化启用”策略:只启用那些与你的业务风险高度相关的规则文件。我的标准流程是三步走:
- 基线启用:首先,只启用
crs-setup.conf(初始化配置)和REQUEST-910-IP-REPUTATION.conf(IP 黑名单)。这是所有规则的基石,开销几乎为零。 - 协议层加固:接着,启用
REQUEST-920-PROTOCOL-ENFORCEMENT.conf(强制 HTTP 方法、头字段格式)和REQUEST-921-PROTOCOL-ANOMALY.conf(检测协议异常,如非法字符、超长头)。这两者构成了第一道“守门员”,能过滤掉 60% 以上的自动化扫描器流量。 - 应用层聚焦:最后,根据你的应用类型,选择性启用。如果是 PHP+MySQL 的 CMS 系统,重点启用
REQUEST-930-APPLICATION-ATTACK-LFI.conf(路径遍历)、REQUEST-942-APPLICATION-ATTACK-SQLI.conf(SQL 注入)、RESPONSE-951-DATA-LEAKAGES-SQL.conf(SQL 错误信息泄露);如果是纯 API 接口,且只接受 JSON,那么REQUEST-933-APPLICATION-ATTACK-PHP.conf(PHP 代码注入)和REQUEST-934-APPLICATION-ATTACK-NODEJS.conf(Node.js 注入)就可以安全禁用。
完成配置后,必须进行严格的性能回归测试。我使用siege工具进行对比:
# 测试未启用 CRS 时的基准性能 siege -b -c 100 -t 30S http://localhost/test.php # 测试启用最小化 CRS 后的性能 siege -b -c 100 -t 30S http://localhost/test.php关注Transactions per second和Response time两个指标。合格的标准是:TPS 下降不超过 5%,响应时间增加不超过 15ms。如果超出,就需要回退到上一步,检查是否有不必要的规则文件被启用了。这个过程不是一蹴而就的,而是一个需要耐心打磨的“调参”过程。记住,WAF 的目标不是拦截 100% 的攻击,而是在可接受的性能代价下,拦截 95% 的已知威胁,并为那 5% 的未知威胁争取足够的响应时间。
3.4 自定义规则编写的核心语法与避坑指南
ModSecurity 的规则语法(SecRule)是其灵魂,但也是新手最容易栽跟头的地方。一条典型的规则SecRule ARGS:username "@rx ^[a-zA-Z0-9_]{3,16}$" "id:1001,rev:1,tag:OWASP_CRS,severity:2,msg:'Invalid username format'"看似简单,实则暗藏玄机。
第一个坑是变量作用域。ARGS:username只匹配 GET 查询参数或 POST 表单参数中名为username的字段。如果你的应用使用 JSON body,如{"username":"admin"},那么ARGS变量是无法捕获它的,必须使用REQUEST_BODY变量,并配合SecRequestBodyAccess On。但REQUEST_BODY是一个原始字符串,需要先用SecRule REQUEST_HEADERS:Content-Type "@contains application/json"判断类型,再用SecRule REQUEST_BODY "@rx \"username\":\"[^\"]{3,16}\""进行匹配。这个过程涉及正则表达式的贪婪匹配和转义,极易出错。
第二个坑是正则引擎的差异。ModSecurity 2.9.x 使用的是 PCRE(Perl Compatible Regular Expressions),它支持\d、\s、(?i)等高级特性,但不支持 JavaScript 风格的g(全局)或m(多行)标志。更重要的是,PCRE 默认是“贪婪”的,.*会尽可能多地匹配。例如,规则@rx "password=.*&"本意是匹配password=123456&,但如果请求体是password=123456&confirm_password=123456&token=abc,它会匹配从第一个password=到最后一个&的整个字符串,导致误报。解决方案是使用“非贪婪”匹配password=[^&]*&。
第三个坑是规则 ID 的唯一性与可追溯性。id:1001是规则的唯一标识,它必须在整个规则集中全局唯一。我建议自定义规则的 ID 从 10000 开始,避免与 CRS 的 9xxx ID 冲突。同时,在msg字段中,务必写明规则的业务上下文,例如"msg:'Login API: Block username with special chars except underscore'",而不是笼统的"msg:'Invalid input'"。这样,当modsec_audit.log中出现告警时,运维人员一眼就能知道这条规则是为哪个接口、防范哪种业务逻辑漏洞而写的,极大提升了故障定位效率。
注意:所有自定义规则文件(如
/etc/modsecurity/custom_rules.conf)必须在 CRS 主配置文件crs-setup.conf之后被Include,否则 CRS 的初始化设置(如SecRequestBodyAccess On)可能尚未生效,导致你的规则无法工作。
4. 实操过程与核心环节实现
4.1 环境准备与依赖安装(Debian 8 / Ubuntu 14.04)
在开始编译前,必须确保系统处于一个干净、一致的状态。Debian 8 和 Ubuntu 14.04 的软件源虽然已归档,但依然可以通过修改/etc/apt/sources.list来访问。对于 Debian 8(jessie),将源替换为 archive.debian.org:
# 备份原配置 cp /etc/apt/sources.list /etc/apt/sources.list.backup # 编辑 sources.list cat > /etc/apt/sources.list << 'EOF' deb http://archive.debian.org/debian jessie main contrib non-free deb http://archive.debian.org/debian-security jessie/updates main contrib non-free EOF # 更新并升级基础系统 apt-get update apt-get -y upgrade对于 Ubuntu 14.04(trusty),则使用 old-releases.ubuntu.com:
cat > /etc/apt/sources.list << 'EOF' deb http://old-releases.ubuntu.com/ubuntu/ trusty main restricted universe multiverse deb http://old-releases.ubuntu.com/ubuntu/ trusty-updates main restricted universe multiverse deb http://old-releases.ubuntu.com/ubuntu/ trusty-security main restricted universe multiverse EOF apt-get update apt-get -y upgrade这一步至关重要。我曾在一个客户现场,因为忘记切换源,apt-get install build-essential命令卡在Connecting to archive.ubuntu.com上长达 20 分钟,最终超时失败。切换到归档源后,整个过程不到 30 秒。
接下来,安装编译所需的全部依赖。ModSecurity 2.9.5 需要libxml2-dev、libpcre3-dev、libcurl4-openssl-dev、liblua5.2-dev(用于 CRS 的 Lua 脚本支持)以及apache2-dev(Apache 的开发头文件):
# Debian 8 apt-get install -y build-essential libxml2-dev libpcre3-dev libcurl4-openssl-dev liblua5.2-dev apache2-dev # Ubuntu 14.04 apt-get install -y build-essential libxml2-dev libpcre3-dev libcurl4-openssl-dev liblua5.2-dev apache2-dev特别注意liblua5.2-dev。CRS v3.0.2 中的REQUEST-912-DOS-PROTECTION.conf文件使用了 Lua 脚本来实现速率限制(rate limiting),如果缺少这个库,编译时会警告Lua support disabled,导致该文件中的所有规则失效。这个警告很容易被忽略,但后果是严重的——你的 WAF 将失去对暴力破解和 CC 攻击的最后一道防线。因此,在./configure步骤后,务必检查输出日志中是否有checking for lua... yes这一行,确认 Lua 支持已启用。
4.2 ModSecurity 2.9.5 源码编译与 Apache 模块安装
从 ModSecurity 官方 GitHub Release 页面下载 2.9.5 源码包(modsecurity-2.9.5.tar.gz),并解压到/tmp目录:
cd /tmp wget https://github.com/SpiderLabs/ModSecurity/releases/download/v2.9.5/modsecurity-2.9.5.tar.gz tar -xzf modsecurity-2.9.5.tar.gz cd modsecurity-2.9.5编译前的配置是成败关键。必须显式指定 Apache 的 apxs 工具路径(用于生成.so模块)和安装目录:
./configure \ --with-apxs=/usr/bin/apxs2 \ --with-pcre=/usr \ --with-libxml=/usr \ --with-lua=/usr \ --prefix=/usr \ --sysconfdir=/etc/modsecurity这里有几个关键点需要解释:
--with-apxs=/usr/bin/apxs2:apxs2是 Apache 2 的扩展工具,负责将 C 代码编译成.so模块。在 Debian/Ubuntu 上,它的路径是/usr/bin/apxs2,而不是/usr/sbin/apxs。--with-pcre=/usr:告诉编译器 PCRE 库的头文件和库文件位于/usr/include和/usr/lib下。如果不指定,configure可能找不到 PCRE,导致modsecurity编译失败。--prefix=/usr:将modsecurity的可执行文件(如modsecctl)安装到/usr/bin,与系统其他工具保持一致。--sysconfdir=/etc/modsecurity:这是最重要的参数,它将 ModSecurity 的主配置文件modsecurity.conf和规则文件目录统一放置在/etc/modsecurity/下,便于集中管理和备份。
执行make && make install。编译过程大约需要 3–5 分钟。完成后,检查模块是否成功生成:
ls -l /usr/lib/apache2/modules/mod_security2.so # 应该能看到一个大小约为 1.2MB 的 .so 文件然后,创建 Apache 的模块加载配置:
echo "LoadModule security2_module /usr/lib/apache2/modules/mod_security2.so" > /etc/apache2/mods-available/security2.load a2enmod security2此时,不要急于重启 Apache。先用apache2ctl configtest检查配置语法是否正确。如果输出Syntax OK,说明模块加载没有问题;如果报错Cannot load /usr/lib/apache2/modules/mod_security2.so into server: ... undefined symbol: ...,那几乎可以肯定是--with-pcre或--with-libxml路径指定错误,需要回到./configure步骤重新检查。
4.3 OWASP CRS v3.0.2 的部署与最小化配置
OWASP CRS 的部署不是简单的git clone。v3.0.2 的发布包是一个经过预编译和验证的 tarball,比直接克隆 master 分支更稳定。从 OWASP 官网下载owasp-modsecurity-crs-3.0.2.tar.gz:
cd /tmp wget https://github.com/coreruleset/coreruleset/releases/download/v3.0.2/owasp-modsecurity-crs-3.0.2.tar.gz tar -xzf owasp-modsecurity-crs-3.0.2.tar.gz sudo cp -r owasp-modsecurity-crs-3.0.2 /etc/modsecurity/crs进入 CRS 目录,进行关键的初始化:
cd /etc/modsecurity/crs # 复制示例配置为实际配置 sudo cp crs-setup.conf.example crs-setup.conf # 创建规则存放目录 sudo mkdir -p /etc/modsecurity/rules # 创建自定义规则目录 sudo mkdir -p /etc/modsecurity/custom-rules编辑/etc/modsecurity/crs/crs-setup.conf,找到SecAction指令块,确保以下几行是取消注释且值正确:
# 启用核心规则集 SecAction \ "id:900100,\ phase:1,\ nolog,\ pass,\ t:none,\ setvar:tx.crs_setup_version=3002" # 设置默认的 SecRuleEngine 状态为 On SecAction \ "id:900110,\ phase:1,\ nolog,\ pass,\ t:none,\ setvar:tx.enforce_bodyproc_urlencoded=1" # 启用审计日志的“相关性”模式 SecAction \ "id:900120,\ phase:1,\ nolog,\ pass,\ t:none,\ setvar:tx.auditlog_relevant_only=1"最关键的一步,是编辑/etc/modsecurity/modsecurity.conf(这是 ModSecurity 的主配置文件)。找到Include指令部分,按顺序添加以下三行:
# 1. 首先加载 CRS 的初始化配置 Include /etc/modsecurity/crs/crs-setup.conf # 2. 然后加载 CRS 的核心规则文件(按最小化策略) Include /etc/modsecurity/crs/rules/REQUEST-910-IP-REPUTATION.conf Include /etc/modsecurity/crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf Include /etc/modsecurity/crs/rules/REQUEST-921-PROTOCOL-ANOMALY.conf Include /etc/modsecurity/crs/rules/REQUEST-930-APPLICATION-ATTACK-LFI.conf Include /etc/modsecurity/crs/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf Include /etc/modsecurity/crs/rules/RESPONSE-951-DATA-LEAKAGES-SQL.conf # 3. 最后加载自定义规则 Include /etc/modsecurity/custom-rules/*.conf这个顺序不能颠倒。crs-setup.conf必须最先加载,因为它设置了所有后续规则依赖的全局变量(如tx.crs_setup_version)。而自定义规则必须最后加载,这样才能覆盖 CRS 中的默认行为(例如,用SecRuleRemoveById禁用某个 CRS 规则)。
