ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SQL注入绕过实战:从参数混淆到宽字节注入原理详解

SQL注入绕过实战:从参数混淆到宽字节注入原理详解 1. 从Less 29到Less 33当SQL注入遇上“保护”机制如果你已经一路从Sqli-labs的Less 1刷到了Less 28可能会觉得SQL注入的基本套路已经了然于胸联合查询、报错注入、布尔盲注、时间盲注无非就是这些。但当你推开Less 29的大门事情开始变得有点不一样了。从Less 29到Less 33这五关靶场设计者不再只是简单地给你一个存在注入点的登录框或查询框而是开始引入一些“保护”机制。这些机制在真实的渗透测试和CTF比赛中恰恰是区分“脚本小子”和真正理解漏洞原理的测试者的关键。它们模拟了现实环境中开发者为了“安全”而添加的一些代码层防护比如对输入进行转义、过滤特定字符甚至是部署了简单的WAFWeb应用防火墙规则。这五关的核心不再是教你一种新的注入技术而是教你如何在“戴着镣铐跳舞”。你会遇到服务器端对单引号进行转义的情况也会遇到通过编码或宽字节来“欺骗”防护逻辑的场景。理解这些绕过手法的本质比单纯记住一个Payload重要得多。因为现实世界的防护千变万化但底层原理——字符编码、解析顺序、逻辑缺陷——往往是相通的。接下来我们就一层层剥开这些“保护”的外衣看看里面的注入点究竟是如何被触发的。我会结合我多次通关和在实际测试中遇到的类似场景把每个环节的思考过程、测试方法和最终绕过方案都拆解清楚。2. Less 29被错误理解的“保护”——多参数与逻辑混淆Less 29的界面看起来和之前的许多关卡类似一个简单的id参数查询。但如果你直接用id1去测试可能会发现单引号被转义成了\页面返回正常这似乎暗示着存在addslashes()或magic_quotes_gpc这类转义函数在PHP中它们会在单引号、双引号、反斜杠\和NULL字符前添加反斜杠进行转义。如果按照这个思路你可能会去尝试宽字节注入我们稍后在Less 32会详细讲或者寻找数字型注入点。但Less 29的“坑”恰恰就在这里。它模拟了一种在初级开发中非常常见的错误开发者以为对用户输入的某一个变量进行了防护就万事大吉了。实际上这一关的源码我们可以通过查看Sqli-labs的源代码或基于经验推断揭示了一个关键点它存在多个接收同一数据的入口点但只对其中的一个进行了过滤。常见的情况是代码可能通过$_REQUEST、$_GET、$_POST等多个超全局变量获取参数但只在处理$_GET[id]时调用了转义函数而处理$_POST[id]或$_REQUEST[id]时没有。所以绕过Less 29的核心思路不是去硬刚转义而是换一个参数传递方式。具体操作如下确认基础注入点首先用id1和id2确认页面存在差异说明id参数确实被带入数据库查询。测试转义使用id1发现返回正常可能显示id1的结果或无结果而id1 and 11这类Payload可能失效因为单引号被转义破坏了SQL语法。关键绕过尝试此时不要纠结于GET请求。将请求方式从GET改为POST。你可以使用Burp Suite、HackBar插件或者直接编写一个简单的HTML表单。form actionhttp://your-target/Less-29/ methodPOST input typetext nameid value1 input typesubmit /form用这个表单提交数据或者用Burp Suite拦截GET请求后将其改为POST请求并在Body中填入id1。这时你很可能会看到数据库报错信息比如You have an error in your SQL syntax...。原理分析为什么POST能绕过因为后端代码可能类似这样$id $_GET[id]; // 或者从$_REQUEST中获取但处理逻辑有分支 $id mysql_real_escape_string($id); // 对来自GET的id进行了转义 // ... 但是在代码的另一处或者因为框架特性它又直接使用了$_POST[id]或未经转义的$_REQUEST[id] $sql SELECT * FROM users WHERE id$id LIMIT 0,1;当我们以POST方式提交时$_GET数组为空程序可能从$_POST中获取了未经过滤的id值从而导致注入。实操心得这种漏洞在早期PHP项目中特别是那些自己手写路由和参数处理逻辑的代码里非常常见。在测试时一个很好的习惯是对任何一个参数都尝试用GET、POST、Cookie、Header如X-Forwarded-For等多种方式去传递观察响应差异。Less 29就是培养这种“参数来源多样性”思维的绝佳练习。3. Less 30当“保护”升级为WAF规则模拟通过了Less 29你可能会想那是不是所有用POST请求就能搞定Less 30立刻给你上了一课。这一关同样对GET请求中的单引号等特殊字符进行了转义或过滤但当你尝试切换到POST方法时会发现依然不行。这说明防护可能被应用在了更底层的位置或者针对所有输入源都进行了处理。此时我们需要考虑另一种常见的防护手段基于黑名单的字符串过滤。这可以是一个简单的PHP函数str_replace()也可以模拟一个轻量级WAF的规则。例如代码可能这样写$id $_REQUEST[id]; // 同时处理GET和POST $blacklist array(, \, or, and, union, select, from, where, ...); $id str_replace($blacklist, , $id); // 简单粗暴地删除黑名单字符 $sql SELECT * FROM users WHERE id$id LIMIT 0,1;对于这种过滤直接的1 union select 1,2,3--会被过滤成1 union select 1,2,3--导致语法错误或Payload失效。我们的绕过思路是使用双写绕过。因为str_replace()函数在一次调用中只会替换一次。它不会递归地检查替换后的字符串。双写绕过实战步骤探测过滤规则首先需要知道它过滤了什么。提交id1观察是否返回错误。如果页面正常说明单引号被删除或转义了。提交id1 union select 1,2,3观察union和select是否被处理。构造双写Payload假设我们发现union和select被过滤了。我们可以构造这样的Payload1 ununionion seselectlect 1,2,3--当这个字符串经过str_replace(union, , $id)和str_replace(select, , $id)处理后中间的union和select会被删除但剩下的字符正好又组合成了新的union和select 处理过程模拟原始输入ununionion seselectlect 1,2,3--过滤union删除中间的union得到un ion se select lect 1,2,3--过滤select在上一步结果中删除select得到un ion se lect 1,2,3--最终字符串union select 1,2,3--注入成功。注意事项双写时要注意单词的拼接点。ununionion的写法是为了确保删除union后头尾的un和ion能无缝拼接。你需要根据实际过滤的词来调整。例如如果过滤or可以写成oorr。踩坑记录在实际测试中我曾遇到过一个过滤select但不过滤SELECT大小写敏感的WAF。所以在尝试双写前先试试简单的大小写绕过如UnIoN SeLeCt。如果不行再上双写。Less 30主要训练的就是这种基于字符串操作的过滤绕过思维。4. Less 31深入理解“宽字节注入”的前置知识在进入Less 32著名的“宽字节注入”关卡前Less 31是一个非常重要的预备课。它看起来可能很简单甚至有些令人困惑因为它似乎没有明显的防护。但这一关的核心目的是让你理解数据库连接中一个至关重要的设置字符集。当我们向数据库发送一个SQL语句时无论是用户输入的1还是程序拼接的最终都是一串字节流。数据库如何解读这串字节流取决于连接所使用的字符集。常见的字符集有UTF-8、GBK、GB2312、ISO-8859-1等。为什么字符集如此重要因为它直接决定了“转义”是否有效。PHP的mysql_real_escape_string()或addslashes()函数其作用是在特殊字符前加上一个反斜杠\ASCII码为0x5C。例如单引号0x27被转义为\0x5C27。如果数据库连接使用的是GBK、BIG5这类宽字符集通常用两个字节表示一个汉字事情就变得有趣了。在GBK编码中一个汉字由两个字节组成且第一个字节高位字节的范围是0x81-0xFE第二个字节低位字节的范围是0x40-0xFE不包括0x7F。关键点来了反斜杠\的ASCII码0x5C正好落在GBK低位字节的合法范围内。这意味着如果我们能构造一个特殊的输入使得其最后一个字节与转义添加的反斜杠0x5C结合形成一个GBK编码的合法汉字那么数据库在GBK字符集下解析时就会将反斜杠我们输入的某个字节当作一个完整的汉字字符吃掉从而让后面的单引号“逃逸”出来重新成为有效的字符串分隔符。Less 31通常被设置为一个使用GBK字符集连接的环境但可能没有主动进行转义。它让你先感受一下在GBK环境下直接输入一个包含特殊字节的Payload会发生什么。例如你可能会尝试输入id1%df%df是URL编码对应字节0xDF。在没有转义的情况下0xDF27在GBK里可能不是一个合法字符会导致错误。但这为你理解下一关打下了基础当存在转义时我们输入%df会被转义为%df\即0xDF5C27。而在GBK看来0xDF5C正好对应一个特定的汉字“運”的GBK编码于是0xDF5C被当作一个字符剩下的0x27单引号就独立出来了。所以Less 31的练习可能是让你尝试id1%df%27并观察数据库的错误信息理解%df和后面的单引号是如何被数据库解析的。这一步是理解宽字节注入原理的必经之路。5. Less 32经典宽字节注入原理与实战突破有了Less 31的铺垫Less 32的挑战就清晰了。这一关明确设置了mysql_set_charset(gbk)或类似的连接字符集为GBK并且对输入使用了addslashes()或mysql_real_escape_string()进行转义。我们的目标就是利用宽字节特性让转义失效。完整攻击链演示假设后端代码如下mysql_set_charset(gbk); $id addslashes($_GET[id]); // 输入 1%df 会被处理为 1%df\ $sql SELECT * FROM users WHERE id$id LIMIT 0,1;我们提交的Payload是?id1%df%27%df是0xDF%27是单引号。URL解码与转义服务器收到%df%27先进行URL解码得到字节序列0xDF27。然后addslashes()函数在单引号0x27前插入反斜杠0x5C。此时$id在内存中的字节序列变为0x31(1),0xDF,0x5C,0x27。拼接SQL语句SQL语句被拼接为SELECT ... WHERE id1運 LIMIT 0,1。注意这里0xDF5C在GBK编码中对应汉字“運”所以程序“认为”$id的值是字符串1運。数据库解析关键当这个SQL语句以GBK字符集发送到MySQL时MySQL同样以GBK来解析这个SQL字符串。它依次读取字节0x31-10xDF5C- 被解析为一个完整的GBK字符“運”0x27- 独立的单引号于是最终的SQL在数据库看来是这样的SELECT ... WHERE id1運 LIMIT 0,1。这里出现了两个连续的单引号在SQL中这通常表示一个空字符如果是在字符串里或者会导致语法错误如果是在外面。但更重要的是我们输入的那个单引号已经成功地逃逸出了原字符串的包围成为了SQL语法的一部分。构造有效注入为了让这个逃逸的单引号发挥作用我们需要用注释符将其后面的原语句单引号注释掉。所以完整的Payload是?id1%df%27%20union%20select%201,2,3--%df%27构成宽字节吃掉反斜杠。%20是空格。--是注释符在URL中代表空格用于注释掉SQL语句末尾原本存在的那个单引号。 最终数据库执行的语句是SELECT ... WHERE id1運 union select 1,2,3-- LIMIT 0,1。注入成功。核心技巧与变种并非只有%df任何高位字节0x81至0xFE与0x5C组合能构成合法GBK字符的都可以。常用的是%df、%bf等。%bf%5c是“縗”。防御措施真正的安全做法是在使用mysql_real_escape_string()等函数之前先使用mysql_set_charset(gbk)或mysqli_set_charset/PDO::setCharset。因为mysql_real_escape_string()会考虑当前连接的字符集从而安全地转义。或者更根本的方法是统一使用UTF-8字符集并确保连接、客户端、数据库三者的字符集一致。其他宽字符集除了GBKBIG5等也存在类似问题原理相通。6. Less 33addslashes()的全局之困与绕过思路延续Less 33可以看作是Less 32的一个“变体”或“巩固”。它可能使用了addslashes()函数进行转义并且字符集环境可能也是GBK或者通过其他方式触发了类似宽字节的行为。addslashes()和mysql_real_escape_string()的主要区别在于后者会考虑数据库连接的字符集而前者是“无脑”地转义那四个字符,,\,NULL。在GBK环境下addslashes()的缺陷和Less 32中描述的一样因为它只是添加了0x5C没有考虑后续的字符集解析。所以宽字节注入的Payload在Less 33上完全通用。但是Less 33可能还隐藏着另一个教学点addslashes()的局限性。它只转义单引号、双引号、反斜杠和NULL。如果注入点是数字型或者SQL语句中参数没有被引号包围那么addslashes()就形同虚设。例如$id addslashes($_GET[id]); $sql SELECT * FROM users WHERE id$id; // 数字型没有引号在这种情况下输入1 union select 1,2,3addslashes()不会做任何事因为里面没有它要转义的字符。注入直接成功。因此对于Less 33的测试我们应该遵循以下步骤判断注入类型首先用id1和id2-1测试。如果2-1的结果和1一样说明很可能是数字型注入addslashes()防护无效。可以直接进行联合查询等操作。如果是有引号的字符型尝试Less 32的宽字节Payload?id1%df%27%20union%20select%201,2,3--。如果成功说明是GBK环境下的字符型注入addslashes()被宽字节绕过。如果宽字节不成功考虑其他可能性。例如是否字符集不是GBK或者是否在addslashes()之后又进行了其他解码操作如urldecode()可以尝试二次编码绕过%2527%27是单引号再次URL编码后变成%2527。服务器收到后可能会解码一次变成%27然后addslashes()将其转义为%5C%27但如果程序又意外地进行了一次URL解码%5C%27会被解码为\单引号依然被转义。这种情况比较少见但体现了“多层编码/解码”可能引入的混乱。经验总结从Less 29到Less 33是一个思维进阶的过程。它告诉我们安全是一个链条任何一个环节的疏忽如多参数来源、只过滤一次、字符集不一致、转义函数使用不当都可能导致整个防护体系崩塌。在实战中面对一个看似有防护的点我们需要系统地思考防护作用于哪个层面输入层、应用层、数据库层防护的逻辑是什么黑名单、转义、编码是否存在逻辑死角或解析差异通过这五关的训练你应该建立起这种多角度、深层次的测试思维。
返回列表