SQL注入绕过技巧全解析:从基础过滤到WAF对抗实战
1. 从“新手”到“绕过”为什么SQL注入依然危险如果你刚接触网络安全或者对渗透测试有点兴趣那你肯定在各种教程、靶场和CTF题目里见过“SQL注入”这四个字。它太经典了经典到很多人觉得它已经是“上古漏洞”现代框架和WAFWeb应用防火墙早就把它防得死死的。但现实是它依然活跃在漏洞报告和真实攻击中。为什么因为防御从来不是一劳永逸的而攻击者的“绕过”技巧也在不断进化。我见过太多新手跟着教程在DVWA、Pikachu靶场里用‘ or ‘1’’1成功登录后就以为掌握了精髓。但一到稍微有点防护的真实环境或CTF题目里立刻傻眼—— payload被拦截返回错误甚至触发警报。这中间的差距就是“基础注入”和“绕过技巧”之间的鸿沟。所谓“黑客都在用”的技巧并不是什么高深莫测的黑魔法而是一系列针对常见防护机制的、有逻辑的“变形”和“试探”。这篇文章我就以一个过来人的身份拆解这些绕过的核心思路和实操手法让你不仅能复现靶场更能理解背后的“攻防博弈”。2. 理解战场SQL注入的防御与常见拦截点在学绕过之前必须明白你在绕过什么。盲目尝试就像蒙着眼睛走迷宫。现代应用对SQL注入的防御通常是多层级的我们需要一层层拆解。2.1 防御机制面面观1. 输入过滤与转义这是最基础的一层。开发人员或框架会对用户输入进行处理。关键字过滤直接黑名单匹配发现union,select,or,and,sleep,benchmark,information_schema等关键词就拦截或替换为空。字符转义对单引号‘、双引号、反斜杠\等特殊字符进行转义例如将‘变成\’使其失去闭合字符串的能力。类型强制转换对于期望是数字的参数强行转换为整数intval()非数字部分会被丢弃。2. WAFWeb应用防火墙规则WAF像是一个站在Web服务器前的安检员基于规则库实时检测流量。正则表达式匹配使用复杂的正则式匹配常见的SQL注入模式比如‘\sor\s‘、union\sselect等。语义分析更高级的WAF会尝试理解请求的语义判断是否存在异常的参数拼接行为。频率与异常检测短时间内大量包含特殊字符的请求或触发大量错误的请求可能被判定为攻击而封禁IP。3. 预编译语句参数化查询这是从根源上杜绝SQL注入的“银弹”。它的原理是将SQL语句的结构SELECT * FROM users WHERE id ?和传入的数据?的值分开处理。数据库引擎会先编译语句结构再将数据当作纯数据处理因此数据部分无论如何变化都无法改变语句结构。绕过预编译语句本身极其困难我们的绕过技巧主要针对前两种防御。4. 其他防护错误信息屏蔽将数据库的详细错误信息隐藏只返回通用错误页面增加“盲注”的难度。最小权限原则数据库连接账户权限被严格限制即使注入成功也无法执行高危操作如LOAD_FILE,INTO OUTFILE。2.2 我们的目标让恶意Payload“看起来正常”所有绕过技巧的核心目标就是让我们的SQL注入Payload能够逃过过滤规则同时在数据库层面依然能被正确解析为恶意代码。这需要一点点的“欺骗”艺术。注意本文所有技巧均用于授权测试如CTF比赛、自家靶场、有明确授权的渗透测试。未经授权对他人系统进行测试是违法行为。3. 基础变形绕过简单过滤与WAF规则我们从最简单的场景开始假设目标只有基础的关键字过滤和WAF规则。3.1 大小写与大小写混合绕过这是最古老但有时依然有效的技巧。有些简单的过滤规则是大小写敏感的。原始Payload:UNION SELECT 1,2,3绕过尝试:UnIoN SeLeCt 1,2,3或uNiOn sElEcT 1,2,3原理如果WAF规则只匹配了大写的UNION SELECT那么混合大小写的变体就可能溜过去。而MySQL等数据库在解析SQL语句时通常是不区分大小写的。3.2 双写、插入注释与空白符混淆当简单的关键字被过滤比如替换为空字符串时我们可以利用过滤逻辑的缺陷。1. 双写绕过如果过滤规则是简单地删除关键词比如把union替换成空。原始Payload:union select过滤后selectunion被删了语句错误双写Payload:ununionion selselectect过滤后union select中间的union被删除两边的un和ion组合成了新的unionselect同理实操场景在一些非常简单的DIY过滤代码中可能遇到。2. 内联注释绕过在SQL中/*注释内容*/是可以插入到语句中的。我们可以利用它将一个关键词“切开”。原始Payload:union select绕过Payload:uni/*任意内容*/on sel/*xxx*/ect原理对于数据库/*...*/内的内容会被忽略所以它执行的还是union select。但一些基于正则匹配的WAF可能无法识别被注释分隔的关键字。在MySQL中还可以使用/*!...*/这种特殊注释里面的内容在特定版本MySQL中会被执行有时也能起到混淆作用。3. 空白符替换SQL语句中的空白空格、制表符、换行通常是为了人类阅读方便数据库并不关心有多少或是什么类型的空白。但WAF规则可能只匹配了带空格的模式。原始Payload:union select 1,2,3绕过尝试:使用制表符%09union%09select%091,2,3使用换行符%0aunion%0aselect%0a1,2,3使用多个空格union select 1,2,3完全不写空格在某些特定位置可行union select(1),2,3将空格用括号代替原理%09(Tab)、%0a(换行)、%0d(回车) 在URL编码中代表特殊空白符它们都能在SQL中充当分隔符但可能绕过简单的空格匹配规则。3.3 等价函数与操作符替换如果or、and、被过滤了我们可以用别的来表达相同逻辑。or的替代:||在多数数据库中是字符串连接符但在某些上下文或配置下可作为逻辑或需测试更可靠的是利用运算‘ or ‘1’’1可以尝试变为‘ || ‘1’’1但更常见的是直接构造永真条件绕过or。and的替代:类似||作为逻辑与使用位运算或号拼接条件但不如and直观通常用于盲注。的替代:like‘ or ‘1’ like ‘1rlike,regexp使用正则匹配in()‘ or ‘1’ in (‘1’)或!的否定形式‘ or not (‘1’’1’)大于小于号在盲注中极其有用。‘ or ascii(substr(database(),1,1)) 100。通过二分法用和可以确定一个字符的ASCII码完全不需要。实操心得不要死记硬背Payload。准备一个本地测试环境如用Docker快速启动一个MySQL自己写个带简单过滤的PHP页面然后尝试各种变形用echo或日志查看最终拼接的SQL语句观察哪些被过滤了哪些成功执行了。这是理解绕过最有效的方法。4. 高级混淆应对更智能的WAF当目标使用了更现代的WAF如Cloudflare, ModSecurity等时上述简单变形可能失效。它们可能使用语法树分析能识别被空白和注释分隔的单词。这时需要更“深入”的混淆。4.1 编码与十六进制混淆1. 十六进制Hex编码将字符串转换成十六进制表示。数据库可以直接解释十六进制字符串。目标查询admin用户。常规Payload:‘ union select 1,2,3 from users where username‘admin‘--Hex编码Payload:‘ union select 1,2,3 from users where username0x61646d696e--原理admin的十六进制是0x61646d696e。WAF可能只检测到username‘admin‘这种模式但对username0x...不敏感。注意十六进制前需要加0x。2. URL编码、双重URL编码单次URL编码将特殊字符编码如单引号‘变成%27空格变成%20。这主要用于在HTTP请求中传输但后端程序通常会解码一次。如果WAF在解码前检查可能被绕过。双重URL编码更狡猾的做法。假设WAF解码一次后检查。原始单引号‘一次编码%27二次编码将%编码为%25得到%2527如果WAF只解码一次看到的是%27可能不认为它是单引号。但后端应用服务器如Apache/PHP可能会进行两次解码最终还原为‘。3. Unicode编码/UTF-8变形在一些国际化应用中可能会处理Unicode。例如某些WAF可能无法正确标准化所有Unicode字符。例如字母a可以用%u0061表示。但这种方式成功率较低高度依赖于目标应用和WAF的具体实现。4.2 利用数据库特性与非常规语法不同的数据库MySQL, PostgreSQL, SQL Server, Oracle有各自的“方言”和特性这些特性可能成为绕过的突破口。1. 字符串拼接当单引号被过滤时我们可以用函数来构造字符串。MySQL:‘ or ‘1‘‘1可以变为‘ or ‘1‘concat(‘1‘)更进一步用CHAR()函数‘ or ‘1‘char(49)49是 ‘1‘ 的ASCII码。甚至可以完全不用引号‘ or usernamechar(97,100,109,105,110)表示username‘admin‘。2. 注释符的妙用除了--注意有个空格和#MySQL还支持;%00空字节在某些情况下作为语句结束。但空字节注入在现代PHP版本中基本已修复。3. 分隔执行堆叠查询如果目标数据库驱动支持如PHP的mysqli_multi_query注入点可能允许执行多条SQL语句用分号;分隔。Payload:‘; DROP TABLE users; --绕过思路即使DROP被过滤可以先注入一条查询再用分号分隔下一条。但WAF通常会对分号高度警惕。堆叠查询在真实Web注入中并不常见更多见于SQLi靶场或特定场景。4. 非常规路径访问以MySQL为例/*!...*/版本特定注释里面的代码只在特定版本以上的MySQL执行。可用于绕过一些简单的关键字检查因为/*!50000union*/对数据库和WAF来说看起来不一样。用户变量‘ or a:1 union select a,2,3--通过变量传递值。4.3 分段传输与协议层绕过这类技巧更偏向于“攻击手法”而非单纯的Payload变形旨在扰乱WAF的检测逻辑。1. HTTP参数污染HPP提交多个同名参数不同中间件/后端处理方式不同可能导致WAF和后端解析结果不一致。请求/page?id1idunion select 1,2,3可能的情况WAF检查第一个id1认为是正常的。但PHP后端可能取最后一个参数值idunion select 1,2,3从而造成注入。2. 分块传输编码Chunked Transfer Encoding将请求体分块发送可能干扰那些需要完整请求体才能做分析的WAF。这通常需要借助Burp Suite等工具手动构造畸形的HTTP请求。3. 请求方式转换WAF可能只严格检查了GET参数但对同样参数的POST请求规则宽松或者反之。尝试将注入点从GET改为POST有时会有奇效。注意事项高级WAF绕过往往需要结合具体目标。没有通杀的Payload。最好的方法是信息收集通过报错、延时、布尔盲注等方式一点点试探出目标的过滤规则、数据库类型、以及哪些字符和函数可用。这是一个耐心的“拼图”过程。5. 盲注场景下的特殊绕过技巧在盲注中我们无法直接看到查询结果只能通过页面返回的差异布尔盲注或响应时间时间盲注来推断信息。这里的绕过重点在于构造出能产生“差异”或“延时”且不被过滤的Payload。5.1 布尔盲注的绕过核心是构造一个结果为真或假的条件语句且条件中的函数和操作符不被过滤。绕过substr过滤使用mid()、left()、right()函数mid(database(),1,1)使用like配合通配符database() like ‘a%‘判断库名是否以a开头。绕过ascii过滤使用ord()函数功能同ascii。直接比较字符‘ and substr(database(),1,1)‘a‘但需要单引号。如果单引号被转义可结合Hex编码‘ and substr(database(),1,1)0x61--。构造永真/永假条件如果and、or被过滤尝试用乘法或加法逻辑。例如在数字型注入中1 and 11返回正常1 and 12返回异常。如果and被过滤可以尝试1 11或者利用运算1*11在SQL中也是合法的比较虽然不标准但某些数据库可能接受。5.2 时间盲注的绕过时间盲注依赖sleep()、benchmark()这类能引起延时的函数。这些函数名通常是WAF的重点关注对象。sleep()的替代benchmark()benchmark(10000000, md5(‘test‘))通过大量重复计算md5来消耗时间。笛卡尔积延时‘ and (select count(*) from information_schema.columns a, information_schema.columns b, information_schema.columns c)0 and sleep(5)--。通过巨大的联表查询来制造延时但效率低且对数据库压力大。pg_sleep()(PostgreSQL),WAITFOR DELAY ‘0:0:5‘(SQL Server)根据数据库类型选择。绕过延时函数名过滤同样适用大小写、注释混淆SLEEP(5)-SlEeP(5)-sleep/*x*/(5)如果函数名被完全过滤时间盲注将非常困难可能需要寻找其他判断方式如利用DNS外带需要特殊权限或重度依赖布尔逻辑。时间盲注Payload示例MySQL‘ and if(ascii(substr(database(),1,1))100, sleep(3), 0)--这个Payload的意思是如果数据库名第一个字符的ASCII码大于100就休眠3秒否则立即返回。通过不断调整比较的数值二分法并根据响应时间是否明显延长来判断字符的ASCII码值。6. 工具辅助sqlmap的绕过技巧实战手工注入是理解原理的基础但在实战或CTF中合理使用sqlmap这样的自动化工具能极大提升效率。sqlmap内置了强大的绕过脚本tamper脚本。6.1 常用tamper脚本解析sqlmap的--tamper参数可以指定脚本对Payload进行混淆。以下是几个经典脚本的原理space2comment将空格替换为/**/。对应我们上面说的空白符混淆。space2plus将空格替换为。在URL中通常表示空格但在SQL的某些上下文里如字符串拼接可能被解析。between用between和and来替换和比较符。例如id 1变成id between 1 and 1用于绕过对比较符的检测。charencode对Payload进行URL编码。randomcase随机大小写。对应我们的大小写混合绕过。equaltolike将替换为like。greatest用greatest()函数绕过过滤。id 1变成id 1 and greatest(ascii(substr(...)), 1)ascii(substr(...))是一种更复杂的变形。apostrophemask将单引号替换为%EF%BC%87全角单引号的UTF-8编码在某些字符集处理不当的情况下有效。concat2concatws将concat()函数替换为concat_ws()后者参数格式不同可能绕过对concat的检测。modsecurityversioned用/*!...*/注释包裹关键字针对ModSecurity WAF。space2randomblank将空格替换为随机空白符如Tab换行。6.2 组合使用tamper脚本很少有场景只用一个脚本就能绕过。通常需要组合。sqlmap -u “http://target.com/page?id1“ --tamper“space2comment,between,randomcase“这个命令会依次应用三个脚本对Payload进行混淆。sqlmap的智能在于它会根据目标的响应动态调整Payload和脚本的使用。6.3 自定义tamper脚本如果已知目标的过滤规则非常特殊可以自己编写tamper脚本。脚本用Python编写基本结构是定义一个tamper(payload, **kwargs)函数对输入的payload字符串进行变换后返回。 例如如果目标过滤了select但不过滤sElect你可以写一个脚本将select随机替换为sElect、Select等变体。这需要你对Python和sqlmap的tamper API有一定了解。实操心得不要一开始就上--tamper。先用默认参数跑看拦截情况。如果被拦截用-v 3查看它发送的具体Payload分析是被哪个关键词或模式拦截了。然后有针对性地选择或组合tamper脚本。同时合理使用--delay设置请求延迟避免触发频率限制。7. 实战链路与排查心法掌握了各种技巧最终要串联成一次完整的测试流程。这里以测试一个疑似注入点为例。7.1 手工测试与确认注入点初步探测在参数后加单引号‘观察是否出现数据库错误如MySQL的You have an error in your SQL syntax或页面显示异常空白、布局错乱。如果错误信息被屏蔽进行下一步。判断类型数字型id1 and 11正常id1 and 12异常。字符型id1‘ and ‘1‘‘1正常id1‘ and ‘1‘‘2异常。如果单引号被转义尝试数字型测试或者用Hex编码测试字符型。判断闭合与注释通过and ‘1‘‘1和and ‘1‘‘2确定闭合方式后使用--或#注释掉后续语句确保Payload完整。7.2 逐步实施信息收集确认注入点后按顺序获取信息每一步都要考虑绕过。数据库版本‘ union select 1,version(),3--。如果union被过滤尝试用盲注‘ and ascii(substr(version(),1,1))50--。当前数据库名‘ union select 1,database(),3--。表名从information_schema.tables查询。Payload会变长被拦截概率增大。可能需要拆分‘ union select 1,group_concat(table_name),3 from information_schema.tables where table_schemadatabase()--。如果information_schema被过滤在一些老系统或特定配置下可以尝试用mysql.innodb_table_stats等替代但这不具有通用性。列名‘ union select 1,group_concat(column_name),3 from information_schema.columns where table_name‘users‘--。这里‘users‘可能需要Hex编码。数据‘ union select 1,username,password from users--。7.3 常见问题与排查表现象可能原因排查思路提交单引号后页面返回一个通用错误页或空白1. 单引号导致SQL语法错误但错误信息被屏蔽。2. 触发了WAF拦截连接被重置或阻断。1. 进行布尔盲注测试and 11/and 12看页面内容是否有细微差异如某个HTML元素消失、文字不同。2. 使用时间盲注测试and sleep(5)看响应是否明显延迟。3. 在Burp Suite中查看HTTP响应状态码如果是403,444,999等很可能是WAF拦截。union select被拦截但单引号测试正常WAF有针对union,select等关键词的规则。1. 尝试大小写混合UnIoN SeLeCt。2. 尝试插入注释uni/**/on sel/**/ect。3. 尝试使用空白符替换union%0aselect。4. 尝试使用 所有包含information_schema的Payload都被拦截WAF或过滤规则屏蔽了information_schema关键字。1. 尝试用infoorrmation_schema双写绕过如果过滤是删除关键词。2. 尝试用Hex编码表名0x696E666F726D6174696F6E5F736368656D61。3.终极方法盲注猜解。通过盲注用substr((select table_name from mysql.innodb_table_stats where database_namedatabase() limit 0,1),1,1)这类方式从其他可能包含元数据的系统表里一点点猜。非常耗时但有时是唯一途径。时间盲注的sleep()函数无效页面不延时1.sleep()函数被禁用或过滤。2. 数据库用户权限不足。3. 目标不是MySQL。1. 尝试benchmark()函数。2. 尝试使用笛卡尔积延时Payload。3. 确认数据库类型。如果是PostgreSQL用pg_sleep(5)SQL Server用WAITFOR DELAY ‘0:0:5‘。使用sqlmap测试时所有Payload都被识别为“可能不可注入”1. 目标有非常强的动态防护如每次请求token变化。2. 注入点判断错误。3. WAF完全阻断了sqlmap的探测流量。1. 使用--random-agent随机化User-Agent。2. 使用--proxy设置代理或使用--tor通过Tor网络。3. 增加--level和--risk参数如--level3 --risk3使用更多测试Payload。4. 手动在Burp中找到一个能确定注入的Payload然后用-p参数指定注入点并用--tamper精心组合脚本从低强度开始测试。7.4 我的个人经验与最后的建议绕过的过程本质上是一场信息不对等的博弈。防守方有规则攻击方在试探规则。我个人的体会是耐心和观察力比记住一百个Payload更重要。从错误信息中学习如果目标开启了错误回显像DVWA Low级别那是黄金信息。仔细看报错它能告诉你数据库类型、SQL语句的哪部分出错了甚至直接暴露部分数据结构。差异化测试不要只测一种Payload。用and 11和and 12对比用sleep(2)和sleep(5)对比观察页面内容、响应时间、响应头如Content-Length的任何细微不同。Burp Suite的Comparer工具在这里是神器。理解业务逻辑有时候注入点存在于复杂的业务交互中比如先POST登录再GET某个带Token的查询。模拟完整的用户流程才能找到那个隐藏的、防护可能较弱的注入点。工具是延伸不是大脑sqlmap很强大但把它当黑盒用你永远学不会真正的绕过。打开-v 3或-v 4选项看它发了什么目标回了什么。分析它为什么成功或失败这个过程才是能力提升的关键。保持更新安全技术日新月异。新的WAF规则会出现新的绕过技巧也会被研究出来。多关注安全社区、博客、CTF Writeups保持学习的心态。最后请永远记住技术是用来建设和保护的。在合法的靶场、CTF竞赛或获得明确授权的测试中尽情施展你的技巧将遇到的问题和解决方案记录下来分享给社区这才是这些“黑客技巧”真正闪光的地方。