WAF绕过实战:5种SQL注入技巧与靶场演示

WAF绕过实战:5种SQL注入技巧与靶场演示

1. 项目概述:当SQL注入遇上WAF这道墙

在渗透测试和CISP-PTE这类认证考试中,SQL注入是绕不开的经典课题。但如今,一个光秃秃的‘ or 1=1--扔出去,十有八九会撞上Web应用防火墙(WAF)这堵墙,返回一个冷冰冰的“403 Forbidden”或是“您的请求疑似攻击行为”。很多新手,甚至一些有经验的朋友,在遇到WAF时容易懵,觉得无从下手。其实,WAF并非铜墙铁壁,它更像一个有着固定检查流程的安检门。这个项目的核心,就是拆解这扇“安检门”的检查逻辑,并分享五种在实战和备考中高频使用的绕过技巧。我们会结合像DVWA、Pikachu、SQLi-Labs这样的经典靶场进行演示,让你不仅知道“怎么做”,更理解“为什么能这么做”。无论你是正在备考CISP-PTE,还是想深化Web安全实战能力,这些绕过WAF的思路和手法,都是必须掌握的硬核技能。

2. 核心思路:理解WAF的“安检规则”是绕过的第一步

在尝试任何绕过技巧之前,我们必须先建立起一个核心认知:WAF的拦截是基于规则(Rule)的。它不是人工智能,不会理解你请求的“意图”,它只是机械地将你的输入与一系列已知的攻击特征(正则表达式、字符串匹配、语法分析等)进行比对。我们的所有绕过行为,本质上都是在“欺骗”这套规则引擎。

2.1 WAF的常见检测维度

WAF的规则库通常从以下几个维度进行检测:

  1. 关键字/模式匹配:这是最基础的一层。WAF会维护一个庞大的黑名单,包含union select,sleep(,benchmark(,extractvalue(,updatexml(等敏感SQL关键字和函数。一旦你的请求中出现这些字符串,就可能被拦截。
  2. 语法/词法分析:更高级的WAF会尝试解析SQL语句的语法结构。例如,它会检查UNION前后查询的列数是否一致,或者SELECT后面是否跟着FROM等,来判断这是否是一条“结构完整”的恶意SQL。
  3. 请求参数标准化:为了对抗简单的编码绕过,WAF会对接收到的参数进行解码和标准化处理。例如,将%55NION(U的URL编码)还原为UNION后再进行规则匹配。
  4. 频率与行为异常:连续发送大量相似的攻击载荷,或短时间内触发大量错误,可能会被WAF的异常行为模型判定为攻击,从而暂时封锁IP。

2.2 绕过的基本哲学:变形与混淆

基于上述检测原理,我们的绕过思路可以归结为两个核心:变形混淆

  • 变形:改变攻击载荷的“样子”,使其不匹配WAF的规则,但被后端数据库解析时,又能恢复原意。例如,将UNION SELECT写成U/**/NION/**/SEL/**/ECT
  • 混淆:利用WAF与后端应用/数据库解析差异,制造“理解偏差”。WAF可能用一种方式解析你的输入,而数据库用了另一种,这就产生了绕过空间。

理解了这个基础框架,我们就能系统地学习下面的五种技巧,而不是死记硬背几个Payload。

3. 五种实战绕过技巧详解与靶场演示

我们将使用**DVWA(Damn Vulnerable Web Application)**作为主要演示环境,将其安全级别设置为“Low”以模拟存在漏洞但无强过滤的场景,然后在“Medium”或“High”级别,或配合简单WAF规则,来演示绕过。同时会提及这些技巧在Pikachu、SQLi-Labs等靶场中的适用场景。

重要前提:所有测试务必在授权靶场或个人实验环境中进行。未经授权的测试是违法行为。

3.1 技巧一:注释符与空白符混淆

这是最基础、最常用的一种绕过方式,主要针对基于关键字字符串匹配的WAF。

原理:在SQL语句中,注释(/**/,--,#)和某些空白符(如换行符%0a、制表符%09)在语法解析时通常被忽略。但WAF的规则可能只匹配连续的关键字字符串。我们通过在关键字中间插入这些“无害”字符,来打断WAF的匹配。

实战Payload与演示

假设DVWA的SQL注入点(id参数)存在基础关键字过滤,拦截union select

  • 原始攻击语句1‘ union select 1,2--
  • 绕过Payload1‘ uni/**/on sel/**/ect 1,2--
  • 1‘ uni%0aon%0aselect 1,2--(利用URL编码的换行符)
  • 1‘ uNiOn%0aSeLeCt 1,2--(混合大小写,部分简单WAF规则可能大小写敏感)

在靶场中的操作

  1. 在DVWA的SQL Injection页面,输入1‘进行基础测试,确认存在注入。
  2. 尝试输入1‘ union select 1,2--,如果被拦截(可能页面无响应或返回错误)。
  3. 改用1‘ uni/**/on sel/**/ect 1,2--提交,观察是否能正常返回结果,成功显示出12的位置。

注意事项与心得

  • /**/(多行注释)在MySQL中通常比--#更可靠,尤其是在URL中,#常被当作锚点处理。
  • 插入的位置有讲究。不要破坏SQL语法,例如un/**/ion是可行的,但uni/**/on可能更好,因为union是一个完整单词,拆开它更能绕过针对完整单词的匹配。
  • 这是一个“试探性”技巧。通过不断变换插入注释符的位置和数量,可以像“敲门”一样探测出WAF具体拦截的是哪个子串。

3.2 技巧二:等价函数与语句替换

当某些关键函数(如sleep(),substring())被直接封杀时,我们可以寻找功能相同的“替身”。

原理:WAF的规则是具体的,它可能拦截benchmark(1000000,md5(‘test‘)),但未必拦截具有类似延时效果的pg_sleep(5)(PostgreSQL)或WAITFOR DELAY ‘0:0:5‘(MSSQL)。同样,信息获取的函数也有多种选择。

实战Payload与演示

时间盲注为例,需要延时函数。

  • MySQL环境
    • 被拦截:1‘ and sleep(5)--
    • 绕过方案1(使用BENCHMARK):1‘ and benchmark(10000000,md5(‘test‘))--(注意:benchmark次数需要足够大才能产生明显延迟)
    • 绕过方案2(使用笛卡尔积延时):1‘ and (select count(*) from information_schema.columns a, information_schema.columns b, information_schema.columns c)>0--(通过大量连接查询制造延迟)
  • 字符串截取/比较
    • 被拦截:substring(database(),1,1)=‘d‘
    • 绕过方案:mid(database(),1,1)=‘d‘left(database(),1)=‘d‘

在靶场中的操作: 在SQLi-Labs或Pikachu的时间盲注关卡,如果发现sleep()被过滤,可以尝试将注入脚本中的sleep(5)替换为benchmark(10000000,md5(‘a‘)),观察响应时间是否显著增加,从而判断注入是否成功。

注意事项与心得

  • 数据库类型是关键。你必须清楚目标是什么数据库(MySQL, MSSQL, PostgreSQL, Oracle),因为等价函数在不同DBMS中差异巨大。信息收集阶段通过报错、端口扫描等手段判断数据库类型是首要任务。
  • benchmark()的延时效果取决于硬件性能,在本地虚拟机可能很明显,在高性能服务器上可能不明显,需要调整循环次数。
  • 笛卡尔积延时是一种“暴力”但有效的替代方案,尤其在sleepbenchmark都被封杀时。但要注意可能对数据库造成较大压力。

3.3 技巧三:参数污染与多重编码

这是利用HTTP请求处理链中各个环节解析差异的经典方法。

原理:一个请求参数可能经过WAF、Web服务器(如Apache/Nginx)、应用框架(如PHP、Java Spring)、最终SQL查询多个环节。每个环节对参数的解析、解码规则可能不同。我们通过构造“畸形”的参数,让WAF看到的是一种“无害”的内容,而应用后端解析后得到的却是攻击载荷。

实战Payload与演示

  1. HPP(HTTP参数污染)

    • 原始请求:/vuln.php?id=1‘ union select 1,2--
    • 绕过请求:/vuln.php?id=1‘ /*&id=*/ union select 1,2--
    • 原理:某些环境下(如PHP+Apache),当收到多个同名参数id时,可能会取最后一个值(... union select 1,2--),而WAF可能只检查第一个id的值(1‘ /*),后者看起来像是一个未闭合的字符串加注释,可能被规则放过。
  2. 多重URL编码

    • 原始载荷:union select
    • 一次编码:%75%6e%69%6f%6e%20%73%65%6c%65%63%74
    • 二次编码(对%本身编码):%2575%256e%2569%256f%256e%2520%2573%2565%256c%2565%2563%2574
    • 原理:如果WAF只做一次URL解码,它看到的是%75%6e...这一串“乱码”,可能不匹配union select规则。但应用服务器(如Tomcat)可能会自动进行二次解码,最终还原出union select

在靶场中的操作: 在Burp Suite中捕获一个存在注入的请求,例如GET /dvwa/vulnerabilities/sqli/?id=1&Submit=Submit

  • 对于HPP,将请求修改为:GET /dvwa/vulnerabilities/sqli/?id=1‘ /*&id=*/ union select 1,2-- &Submit=Submit
  • 对于双重编码,使用Burp的Ctrl+U快捷键对参数值进行快速编解码。先将union select编码一次,得到%75%6e%69%6f%6e%20%73%65%6c%65%63%74,再对这个结果里的每一个%字符编码为%25,形成最终Payload。

注意事项与心得

  • 这种方法成功率高度依赖于目标栈的具体实现。它没有通用性,但一旦成功,往往能绕过很强大的WAF。
  • 在测试时,配合Burp Suite的Intruder模块,使用预定义的“Fuzzing - 多重编码”字典进行爆破,是高效的探测方式。
  • 时刻关注应用的报错信息。有时编码错误会导致语法错误,从报错信息中可以反推服务器是如何解析你的输入的。

3.4 技巧四:分块传输与协议层绕过

这是一种更高级的技巧,针对的是WAF在HTTP协议层面的处理逻辑。

原理:一些WAF为了性能,可能不会完整解析Transfer-Encoding: chunked这种分块传输的请求体,或者对GET/POST请求的检查策略不同。我们可以通过改变HTTP请求方法、使用分块编码来“绕过”WAF的检测点。

实战Payload与演示

  1. GET转POST

    • 场景:一个注入点在GET参数中,但WAF对GET请求检查严格,对POST请求体检查宽松。
    • 操作:用Burp将原本的GET /vuln.php?id=1‘ and 1=1--改为POST /vuln.php,并在Body中添加id=1‘ and 1=1--
  2. 分块传输编码(Chunked Transfer-Encoding)

    • 这是更高级的技巧。在Burp Repeater中,可以在请求头中添加Transfer-Encoding: chunked,然后将请求体用分块格式发送。WAF可能因为不支持或不完整处理分块数据而无法有效检测载荷。
    • 一个简化的分块示例
      POST /target.php HTTP/1.1 Host: test.com Transfer-Encoding: chunked 6 //第一个块的长度(十六进制) id=1‘ //第一个块的内容 8 //第二个块的长度 and 1=1 //第二个块的内容 0 //长度0表示结束
    • 原理:WAF可能将每个分块单独做检测,id=1‘and 1=1分开看都不构成威胁,但后端服务器会将其拼接成完整的id=1‘ and 1=1

在靶场中的操作: 这通常需要靶场环境具备接收POST请求的注入点。你可以在DVWA的“SQL Injection (Blind)”关卡进行测试,该关卡通常使用POST方式。用Burp抓取请求后,尝试在Repeater中修改或添加Transfer-Encoding: chunked头,并使用扩展(如“Burp Suite Chunked Encoding Converter”)来方便地生成分块数据。

注意事项与心得

  • 分块传输绕过对WAF的要求较高,现代云WAF(如阿里云WAF、腾讯云WAF)大多能很好地处理分块请求。但在一些老旧或自定义的WAF上可能仍有奇效。
  • 这种方法可能改变应用处理请求的方式,导致正常功能出错。测试时需要仔细比对正常请求与分块请求的响应差异。
  • 这是CISP-PTE等考试中可能涉及的高级考点,理解其原理比记忆Payload更重要。

3.5 技巧五:利用数据库特性与非常规语法

每种数据库都有其独特的语法和特性,这些特性可能成为绕过WAF的“秘密通道”。

原理:WAF的通用规则集可能无法覆盖所有数据库特有的、晦涩的语法。利用这些特性,我们可以构造出“合法”但对WAF来说很“陌生”的SQL语句。

实战Payload与演示

  1. MySQL注释符的另类用法

    • /*!50000union*/ select/*!union*/ select
    • 原理:在MySQL中,/*! ... */是一种特殊注释,其中的内容在MySQL版本号大于或等于指定值(这里是50000,即5.00.00)时,会被当作SQL执行。这可以用来“隐藏”关键字。许多WAF的规则可能不会深入解析这种条件注释内部的内容。
  2. 内联注释与版本特性

    • /*!union*//*!select*/ 1,2
    • 甚至更混淆的:U/*!00000nion*/ SE/*!00000lect*/ 1,2
  3. 利用字符串连接与十六进制编码

    • 假设select被过滤,我们可以用:
      • concat(‘sel‘,‘ect‘)->‘sel‘ ‘ect‘(在某些上下文)
      • 或者使用十六进制:0x73656c656374就是select的十六进制。Payload如:union 0x73656c656374 1,2。但注意,这通常用于代替字符串字面量,而不是关键字本身。更常见的用法是将要查询的数据转换为十六进制,避免引号被过滤:union select column_name from information_schema.columns where table_name=0x7573657273(0x7573657273 = ‘users‘)。

在靶场中的操作: 在Pikachu靶场的SQL注入关卡中,尝试各种过滤。当发现union select被直接拦截时,可以尝试输入:1‘ /*!50000union*/ /*!50000select*/ 1,2,3--观察是否能够绕过过滤并正常显示数据。

注意事项与心得

  • 这种方法需要对特定数据库的语法有深入了解。在不确定数据库类型时盲目尝试,效率很低。
  • 十六进制编码在绕过“引号”过滤时极其有用,是必会技巧。当注入点处引号被转义或过滤时,将表名、列名转换为十六进制,往往能一击即中。
  • 条件注释/*! ... */在MySQL中非常强大,也是很多SQL注入工具(如sqlmap)的tamper脚本(如versionedmorekeywords.py)会采用的技术。

4. 综合实战:在模拟WAF环境中进行系统化测试

掌握了单个技巧后,我们需要在更接近真实的环境中进行综合演练。我们可以通过配置Web服务器(如Nginx)的简单规则,或使用ModSecurity等开源WAF,来模拟一个具有基础防护的环境。

4.1 搭建一个简单的测试环境

以Nginx + Lua为例(需要安装ngx_http_lua_module),我们可以写一个简单的WAF规则,拦截包含union select的请求:

location /vuln { access_by_lua_block { local args = ngx.req.get_uri_args() for key, val in pairs(args) do if type(val) == "string" and ngx.re.match(val, "union[\\s\\/\\*]+select", "i") then ngx.exit(403) -- 返回403禁止访问 end end } proxy_pass http://your_dvwa_backend; # 代理到后端的DVWA }

这个规则会检查URL参数中是否出现unionselect(中间允许有空格、注释符/*),不区分大小写。

4.2 系统化绕过测试流程

面对这样一个WAF,我们可以按照以下步骤进行测试:

  1. 信息收集与规则探测

    • 发送id=1‘,看是否返回SQL错误。确认注入点。
    • 发送id=1‘ union select 1,2--,确认被WAF拦截(返回403)。
    • 发送id=1‘ uni on sel ect 1,2--(中间加多个空格),看是否拦截。如果不拦截,说明规则是匹配连续字符串union select,对空格不敏感或规则写得不好。
    • 发送id=1‘ uni/**/on sel/**/ect 1,2--,测试注释符绕过。如果成功,说明WAF没有对输入进行规范化处理(去除注释)。
  2. 组合技巧,构造最终Payload

    • 假设注释符绕过成功,但WAF升级了规则,也过滤了/**/。我们可以尝试:
      • 大小写混合uNiOn SeLeCt
      • 等价替换:如果只是select被过滤,尝试用concat或子查询代替部分功能?不,这里主要是关键字绕过。可以尝试/*!50000select*/
      • 参数污染id=1‘ /*&id=*/union select 1,2--
    • 将最有效的几种方法组合:id=1‘ /*&id=*/ /*!50000uNiOn*/ /*!50000SeLeCt*/ 1,2--
  3. 自动化工具辅助

    • 在真实渗透测试中,可以使用sqlmap--tamper参数来自动化尝试绕过。例如:sqlmap -u "http://target.com/vuln.php?id=1" --tamper=space2comment,randomcase
      • space2comment.py:将空格替换为/**/
      • randomcase.py:随机大小写
    • 但切记:在CISP-PTE考试或某些CTF比赛中,可能需要手工构造,工具只是辅助思路。

4.3 常见问题与排查实录

在实际绕过过程中,你肯定会遇到各种意外情况。下面是一些典型问题及排查思路:

问题现象可能原因排查思路与解决方案
Payload明明在本地测试成功,到目标上却返回500错误1. 目标数据库类型不同。
2. 目标PHP/Java配置(如magic_quotes_gpc)开启了特殊字符转义。
3. 中间件(如Nginx)有额外的过滤或长度限制。
1. 重新确认数据库类型(通过报错、端口、默认页面)。
2. 尝试使用十六进制编码代替所有引号内的字符串。
3. 简化Payload,分步测试,确定是哪个部分触发了500错误。
输入任何带有单引号的字符都被拦截WAF可能有非常严格的非法字符过滤策略,直接拦截了单引号、双引号、反斜杠等。1. 尝试无引号注入:利用数字型注入点,或通过ord()ascii()函数逐字符比较,避免使用引号。
2. 尝试宽字节注入(如果数据库是MySQL且使用GBK等宽字符集):输入%df‘%df与转义符\%5c)结合可能形成合法汉字,从而吃掉转义符。
使用/**/注释符后,SQL语句执行错误1. 注释符位置破坏了SQL语法(如order/**/by是OK的,但ord/**/er by可能出错)。
2. 目标数据库不支持/**/注释(极少数情况)。
1. 使用Burp的Intruder,对关键字进行位置迭代,系统地测试在哪个位置插入注释符既能绕过WAF又不破坏语法。
2. 尝试使用--(注意后面有空格)或#进行注释。
时间盲注Payload执行了,但没有观察到明显延迟1.benchmark()或笛卡尔积产生的延迟不够。
2. 网络波动掩盖了延迟。
3. 应用设置了脚本超时时间,长时间查询被中断。
1. 显著增加benchmark的次数,或增加笛卡尔积的表数量。
2. 使用差分时间判断:对比id=1‘ and if(1=1,sleep(5),0)id=1‘ and if(1=2,sleep(5),0)的响应时间差。如果两者时间都长,可能是网络问题;如果只有前者长,说明注入成功。
3. 尝试使用更短的延时(如sleep(2))多次请求取平均。

我个人在实际操作中的体会是:绕过WAF没有“银弹”。它更像是一场与规则工程师的博弈。最有效的方法是系统性的探测灵活的组合。从最简单的注释符开始,逐步尝试大小写、编码、等价替换,并仔细观察每一次请求与响应的差异。每一次“被拦截”和“成功绕过”,都在告诉你WAF规则的一点点秘密。把这些信息拼凑起来,你就能找到那条穿墙而过的缝隙。在CISP-PTE的备考中,除了练习这些技巧,更重要的是培养这种分析、试探、验证的思维流程,这才是应对万变题目的核心能力。