
1. 项目概述当SQL注入遇上WAF的“铜墙铁壁”在Web安全测试的实战中SQL注入始终是绕不开的核心课题。然而随着安全意识的提升Web应用防火墙WAF已经成为绝大多数线上系统的标配。它就像一道自动化的“安检门”专门拦截那些携带了select、union、or、and等敏感关键词的恶意请求。很多刚入门的朋友一看到自己的注入语句被WAF无情地返回“403 Forbidden”或者“Blocked”可能就懵了觉得这条路被彻底堵死了。但实际情况是WAF的规则是人写的是静态的而人的思维是动态的、灵活的。今天我们就来深入聊聊当select和union这两个最核心的SQL注入关键词被WAF重点关照时我们如何像特工一样巧妙地“化装”和“渗透”完成绕过。这不仅仅是几个技巧的罗列更是一种对抗思维的训练。你需要理解WAF的检测原理才能找到它的盲点。比如WAF通常基于正则表达式或关键字列表进行匹配那么我们的核心思路就是在不改变SQL语句最终执行逻辑的前提下改变其字符串的呈现形式让WAF认不出来但数据库却能正常解析。这个过程充满了挑战和乐趣也是衡量一个安全测试人员技术深度的重要标尺。无论你是负责企业安全建设的蓝队还是进行授权测试的渗透测试人员掌握这些绕过技术都能帮助你更全面地评估系统的真实风险。2. 核心绕过原理与WAF检测机制拆解在开始具体技巧之前我们必须先搞清楚“对手”是如何工作的。WAF对SQL注入的检测主流机制可以归纳为以下几点理解了这些我们的绕过才能有的放矢。2.1 WAF检测的常见层次基于正则表达式的模式匹配这是最基础、最广泛的方式。WAF维护着一套或几套正则规则集用来匹配请求参数中的字符串。例如一个简单的规则可能是/(union\sselect)/i用于匹配不区分大小写的“union select”。我们的绕过很大程度上就是在和这些正则表达式“斗智斗勇”。基于词法/语法分析的深度解析一些高级WAF或云WAF服务如Cloudflare、AWS WAF的某些托管规则会尝试对输入进行“标准化”或“解析”。它们可能会先对URL进行解码去除多余的空白和注释甚至尝试理解参数的结构然后再与规则库匹配。这增加了绕过的难度但并非无懈可击。行为分析与机器学习较新的WAF会结合请求频率、参数长度、异常错误信息返回等行为特征进行综合判断。例如短时间内大量提交包含相似畸形Payload的请求即使单个请求可能绕过了关键字检测也可能触发频率警报。这类绕过需要更精细的节奏控制。2.2 我们的核心对抗策略针对上述机制我们的绕过策略可以总结为以下几个方向混淆与变形让关键字“看起来”不像关键字。这是最直接的方法包括大小写变换、插入特殊字符、编码、等价替换等。注释分割利用SQL注释符/**/,--,#将关键字拆散打断WAF的正则匹配链条。利用数据库特性不同数据库MySQL、MSSQL、PostgreSQL等有独特的语法和特性这些特性可能被WAF规则所忽略。例如MySQL中的内联注释/*!50001select*/。多层级编码与HTTP参数污染对Payload进行多次编码如双重URL编码或者利用服务器与WAF解析参数的差异HPP制造解析歧义。重要提示所有讨论的技术均应在合法授权的测试环境中进行。未经授权的测试是违法行为。本文目的旨在提升安全防御意识从攻击者视角理解漏洞从而构建更坚固的防御体系。3. Select关键字的绕过技巧大全select是数据查询的基石被过滤后我们几乎无法直接读取数据。以下是经过实战检验的多种绕过方法。3.1 基础混淆大小写与空白符这是最初级的尝试对付简单的字符串匹配有效。大小写绕过SeLeCt、SELECT、Select。有些WAF规则可能只匹配全小写select。内联注释绕过在MySQL中/*!select*/会被正确执行。更妙的是可以加入版本号如/*!50001select*/表示在MySQL 5.00.01及以上版本中才执行其中的内容这本身也是一种混淆。空白符替换利用/**/MySQL注释符但在此处被当作空白符、%0a换行符、%0d回车符、%09制表符等来分隔关键字。示例sel%0aect、sel/**/ect。原理WAF的正则可能只匹配select这个连续字符串插入空白符后匹配失败但数据库的SQL解析器会忽略这些空白符。3.2 高级分割利用注释与拼接当简单的空白符被加入WAF的过滤列表后我们需要更巧妙的“分而治之”。注释符分割将select拆成多个部分用注释符隔开。示例sel/*任意内容*/ect。只要注释符内的内容不被过滤这个Payload就能到达数据库并被还原为select。字符串拼接这在一些支持字符串拼接的数据库场景中非常有效。MySQL示例concat(sel,ect)或sel ect。在SQL语句中你可以用(concat(sel,ect))来代替select关键字的位置吗不直接但可以用于构造查询。更常见的用法是在from子句中拼接表名或在where子句中拼接条件。对于select关键字本身通常需要结合预编译语句或二次注入的上下文但在一些Hack技巧中可以通过prepare和execute动态执行拼接的SQL字符串但这属于更高阶的利用。更实用的场景绕过from后的表名过滤。例如select * from users可以尝试变为select * from (concat(use,rs))但需注意语法正确性通常需要别名select * from (select 1)a join (select 2)b这类方式。3.3 编码与双重编码这是利用WAF解码层和应用程序解码层不一致的经典方法。URL编码select的URL编码是%73%65%6c%65%63%74。有些WAF只做一次解码如果你提交的是编码后的形式它可能匹配不到select这个明文。但应用程序收到后可能会自动解码一次最终数据库执行的是解码后的语句。双重URL编码这是更有效的技巧。s的双重URL编码是%2573%73的URL编码。你提交%2573%2565%256c%2565%2563%2574。可能发生的场景WAF解码第一次看到的是%73%65%6c%65%63%74即select的编码如果它在这一层进行关键字匹配就会被拦截。但有些WAF可能不做这层解码匹配或者匹配规则有遗漏。而应用程序服务器如Apache/PHP、IIS/ASP.NET通常会进行两次解码一次Web容器一次应用自身最终还原出select。实战要点双重编码对GET请求的参数部分非常有效。在Burp Suite的Repeater模块中你可以使用CtrlU进行快速URL编码手动构造双重编码则需要一些耐心。3.4 非常规等价替换在某些特定的注入点和数据库配置下存在一些“曲线救国”的方法。使用handler语句MySQLMySQL提供了一个HANDLER语句它可以逐行读取表数据是select的一个替代品但极少被WAF规则覆盖。语法HANDLER table_name OPEN; HANDLER table_name READ FIRST; ... HANDLER table_name CLOSE;限制需要知道表名且用法繁琐不适合批量数据提取但在极端情况下是一个突破口。利用xml相关函数进行数据外带如果目标数据库支持XML函数如MySQL的extractvalue()、updatexml()虽然它们主要用于报错注入但其报错信息可以携带查询结果。这本质上还是需要执行一个“查询”但可能绕开对select的过滤因为Payload里没有select只有这些函数和concat等。例如and updatexml(1, concat(0x7e, (subquery)), 1)这里的subquery需要返回数据通常还是需要select除非你能用其他方式构造数据。4. Union关键字的绕过技巧大全union用于联合查询是注入后获取数据的主要手段之一。过滤了unionunion select这种经典组合就失效了。4.1 基础混淆同上但需注意组合适用于union的技巧与select类似但经常需要组合使用。大小写/内联注释/空白符UnIoN、/*!union*/、uni%0aon。组合union select绕过当两者都被过滤时可以分别对它们进行混淆。示例uni%0aon/*!50001select*/、U/*!/**/nIoN*/ sel%0bect。这里的%0b是垂直制表符也是一个不常见的空白符。4.2 注释符高级利用拆散整个查询这是绕过“union select”整体匹配的强有力手段。将union和select完全拆到注释两端这是我最喜欢用的技巧之一成功率很高。示例uni/**/on sel/**/ect是最基础的。高级示例un/*任意干扰字符串比如aaaaa*/ion s/*bbbbb*/elect。WAF的正则如果试图匹配union和select之间的模式比如\s会被中间的注释内容彻底打乱。利用注释包裹整个select列表在某些情况下你可以这样构造union select 1,2,3-union/*!select 1,2,3*/。 这看起来有点怪但在MySQL中如果/*! */中的内容符合语法它会被执行。这相当于把select 1,2,3整体作为一个“令牌”藏了起来。4.3 编码与特殊字符插入URL编码union-%75%6e%69%6f%6e。插入无关特殊字符在某些数据库的语法中一些字符会被忽略。例如在MySQL中符号可用于变量但放在关键字中有时会被忽略并非总是有效需测试。更可靠的是利用号在某些语境下作为连接符或正号但可能引起语法错误需谨慎。4.4 不使用Union的替代方案如果union被过滤得死死的我们就要考虑其他注入类型这通常意味着漏洞利用条件更为苛刻。布尔盲注这是最常用的备选方案。通过and、or连接条件根据页面返回内容的差异真/假来逐位推断数据。它不需要union但速度极慢需要自动化脚本如sqlmap。核心Payloadand ascii(substr(database(),1,1))100—— 判断数据库名第一个字符的ASCII码是否大于100。时间盲注当页面无论真假都返回相同内容时使用。通过sleep()等函数根据页面响应时间来判断条件真假。核心Payloadand if(ascii(substr(database(),1,1))100, sleep(5), 0)—— 如果条件为真则延迟5秒。报错注入利用数据库执行错误并回显错误信息的特点将查询结果带到错误信息中。它通常需要updatexml()、extractvalue()、floor(rand())等函数同样不依赖union。经典Payloadand updatexml(1, concat(0x7e, (select user()), 0x7e), 1)。实操心得在实际测试中我通常会准备一个“绕过字典”它是一个文本文件里面按顺序排列了从简单到复杂的Payload。我的自动化脚本或手动测试时会依次尝试这些Payload观察响应。这个字典的构建顺序很重要先尝试最轻量、最可能成功的如大小写、内联注释再尝试复杂的如双重编码、注释分割最后才考虑盲注。这样可以提高测试效率。5. 联合绕过实战针对“union select”整体过滤的案例很多WAF规则会直接将union select作为一个整体来拦截。下面我们模拟一个实战场景。假设场景一个GET型注入点参数是id后端数据库是MySQL。union select被过滤返回403。测试步骤确认注入点与数据库?id1 and 11返回正常?id1 and 12返回异常确认存在字符型注入。通过?id1 and version()0 --确认数据库为MySQL。尝试基础绕过?id1 UniOn SelEcT 1,2,3 --(失败看来不是简单大小写)?id1 /*!union*/ /*!select*/ 1,2,3 --(失败内联注释也被识别了)尝试注释分割?id1 uni/**/on sel/**/ect 1,2,3 --(失败可能/**/被规范化处理了)?id1 un%0aion s%0elect 1,2,3 --(换行符失败)尝试更隐蔽的分割使用不常见的空白符或注释?id1 un%0bion s%0blect 1,2,3 --(%0b垂直制表符尝试)?id1 un%09ion s%09elect 1,2,3 --(%09水平制表符尝试)?id-1 union%0aselect%0a1,2,3 --(在union和select后都加换行有时有效)尝试双重编码在Burp Suite中操作原始Payload:union select第一次URL编码:%75%6e%69%6f%6e%20%73%65%6c%65%63%74对上述结果再次URL编码对%号也编码:%-%25所以%75-%2575最终:%2575%256e%2569%256f%256e%2520%2573%2565%256c%2565%2563%2574构造URL:?id-1 %2575%256e%2569%256f%256e%2520%2573%2565%256c%2565%2563%2574 1,2,3 --发送请求观察是否绕过。尝试混合技巧?id-1 /*!u%6eion*/ /*!s%65lect*/ 1,2,3 --(内联注释部分URL编码)?id-1 unio%6e sel%65ct 1,2,3 --(只对关键字中的个别字符进行编码制造不规则性)终极方案如果以上均失败转向盲注。使用sqlmap的--tamper脚本功能自动尝试各种绕过。例如sqlmap -u http://target.com/page?id1 --tamperspace2comment,charencode --dbs常见问题与排查问题Payload明明在本地MySQL测试通过但发送到目标却报语法错误或没反应。排查检查空格处理WAF或应用是否过滤了所有空白尝试用/**/或%0a代替普通空格。检查注释符--后面有个空格、#、/* */哪种被支持尝试更换。检查单引号/双引号闭合确认注入点的闭合方式确保Payload插入后SQL语法正确。使用Burp Suite的Compare功能对比正常请求和注入请求的响应差异哪怕是一个单词、一个标点的变化都可能是盲注的判断依据。6. 自动化工具辅助与手工测试平衡对于这类绕过完全依赖手工效率太低完全依赖自动化工具又可能不够灵活。我的经验是初期侦察用手工用浏览器和Burp Suite手动测试几个最简单的Payload快速判断WAF的敏感度和拦截级别。观察拦截页面是WAF返回的如Cloudflare的挑战页面、阿里云的拦截提示还是应用返回的。核心爆破用sqlmap tamper脚本sqlmap的强大之处在于其丰富的--tamper脚本。这些脚本能自动对Payload进行各种混淆。常用tamper脚本space2comment: 用/**/替换空格between: 用between替换比较符charencode: URL编码randomcase: 随机大小写equaltolike: 用like替换greatest: 绕过过滤apostrophemask: 用UTF-8全角字符绕过单引号过滤命令示例sqlmap -u URL --tamperspace2comment,randomcase --level3 --risk2高级用法可以自己编写tamper脚本Python定义独特的混淆规则应对定制化WAF。难点突破回归手工当sqlmap的所有tamper脚本都无效时就需要结合之前分析的WAF逻辑手工构造一些“怪异”的Payload。比如研究WAF是否对某些特定HTTP头如X-Forwarded-For的处理不同或者尝试POST请求与GET请求的拦截策略是否一致。7. 防御视角如何构建更有效的WAF规则作为防守方了解这些绕过技术是为了更好地防御。以下是一些建议正则表达式优化使用(?i)进行不区分大小写匹配。在匹配关键字时考虑其前后边界例如\bunion\b可以匹配单词union但不会匹配unionized。编写规则匹配经过简单混淆的Payload例如/s\s*e\s*l\s*e\s*c\s*t/i可以匹配插入任意空白符的select。多层级清洗与规范化在WAF层或应用入口对输入进行统一的URL解码甚至双重解码。将多种空白符%20,%0a,%0d,%09,%0b,/**/统一替换为单个空格。移除SQL注释--,#,/* */但需谨慎避免破坏合法输入。语义分析尝试构建简单的SQL语法树。如果检测到union后面紧跟select无论中间有多少混淆都进行拦截。这需要更强的计算能力。行为分析与频率限制对同一参数提交大量不同Payload的IP进行限速或临时封禁。监控产生大量数据库错误特别是报错注入特征错误的请求。根本解决参数化查询与ORM所有讨论的绕过其根源在于应用程序将用户输入直接拼接到了SQL语句中。使用参数化查询Prepared Statements或ORM框架是杜绝SQL注入的终极方案。让数据和指令彻底分离无论攻击者如何混淆Payload都会被当作纯数据处理无法改变SQL语句的结构。绕过WAF是一个持续对抗的过程。今天有效的技巧明天可能就被加入规则库。因此对于安全测试者保持学习、研究新特性、新漏洞如新型数据库函数的利用至关重要。而对于防御者则应坚持最小化攻击面、实施纵深防御并将参数化查询作为开发的铁律。安全的核心永远在于对原理的深刻理解而非单纯依赖工具或规则。