广告位 · 页头(后台-模块管理-新建模块后替换此区域)

如何配置WAF规则用正则表达式精准拦截高危SQL注入关键字?

WAF拦截SQL注入,难点不在把关键字列全,而在于让正则表达式只在攻击语境里生效。select、union、from这些词既可能出现在恶意参数里,也可能只是用户在搜索框输入的普通英文。若规则写成select|union|from,误报率会高到无法上线。因此需要从边界、动作、上下文三个维度设计过滤规则。

如何配置WAF规则用正则表达式精准拦截高危SQL注入关键字?

单纯关键字匹配为什么不够可靠

SQL注入检测通常会先列出一批高危词,比如select、union、sleep、information_schema。如果直接用select|union这样的正子去匹配整个请求参数,确实能抓住最简单的注入尝试,但也会把普通查询参数?q=from、搜索内容union all直接拦截。原因在于没有考虑词边界:SQL关键字只有在独立成词时才具有攻击语义。

先看一个常见边界写法:\bselect\b。它要求select两侧不是单词字符,能过滤掉selection或selectAll这类误报。但在WAF处理URL参数时,下划线属于单词字符,所以information_schema不会匹配\binformation_schema\b,因为两侧可能紧邻下划线或其他字符。这时需要自定义边界条件,比如使用[^a-z0-9_]来表示非字母数字下划线字符,再结合行首行尾锚定。

另一个问题是SQL语法允许大小写混写,SeLeCt和select在数据库层等价。若不开启正则的不区分大小写标志,规则会大量漏报。类似的,攻击者还能用MySQL内联注释把关键字拆开,例如sel/**/ect,单纯的关键字列表完全无法匹配。因此拦截规则需要在关键字匹配之前先做一次规范化处理,或者使用能容忍注释符与空白符的正则模式。

ModSecurity规则配置实战

ModSecurity的SecRule指令非常适合做这种基于正则的请求过滤。它的ARGS变量代表所有请求参数(查询字符串和POST表单),配合@rx操作符即可对参数值执行正则匹配。下面是一条拦截高危SQL关键字的规则示例。

SecRule ARGS "@rx (?i)(\bselect\b|\bunion\b|\binformation_schema\b|\bsleep\s*\(|\bbenchmark\s*\()" \
  "id:100001,phase:2,deny,status:403,msg:'SQL Injection keyword detected'"

这条规则的含义是:对请求参数值应用正则,(?i)开启不区分大小写,\bselect\b与\bunion\b匹配独立单词,\bsleep\s*\(匹配sleep (或sleep(,\bbenchmark\s*\(同理。命中后动作是拒绝请求并返回403。ModSecurity的phase:2表示在请求体读取后执行,适合检查POST参数。

实际部署时还可以扩展变量范围。注入经常发生在Cookie、请求头或参数名中,可以增加ARGS_NAMES、REQUEST_HEADERS、REQUEST_COOKIES等变量。也可以使用链式规则先判断参数是否包含SQL特征,再进行后续检测,这样能减少单条复杂正则带来的性能压力。规则中的msg字段建议写成可读的标记,方便日志平台统计误报和漏报。

Nginx场景下的轻量拦截与误报控制

如果站点入口是Nginx,且暂时没有接入完整WAF,可以用if指令对请求参数做正则匹配。虽然Nginx官方不建议在location中滥用if,但针对特定路径或特定参数做拦截,权重可以接受。

location / {
    if ($args ~* "(select|union|information_schema|sleep\s*\(|benchmark\s*\()") {
        return 403;
    }
}

这里的$args变量包含完整查询字符串,~*表示不区分大小写的正则匹配。与ModSecurity方案相比,它只能覆盖查询参数,无法直接检查POST请求体。要处理POST内容,需要引入lua-nginx-module或在access_by_lua_block中读取请求体,或者配合OpenResty的WAF模块实现更完整的检测。

误报控制是Nginx轻量拦截能不能上线的关键。建议不要对整个站点全局启用,而是只对登录、搜索、评论等输入入口启用。可以为特定参数名增加白名单,例如参数q允许出现from,但需限制长度和字符集。也可以根据响应状态码和日志定期回放请求,把误报URL加入例外location。核心思路是先小范围观察,再逐步扩大拦截面。

常见绕过变体与规则优化

攻击者不会只用select这种原样字符串。MySQL的/**/注释、URL编码、换行符都能把关键字拆散。例如union/**/select在数据库里等价于union select,但基础正则只匹配连续关键字。更隐蔽的还有双重URL编码,%2533elect一层解码后是%33elect,再解码才得到select。WAF在进入正则前必须确保参数已经完成标准化解码。

一条能容忍注释符的正则可以写成下面这样:

(?i)(select|union|sleep)\s*\/\*.*?\*\/|\b(select|union|sleep)\b

它先匹配带内联注释的关键字,再匹配普通单词。但这类正则会明显增加回溯和性能消耗,建议对参数长度做限制,比如只对长度小于200的单个参数值应用复杂正则,超过长度的参数直接按策略放行或转给后端校验。另一个思路是先移除参数中的注释符、空白符和重复编码,再交给简化正则判断,这样能降低维护成本。

上线前应准备一批正反测试用例。正向用例包括?id=1 union select user、?id=1 and sleep(5)、?id=1 SeLeCt/**/1;反向用例包括?q=from、?note=union all、?name=information。只有在这些用例上都能按预期放行或拦截,规则才算达到可上线状态。若出现大量误报,优先收紧边界和参数范围,而不是简单删掉关键字。

广告位 · 文章底部(后台-模块管理替换此区域)