ModSecurity+Apache老旧系统WAF加固实战指南 1. 项目概述为什么今天还要折腾 ModSecurity Apache 这套“老组合”ModSecurity 是 Web 应用防火墙WAF领域里真正扛过十年以上生产压力的“老兵”不是那种靠营销话术撑起来的轻量级插件。它不像某些云 WAF 那样点几下鼠标就开箱即用但正因如此它在 Ubuntu 14.04 和 Debian 8 这类已进入 EOLEnd-of-Life但仍在大量老旧业务系统中稳定运行的操作系统上依然具备不可替代的价值——不是因为它多新潮而是因为它足够“可控”、足够“透明”、足够“可审计”。我经手过的三个典型场景至今记忆犹新一家本地政务服务平台后端是 PHPMySQL 架构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.xOWASP Core Rule SetCRS是 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/RCE94 是 SQLi95 是 XSS951 是数据泄露。例如若你的业务接口需要接收包含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_id123如果 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?id1 and 11--却对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设为10485761MB是合理的。这样100MB 的上传请求会被写入临时文件ModSecurity 只需读取该文件进行规则扫描而不会尝试将 100MB 全部加载进内存。我在为某视频平台的封面图上传接口配置时就将SecRequestBodyLimit设为5242880050MBSecRequestBodyInMemoryLimit设为524288512KB。测试表明这个配置下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.confIP 黑名单。这是所有规则的基石开销几乎为零。协议层加固接着启用REQUEST-920-PROTOCOL-ENFORCEMENT.conf强制 HTTP 方法、头字段格式和REQUEST-921-PROTOCOL-ANOMALY.conf检测协议异常如非法字符、超长头。这两者构成了第一道“守门员”能过滤掉 60% 以上的自动化扫描器流量。应用层聚焦最后根据你的应用类型选择性启用。如果是 PHPMySQL 的 CMS 系统重点启用REQUEST-930-APPLICATION-ATTACK-LFI.conf路径遍历、REQUEST-942-APPLICATION-ATTACK-SQLI.confSQL 注入、RESPONSE-951-DATA-LEAKAGES-SQL.confSQL 错误信息泄露如果是纯 API 接口且只接受 JSON那么REQUEST-933-APPLICATION-ATTACK-PHP.confPHP 代码注入和REQUEST-934-APPLICATION-ATTACK-NODEJS.confNode.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 使用的是 PCREPerl Compatible Regular Expressions它支持\d、\s、(?i)等高级特性但不支持 JavaScript 风格的g全局或m多行标志。更重要的是PCRE 默认是“贪婪”的.*会尽可能多地匹配。例如规则rx password.*本意是匹配password123456但如果请求体是password123456confirm_password123456tokenabc它会匹配从第一个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 8jessie将源替换为 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.04trusty则使用 old-releases.ubuntu.comcat /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-devApache 的开发头文件# 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/apxs2apxs2是 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.gzcd /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_version3002 # 设置默认的 SecRuleEngine 状态为 On SecAction \ id:900110,\ phase:1,\ nolog,\ pass,\ t:none,\ setvar:tx.enforce_bodyproc_urlencoded1 # 启用审计日志的“相关性”模式 SecAction \ id:900120,\ phase:1,\ nolog,\ pass,\ t:none,\ setvar:tx.auditlog_relevant_only1最关键的一步是编辑/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 规则。4.4 审计日志audit log的精细化配置与轮转管理