Sqllibs Less 23-28:SQL注入过滤绕过实战复盘
刚把 Sqllibs 靶场里的 Less 23 到 Less 28 这一组关卡跨过去感觉特别值得写一篇复盘。前面二十几关教的是基础注入联合查询、报错注入、盲注大家练得再熟也只是在同一种固定套路里反复确认“这里能注入、那里也能注入”。但到了 Less 23 以后难度突然换了一个方向注释符被过滤、OR 和 AND 被打回、空格被吞掉、连括号都要自己数。这说明注入开始从“找漏洞”变成“绕过防御”更接近真实环境里碰到的场景。这篇文章不打算把所有 payload 机械地背一遍而是想带着你把每一关背后的过滤逻辑理清楚为什么这个写法能绕过绕过之后 SQL 最终变成了什么适合刚学完基础注入、准备进阶过滤绕过的同学也适合做开发的同学看一眼——你们真的以为过滤掉几个危险字符就安全了吗还早得很。1. Less 23-28 的系统设计一张表看清六道关卡的递进关系1.1 从基础注入到过滤绕过的分水岭Sqllibs 中前面几关更像“漏斗”测试先用单引号试错发现 SQL 报错再判断字段数然后 UNION 或盲注拿数据。这套流程一旦熟练很多关卡其实长得差不多。Less 23 开始加入了服务端过滤等于考试从“开卷考”变成了“闭卷考”不是不知道答案而是答案里的一部分字符被划掉了。这种设计非常贴近真实系统。现在很少有应用把输入原封不动拼进 SQL多数多少会加一点防护最常见的就是黑名单过滤。但黑名单的问题在于它依赖维护者把所有危险写法都列干净而这几乎不可能。Less 23 到 28 就是一组围绕黑名单的对照组过滤注释符、过滤 or、过滤 and、过滤空格、过滤关键字、再加上括号包裹。每关只变一个维度你就能清楚看到“过滤规则不同绕过思路也完全不同”。1.2 六关的能力要求速查表先给一张总表后面的小节再逐关展开。不同版本的 Sqllibs 具体关卡细节可能有点差异但技能点基本落在这些地方。关卡常见过滤内容核心训练点越接近实战的收获Less 23过滤--和#类注释符不用注释符也能闭合引号理解语句如何被补成一个合法整体Less 24入口点过滤不明显二次注入注入点不在第一个请求里Less 25过滤or和and双写绕过、逻辑符号替代判断黑名单是否只删一遍Less 26过滤空格、or、and、注释符无空格 SQL 构造应对\s类空白字符过滤Less 27过滤关键字但不够完整大小写混合黑名单不规范化时如何失效Less 28过滤空格、注释符、叠加括号多层括号闭合应对“查询外面套括号”的写法这张表想强调一件事Less 的核心不是“背一个 payload”而是“遇到一个过滤规则你能否马上判断出它整个只做了一层并且能不能用等价符号替代”。2. Less 23没有注释符时怎么把查询语句补成一个合法整体2.1 先搞清楚源码里的拼接姿势Less 23 的经典配置是这样的参数被直接拼进单引号包裹的 SQL 语句里同时服务端对注释符做了过滤。你可以理解为后端代码长这样$id $_GET[id]; $id str_replace([--, #], , $id); $sql SELECT * FROM users WHERE id$id LIMIT 0,1;;前面练得熟的人上来第一反应是输入?id1 --。但在 Less 23 里--会先被删掉剩下1 语句后半截直接变成WHERE id1 LIMIT 0,1自然报错。#也一样会被过滤。问题就来了传统注入靠注释符吞掉 SQL 语句尾部的引号现在尾部引号没法吞但前置的引号已经被我们闭合了那就必须让整个语句自己成立。2.2 用闭合引号替代注释符不依赖尾部注释的 payload 构造Less 23 最核心的思路就是“用另一个单引号去配对而不是用注释把尾部引号废掉”。最经典的验证写法是?id1 AND 11拼出来是SELECT * FROM users WHERE id1 AND 11 LIMIT 0,1;注意我们输入最后那个补上了源码里的收尾引号这样整条 SQL 就是一个合法表达式前面1已经把查询条件闭合后面AND 11永远为真所以页面正常返回。联合注入的写法同样由这个思路变形而来?id-1 UNION SELECT 1,2,3 AND 11拼出来大约是SELECT * FROM users WHERE id-1 UNION SELECT 1,2,3 AND 11 LIMIT 0,1;这里用-1让前面的查询结果为空UNION 后面的部分来决定页面显示哪几列。AND 11不再负责“布尔判断”而是负责补引号同时还不会让整条语句失败。这就是 Less 23 和前面 Less 最大的区别你不需要注释符你需要的是让 SQL 的引号数量刚好匹配。2.3 我在这一关容易踩的坑第一个坑是 URL 编码问题。很多人在浏览器里输入#结果发现 URL 到#就断了因为#在 URL 里是锚点标识。必须写成%23。但在 Less 23 里即使写成%23后端解码后也会被过滤所以别在注释符上死磕。第二个坑是列数检测。平时习惯用order by 3但注释符被过滤后直接写1 order by 3会因为尾部引号没闭合而报错。可以改成1 order by 3, 11把最后一个单引号补上。不过说实话在已知是 union 注入的前提下直接用UNION SELECT 1,2,3试错比 order by 更快错了就换UNION SELECT 1,2,3,4直到不报错为止。第三个坑是盲目套用“注释符过滤”就以为不会注入。Less 23 恰恰在告诉你注释符本质上是 SQL 语法的一部分但 SQL 语言本身有非常强的自我闭合能力。只要你能把条件表达式补完整尾部注释根本无所谓。3. Less 24二次注入的完整利用链比普通注入更隐蔽3.1 注入点不在第一个请求里Less 24 是很典型的二次注入关卡。它的特点是注册用户时输入的内容不会立刻触发 SQL 注入而是在后面某个操作中重新进入 SQL 语句。不少人在入口点试了半天发现普通注入没戏就直接跳过了。这其实漏掉了一个非常重要的场景危险数据会被存储之后被其他代码段不加处理地拼进 SQL。简单理解就是第一次写入数据库时如果代码用了转义或预处理单引号还能被安全存下来。但后续某个功能比如“修改密码”它把数据库里读出来的用户名直接拼到UPDATE语句里那么注入就可能在第二次请求时发生。3.2 一次模拟实验的完整时间线在本地靶场里可以按这个流程复现注册一个用户用户名写成admin#这个#会被原样存进数据库。用这个账号登录系统会把用户名带进会话。执行修改密码操作后端执行类似UPDATE users SET password$newpass WHERE username$username的语句。如果$username是admin#拼出来的 SQL 就是UPDATE users SET passwordhacked WHERE usernameadmin# AND passwordoldpass;由于#后面被注释掉这条语句真正生效的是WHERE usernameadmin。也就是说攻击者把自己账号的密码改成了任意值同时也把管理员账号的密码改成了这个值。整个过程里注册接口没有直接暴露注入因为它很可能用了预处理或转义。真正的问题出在“使用数据的时候”没有再次转义。这种“存储—再使用”的链路在真实业务里非常常见比如昵称、地址、备注字段很可能只在校验当前接口却忽略了后台管理页、通知消息、报表查询等后续环节。3.3 二次注入给开发的启示Less 24 最值得学习的其实不是怎么打而是怎么防。黑名单、转义函数、过滤这些手段都只能保护“当前调用点”。只要后面有代码从数据库读数据再拼 SQL前面再好的过滤都会失效。唯一的根治办法是全链路使用参数化查询存储过程、ORM、占位符都行关键原则只有一个SQL 结构固定数据永远是参数绝不能把外部或数据库读到的内容变成 SQL 代码的一部分。另外补充一个排查技巧怀疑存在二次注入时可以特意注册一个带引号的用户名然后去看“修改密码”或“个人信息更新”这些操作的数据库日志。一旦日志里出现异常 SQL 或报错就说明这条链路没有做参数化。4. Less 25、26过滤 or/and 和空格如何系统化绕过而不是背字典4.1 双写绕过 or 的核心条件Less 25 的主要过滤目标是or和and。很多初学者碰到1 or 11被过滤就麻了其实第一步是判断后端到底怎么过滤。如果它是“先删除一次”而不是“循环删除到没有为止”那么双写就能轻松过关。举个例子假设后端处理代码是$id str_replace([or, and], , $id);注意这个替换是一次性替换不是循环。当我们输入?id1 oorr 11过滤时oorr中间被删掉一个or剩下的内容恰好是or。原 SQL 变成SELECT * FROM users WHERE id1 or 11 LIMIT 0,1;这就实现了从oorr到or的“还原”。同理anandd会被还原成and。双写能不能成功取决于过滤逻辑是不是“一次删除”而不是“递归删除”。很多粗糙的 WAF 或自研防注入函数都是前者所以这种低成本的绕过方式直到今天仍然能碰到。另外如果过滤只针对关键字文字我们还可以用符号替代。在默认 MySQL 模式下||等价于or等价于and。比如?id1 || 11这种写法没有出现or和and照样能达到布尔逻辑效果。但要注意不同数据库对||的解释不一样Oracle 和 PostgreSQL 里它是字符串拼接MySQL 默认才当逻辑或。做题时先确认数据库类型再决定用不用符号替代。4.2 空格被过滤后的三板斧Less 26 在 Less 25 的基础上增加了“空格过滤”有些版本甚至会过滤注释符。空格的过滤一般来自类似preg_replace(/\s/, , $id)的正则意味着%20、%09、%0a这些常见替代空白也可能被吞掉。这时候需要三板斧依次尝试。第一板斧是换空白字符。URL 里的%09是制表符%0a是换行%0b、%0c、%0d也都是不同形式的空白。MySQL 解析器会把它们当成普通空白处理。如果后端只删了半角空格没有删制表符那么1 %09or %0911照样能过。第二板斧是彻底去掉空格改用括号和特殊语法重新组织表达式。比如select(1)括号替代了select和参数之间的空格from(users)替代了from和表名之间的空格where(11)同理。许多 SQL 函数调用都可以把参数直接放进括号不需要空格。第三板斧是用等价操作符和双写把敏感词规避掉。在 Less 26 那种“空格、or、and、注释符”全过滤的条件下可以构造一个完全不依赖空格和敏感词的报错注入例如?id1||(select(1))||11这里用||替代or用(select(1))消除空格末尾用||11闭合引号。整体没有空格也没有or、and、#、--但 SQL 的合法性和注入逻辑都保留了下来。这里要提醒一下一道题的本意不是让你死记某个%0b而是训练你“在字符级别想问题”。你可以先用 Burp 的 Repeater 逐个尝试空白字符变体再从后端响应差异里判断哪个字符被保留了。这个排查习惯比拿到一个万能 payload 重要得多。5. Less 27、28大小写混合与多层括号闭合的最后一公里5.1 为什么UnIoN SeLeCt能过而union select不能Less 27 训练的是一种非常矛盾但真实的防御漏洞黑名单不完整。假设后端过滤代码写的是$id preg_replace(/union|select/i, , $id);那UnIoN SeLeCt也会被过滤因为正则带了i修饰符大小写不敏感。但如果后端写的是$id str_replace([union, select], , $id);它只删小写组合那么输入UnIoN SeLeCt就不会被匹配到。SQL 关键字本身不区分大小写所以弱过滤很容易被混合大小写绕过。在 Less 27 里常见的验证方法是?id-1 UnIoN SeLeCt 1,2,3如果页面把 2、3 等数字回显出来就说明union select成功执行。这里的关键不是大小写本身有多神奇而是你必须判断后端的黑名单到底有没有做字符串规范化。如果它先用strtolower再过滤那大小写混合就完全没用如果它只是简单匹配那混合写法是成本最低的绕过方式。这个思路可以延伸到unionselect之间混入注释、换行、特殊空白字符。总之黑名单过滤只要没有经过“先统一转小写/转大写再去删”的步骤绕过空间就会大很多。5.2 括号闭合数括号比背 payload 重要Less 28 的最大看点是“括号”。有些 SQL 语句本身就在外面套一层括号比如SELECT * FROM users WHERE (id$id);如果我们只按普通单引号注入的思路操作闭合引号之后可能还会多出一个右括号整个语句还是非法的。这时候需要先判断源码里$id前面一共有几个左括号答一个就是WHERE (后面的那个。所以输入-1) OR (11就能把这个括号闭合掉SELECT * FROM users WHERE (id-1) OR (11);这里括号数目刚好匹配先是WHERE (被-1后的)闭合新的(11里的左括号又被语句原有的语法或额外右括号闭合。实际做题时你会碰到两层甚至更多层括号的情况那就得从内往外数。注入点前后到底有几个左括号没有闭合就补上几个右括号再通过一个合法的 OR/UNION 子句把控制权接回来。如果同时过滤了空格和注释符括号就是必须依赖的工具。比如? id-1)or((11结合 alternate whitespace maybe。但要记住括号闭合不是闹着玩的多一个少一个都会直接报错。我的排错办法是先输入一个观察报错样式再加一个)观察是否变化再加第二个)……每次只加一个符号响应从“报错”变成“正常”时就说明这一层闭合成功。这种逐字符逼近的方法比对着猜测的 payload 盲试靠谱得多。6. 防御视角和自动化测试从过关到真正能用的经验6.1 我刷完这些关卡后对“过滤”的新理解Less 23-28 这一整套刷下来最大的收获不是会了六个 payload而是知道了一个残酷的事实任何只靠黑名单字符过滤的防御都很难真正挡住懂语法的人。注释符不行就补引号or/and 不行就双写或用符号空格不行就换控制字符或括号大小写不行就编码绕过。攻击者不需要穷举所有可能只需要不断探测过滤规则里的漏网之鱼。关键短板有三处。第一黑名单覆盖面永远有限而且一次替换很容易被双写还原。第二过滤常常只发生在入口却忘了二次使用时同样不安全。第三有些系统对输入做了过滤却仍然用字符串拼接 SQL等于“把脏水滤了一遍然后倒进干净杯子”。真正有效的方案只有一个把数据当数据处理绝不让数据有机会被解析成 SQL 代码。6.2 在本地靶场里自动化工具要配合手工理解很多人一进靶场就开自动注入工具直接把整道题跑完其实少了很多乐趣也容易在真实环境里翻车。我建议的顺序是先在 Burp 的 Repeater 里手工判断注入点、闭合方式、过滤规则再让自动工具去跑剩余的数据提取。比如用自动工具扫描 Less 25-28可以指定一些通用 tamper 脚本但更重要的是理解它选了什么手法。如果自动工具选用“双写关键字”来绕or、用“hex 编码”来绕引号那不是魔法而是它把我们在手工环节验证过的思路固化了。对靶场来说最终结果不重要中间判断链最重要。另外练习归练习未经授权的系统绝对不要测。Sqllibs 这类本地靶场本来就是让你放开手脚试的一旦把 payload 打到不属于自己的线上环境那就不是学习而是违规违法的行为。6.3 给开发者的三句防注入忠告第一SQL 语句的结构必须固定所有外部输入一律用参数化查询或预处理语句绑定不要手动转义后拼接。第二数据库账号遵循最小权限原则应用连接账号不要给DROP、FILE、SUPER之类的高权限这样即使发生注入攻击者能做的事也有限。第三关闭页面上的详细报错输出把 SQL 错误记到后台日志里。很多人觉得报错注入是靶场才有的功能其实它能在真实系统里出现就是因为开发把数据库异常直接吐给了浏览器。我个人刷完这组题后的体会是Less 23-28 表面上是一组“绕过技巧”的练习题实际上是在训练一种“从语法层面理解 SQL”的能力。你一旦能不看 payload 就预判出过滤后的 SQL 长什么样才算真正过了这道坎。以后再遇到黑名单过滤第一反应就不是翻笔记而是问自己四个问题注释能不能用空格有哪些等价替代关键字能不能双写或符号化括号和引号到底需要补多少个这四个问题问完大多数不完善的过滤也就到头了。