攻防世界Supersqli SQL注入题详解:堆叠注入绕过select过滤拿flag

发布时间:2026/10/9 3:30:24
攻防世界Supersqli SQL注入题详解:堆叠注入绕过select过滤拿flag
打CTF的时候攻防世界Web方向有一道叫Supersqli的题目算是比较经典的SQL注入入门题。网上虽然有不少wp但大多数写得太简略新手照着做经常卡在某一步不知道为啥。这篇博客就按“胎教”标准来写把每一步的思路、命令、预期输出、报错原因都摊开讲清楚。不管你之前有没有接触过SQL注入只要能打开浏览器、能复制粘贴命令这篇文章就能带你完整走一遍最后把flag拿到手。先说明一下这道题的环境特点它表面上是一个普通的查询页面实际上藏了不少过滤逻辑直接提交常规的注入payload会碰到“select被过滤”的硬钉子。这个设计逼着你换思路也正是这道题最有价值的地方。攻防世界的靶场没有额外登录步骤只要进入题目页面就能开始测试非常适合用来练习手工注入和绕过技巧。如果你正好在刷攻防世界Web题这道题的优先级很高因为它能把SQL注入的基础知识、报错注入原理、堆叠注入的概念、以及如何绕过简单过滤串成一条线学完之后再看其他注入题会轻松很多。1. 题目初探与靶场环境准备1.1 进入题目先看页面长什么样攻防世界平台上Supersqli这道题的入口通常是一个独立的挑战页面点进去之后就是题目环境。页面样式非常朴素就是一个输入框加一个提交按钮输入框前面写着“查询”之类的字样。先别急着输入payload我习惯先做两件事一是右键查看页面源码二是随便输入一个正常值看看响应行为。输入一个数字或者字符串比如输入1页面会返回查询结果大概格式是一个表格或一段文字告诉你“数组为1”之类。这说明后端大概率是把输入拼进SQL语句并执行了如果输入1页面很可能直接报SQL语法错误比如返回error或者You have an error in your SQL syntax。这个反馈就是我们的突破口因为它确认了参数是直接进入SQL语句的存在注入点。有经验的朋友可能已经想到用SQLMap来扫但我的建议是这道题先手工打一遍。原因很简单Supersqli的设计重点就是让你理解绕过过滤的思维SQLMap自动跑很可能直接跑失败就算跑成功了你也不知道它干了啥。手工做一遍后面再用工具就会理解得更好。1.2 工具准备Burp Suite和本地环境这道题只需要两个工具浏览器和Burp Suite可选。浏览器用来查看页面和提交数据Burp Suite用来抓包看原始请求、构造特殊payload。如果你不想用Burp完全可以用浏览器自带的开发者工具F12直接改请求但Burp在后期调试编码和绕过滤的时候更方便。国内访问攻防世界的靶场一般不需要额外操作直接打开题目地址就行。如果遇到靶场加载慢的情况可能是平台自身的问题等一会儿再刷新。Windows、macOS、Linux都无所谓这道题不依赖操作系统。Burp Suite的配置还是简单提一下打开Burp后在Proxy标签页把Intercept打开浏览器设置代理指向127.0.0.1:8080然后访问靶场页面Burp就能截获请求。如果你之前没怎么用过Burp这道题可以顺便练练抓包、改包、重放。后面堆叠注入的部分我个人更习惯直接用Burp的Repeater来发请求因为可以随时修改body里的payload不用反复开新页面。准备工作就这么多。接下来正式开始注入测试。2. 注入点发现与注入类型判断2.1 确认注入点单引号触发报错页面输入框对应的POST参数一般叫inject或者id具体名字可以在Burp里看到。我抓包之后看到的请求格式大致是POST /?inject1 HTTP/1.1 Host: 靶场地址 Content-Type: application/x-www-form-urlencoded inject1先输入正常值1返回正常结果。然后输入1页面返回异常类似于SQL语法错误。这个报错极其重要它告诉我们后端SQL语句里直接拼接了输入内容使用的是单引号作为字符串边界闭合方式大概率是 input 这种形式。有报错说明至少存在报错注入和联合查询注入的可能。接下来按顺序测试字段数。2.2 测试字段数order by 与联合查询联合查询注入的第一步是判断当前查询返回几列。用order by N来试探输入1 order by 1-- 1 order by 2-- 1 order by 3--这里有个小细节--在MySQL里是注释符作用是把后面的单引号注释掉避免语法错误。URL编码环境下#也可以做注释符但--有时候会被过滤或格式问题所以我习惯用--。尝试到order by 3的时候如果页面报错或者不返回数据就说明列数小于3。我记得原始题目的列数是2所以order by 2正常order by 3报错。确认列数为2之后就可以构造联合查询1 union select 1,2--正常情况下页面会显示两个数字比如1和2代表两个可显示的字段位置。但Supersqli这道题在这里埋了一个坑当我提交1 union select 1,2--的时候页面直接报了一个类似“select被过滤”的错误。这说明后端对select做了黑名单过滤联合查询这条路走不通了。这是这道题的第一个核心卡点。很多新手走到这里就放弃了或者以为题目坏了。其实这是出题人故意设计的常规的union注入被堵死目的是引导你想到其他注入方式。2.3 绕过过滤的常用思路遇到select被过滤常见的思路有三类改变大小写比如SELECT很多过滤规则只匹配小写内联注释比如/*!50000SELECT*/有些WAF会漏掉换注入方式比如报错注入、布尔盲注、时间盲注、堆叠注入。我在这道题里把前两种都试了一遍。大小写不生效说明过滤规则是大小写不敏感的内联注释也不生效说明过滤得比较彻底。既然select被卡死那就要考虑不依赖select的注入方式。报错注入虽然不需要select出数据但它需要调用updatexml、extractvalue这类函数而这些函数在使用时往往也需要配合select或者union来构造报错语句。所以报错注入在这里也不一定好用。如果你试了1 and updatexml(1,concat(0x7e,database()),1)--也被拦截那就干脆放弃这条线。此时最值得尝试的就是堆叠注入stacked injections。它的核心思想是后端允许一次执行多条SQL语句以分号分隔。这样即使select被过滤我也可以先用其他语句操作数据库结构比如修改表名、修改字段名然后再用不变态的查询把数据查出来。等一下修改之后还是要查询那select怎么办这里的关键是过滤可能只发生在最外层的输入上而堆叠注入可以分步执行先通过修改语句把数据搬到一个“隐藏”位置再在正常查询中带出来这样就不需要再让select出现在注入payload里。这个思路听着有点绕但实际操作起来非常清晰。下一节详细展开。3. 绕过WAF与Select过滤的核心思路3.1 黑名单过滤分析到底过滤了什么我在测试过程中把常见的注入关键字都试了一遍最终确认这道题的过滤黑名单大致包含select。有些wp说还过滤了or、and、#等但我实测下来最关键的就是select。为了确认过滤规则可以在Burp里直接把注入参数改为1 and 11--如果它返回正常说明 and 没有被过滤如果报错说明被过滤。我在实际靶场里试的结果是and这种逻辑运算符没有被过滤主要卡点就是select。不过要注意不同时间攻防世界可能会更新题目或过滤规则所以不要死记结论重点是掌握排查思路。我的建议是先拿一个标准payload逐字拆开提交观察哪一部分被拦截拦截后返回什么错误。这一步能省下大量瞎猜的时间。3.2 为什么联合查询被堵从SQL语句拼接角度看在正常查询中后端SQL语句大致是select * from table where id $input当输入1 union select 1,2--时最终SQL变成select * from table where id 1 union select 1,2--如果没过滤这条语句能让union后的结果拼到正常查询结果后面。但过滤规则把select字段直接替换为空或者直接拒绝请求导致最终语句变成类似select * from table where id 1 union 1,2--或者直接被WAF拦截页面返回错误。这种设计其实模拟了很多真实站点的WAF规则不求全面但把最常用的关键字卡死。那堆叠注入为什么能绕过因为它不依赖union select这种写法。输入1;show tables;--时最终SQL变成select * from table where id 1;show tables;--分号把两条语句隔开第二条show tables会被MySQL执行但这条语句里没有select关键字所以绕过了过滤。这就是堆叠注入的精髓用其他SQL语句达到目的。3.3 堆叠注入的限制提示堆叠注入不是万能的。它能不能执行取决于后端使用的数据库驱动和API。PHP的MySQL扩展通常不支持多语句执行但MySQLi和PDO在特定配置下支持。Supersqli能使用堆叠注入说明后端环境对多语句是放开的。遇到这个特性时我心里就基本有数了后半段题目大概率是要操作数据库结构而不是简单的数据查询。补充一个安全话这里讨论的所有技术都只用于本地靶场和CTF比赛环境目的是理解Web安全原理。不要把这些手法用在其他未经授权的系统上遵守网络安全法是基本底线。4. 堆叠注入实操从show到set操作4.1 使用show语句获取数据库名和表名既然select被过滤我们第一步用show来代替“查询”的功能。show是MySQL语法里用于展示元数据的语句常见的有show databases; show tables; show columns from 表名;页面输入框提交1;show databases;--执行后页面会列出当前用户能看到的数据库。原理是第一条select * from table where id 1正常执行但返回空或报错随后第二条show databases执行并把结果输出到页面。我实测输出里大概率包含ctf、information_schema之类的数据库名。下一步获取表名提交1;show tables;--这一步很关键此时页面会输出当前数据库的所有表名。我在靶场里看到的表名是两个一个名字看起来很正常另一个带随机后缀。比如可能叫words和Qb4jf2983。这里需要记清楚表名的准确大小写和结构因为后面要用alter语句修改字段名错一个小写字母都会失败。4.2 分析表结构猜出查询语句用的表光看到两个表名还不够需要知道哪张表是当前正常查询正在用的表。怎么判断可以分别查看两张表里有什么字段。用1;show columns from words;-- 1;show columns from Qb4jf2983;--执行之后页面会输出每张表的字段信息。我遇到的典型结构是words表字段为id和data另一张表比如Qb4jf2983字段为flag之类的字段名。这两张表的差别非常明显words表的字段名和正常查询返回的“数组为1”中的键名一致说明页面上的正常查询用的就是words表。另一张表大概率存的就是flag字段名就叫flag。从现在开始解题思路就明朗了我可以通过修改表名和字段名让目标表的数据出现在正常查询中。原理是既然正常查询是select id,data from words如果我把words表改名成其他名字再把存flag的表改名成words然后把它的字段改成id和data那页面正常查询时不就直接把flag当data查出来了吗这就是经典的“改表名 改字段名”路线也是Supersqli后期最容易踩坑的地方。4.3 用alter语句修改表名先执行把原words表改名的操作。使用1;alter table words rename to words2;--改名之后原words表就不存在了正常查询会报错。不要慌这是预期内的。接着把存flag的表改名为words1;alter table Qb4jf2983 rename to words;--注意表名的大小写MySQL在Linux下区分表名大小写在Windows下可能不区分但靶场通常运行在Linux环境所以大写小写必须和show tables看到的一致。执行完这两步现在的状态是原words表变成了words2原本带flag的表变成了words。接下来要修改字段名。原flag表里的字段可能叫flag但正常查询查的是id和data两个字段。所以需要先把flag字段改名成data再添加一个id字段或者把原有字段顺序调整好。4.4 用alter语句修改字段名修改字段名用change关键字1;alter table words change flag data varchar(100);--这条语句把words表里的flag字段改名为data类型设为varchar(100)。如果原字段还有其他属性change后必须把完整定义写清楚否则可能报错。改完字段后还要看表里有没有id字段。如果原表只有flag字段那现在就只有data字段没有id。正常查询select id,data from words会报错因为id字段不存在。解决办法是新增一个id字段1;alter table words add id int;--这样表里就有id和data两个字段了。不过要注意新增的id字段默认值可能是NULL正常查询时结果里id就是NULL但这不影响我们看data的值。4.5 用desc确认修改结果每执行一步修改我建议都用1;desc words;--来确认当前表结构。desc也是不需要select的关键字能展示表字段信息。确认字段为id和data之后再回到页面输入正常值1或者直接提交空查询页面就会把words表里的所有数据当结果输出其中data字段就是我们要的flag。如果你在执行修改过程中发现某一步页面报错99%是字段名或表名大小写写错了或者字段类型定义不完整。用show columns from words或desc words复查即可。5. 数据获取与Flag提取实战5.1 完整payload流程整理为了不让大家看晕我在这里把从show tables到拿到flag的完整命令串整理一遍。拿到题目后按顺序提交即可。注意每一条都要单独提交一次不是写在一行里提交1确认注入点提交1 order by 2--确认列数提交1 union select 1,2--确认select被过滤提交1;show databases;--查看数据库提交1;show tables;--查看表名提交1;show columns from words;--和1;show columns from 目标表名;--查看字段提交1;alter table words rename to words2;--把原表改名提交1;alter table 目标表名 rename to words;--把目标表改名提交1;alter table words change flag data varchar(100);--改字段名提交1;alter table words add id int;--增加id字段提交1或1到正常页面读取flag。其中第4步和第5步的顺序可以颠倒只要能确定表名就行。第8步的“目标表名”就是你从show tables里看到的那个带随机后缀的表不要照抄我举例的名字要以实际页面输出为准。5.2 Flag长什么样正常查询返回结果后页面上会显示一行数据data字段的值通常就是flag格式一般是ctf{...}或者flag{...}。复制它提交到攻防世界的flag框里这道题就算过关了。如果页面返回结果为空别急着怀疑哪里错了先检查一下是不是原words表改名不成功再检查目标表的字段是否真的改成了data。最有效的排查方法是再走一遍1;show columns from words;--看看当前words表的结构到底长什么样。5.3 用Burp Repeater快速重放手工改payload需要在页面上反复刷新效率很低。我的实操习惯是复制Burp里截获的原始POST请求把inject参数的值改成上述payload然后发送到Repeater。在Repeater里我可以直接修改请求体把每个payload按顺序发送响应结果一目了然。这个方式特别适合连续查看show和desc的输出因为页面刷新可能因为编码问题导致输出显示不完整而Burp能显示原始响应更稳定。举个例子在Repeater的请求体里修改为inject1%27%3Balter%20table%20words%20rename%20to%20words2%3B--%2B点击发送然后把请求体改成下一个payload再发。这种方式看起来麻烦但实际操作比来回点页面快得多。5.4 为什么要改表名而不是直接查目标表可能有朋友会问为什么不直接执行1;select * from 目标表;--原因很简单select被过滤了我们无法在注入payload里写出任何包含select的语句。所以只能曲线救国先通过alter table把目标表的结构改成和正常查询表一致让程序自己用正常的select语句把数据查出来。这个思路也叫“间接查询”在很多WAF绕过的实战案例中都会用到核心思想是与其和WAF硬刚不如改变数据库结构让业务逻辑帮你把数据吐出来。这个思路其实很有代表性。真实环境里的WAF不可能过滤所有SQL关键字否则业务本身也跑不动。出题人设计这道题就是为了展示这种绕过思路理解了它以后再遇到类似的“某关键字被过滤”的题目就不会只能干瞪眼。6. 常见问题与排错心得6.1 常见问题速查表我在打这道题的时候整理了一张问题排查表遇到卡顿可以对照着看现象可能原因解决方法提交1无报错输入点不是注入点或后端参数名不对用Burp确认参数名检查请求方法order by 3不报错列数大于等于3继续试order by 4、5union select被拦截select被过滤放弃union改用堆叠注入show tables无输出提交内容被注释符影响尝试--、#、--不同注释方式alter table报错表名大小写不对用show tables重新确认表名change字段报错字段定义不完整明确写出字段类型如varchar(100)最终查询没输出flag原表未成功改名用desc words检查当前表结构页面返回500多条语句导致脚本异常检查是否是注释符没闭合好6.2 我的几条排错心得第一不要怕报错。在这道题里报错不是坏事而是最重要的反馈信号。比如order by 3报错说明列数是2alter table报错说明你表名拼错了。很多新手在页面看到英文报错就慌其实只要把报错信息复制下来翻译一下问题就解决一半了。第二每个payload都要单独提交。堆叠注入虽然能执行多条语句但我在实际操作中不推荐在一条payload里塞太多东西尤其是alter table这种修改结构的操作一旦某一步失败后面全乱。最好的习惯是一条payload只做一件事做完立刻用desc或show columns确认。第三记录你已经改了什么。因为在同一道题里你会执行多次表结构修改如果不记录很容易出现“我到底把哪个表改名了”的混乱。我个人的习惯是直接在一张纸上或者文本编辑器里按顺序记录执行过的每一条alter语句后面排错时逐条回溯。第四关掉浏览器翻译插件。不是因为翻译插件有问题而是因为某些插件会在提交表单时修改请求体导致注入payload被编码转换。我用Chrome时遇到过几次这种情况换成Burp Repeater之后再也没有这种烦恼了。6.3 补充一种备选打法如果改表名卡住这道题还有另一种打法就是利用handler语句。MySQL的handler是直接读取表数据的底层接口它可以不通过select来完成数据读取。比如1;handler words open;handler words read first;--不过这个打法在Supersqli里往往不如改表名方便因为handler的输出也需要通过正常查询才能显示。但了解这个命令没有坏处万一以后遇到连alter都被过滤的题目handler可能是救命稻草。当然这属于扩展知识本轮题目用不到。6.4 关于工具和扩展学习打完这道题建议你顺手把下面几个知识点补一下堆叠注入的原理和限制information_schema库中的表结构MySQLalter table语法show语句和desc语句的使用差异WAF绕过中的“改结构”思路。这些内容加起来才是这道题真正想教给你的东西。单纯拿到flag不算完能把整个思路清楚地讲给别人听才算真正掌握了。7. 个人踩坑回顾与总结最后再说点题外话。我最初打这道题的时候其实卡了很久卡住的点不是第三部分的select过滤而是第六部分的alter table words change flag data varchar(100)这一步。当时我没写字段类型直接写了change flag data结果MySQL报语法错误我以为自己思路错了又回头去试各种绕select的姿势浪费了很多时间。后来重新看报错信息才发现只是字段定义不完整。所以这里专门提醒大家写change的时候一定要把完整的字段定义写出来少一个字符都不行。还有一个小技巧最终查询flag的时候如果你提交1没返回数据可以试试提交1 or 11--。在改完表结构之后这个payload能触发正常查询逻辑返回表中的所有行在某些情况下比提交单个数字值更可靠。当然如果or被过滤就改回1或者1以实际页面反馈为准。说实话Supersqli这道题难度不算高非常适合新手练手。它把一个真实的WAF绕过场景浓缩到了一个页面上把堆叠注入、改表结构、字段操作这些MySQL知识全部串了起来。如果你能不看wp独立打完这道题那你在SQL注入方面的基础就已经超过很多人了。打得多了之后我发现CTF题目的答案本身并不重要重要的是那个“卡住然后想通”的过程。Supersqli的整个过程其实也是很多真实业务场景的缩影过滤器不可能覆盖所有语法总有一条路能绕过去。做安全的人说白了就是在跟规则设计者赛跑而这场比赛从来没有终点线。