FEATURED · 精选文章

SQL注入联合查询实战:从萌新22看手工注入标准流程

发布时间 / 2026/9/16 1:32:48
来源 / 创域科博编辑部
栏目 / 资讯中心
SQL注入联合查询实战:从萌新22看手工注入标准流程 1. 打开萌新22之前你需要知道这道题在考什么ctfshow的萌新系列我一直觉得是给那些刚知道SQL注入四个字怎么写但不知道从哪下手的人准备的。萌新22也不例外它考的方向非常集中SQL注入里的联合查询。如果你做过级客巅峰的web4会发现这两道题的路子几乎是一个模子刻出来的——一个明晃晃的GET参数一个看起来平平无奇的页面剩下的全看你有没有一套标准的注入流程。我第一次做这道题的时候其实走了一段弯路。打开页面看到参数第一反应是掏出sqlmap跑一把结果跑半天不出东西反倒是手工试了几下就拿到flag了。这也是我后来强烈建议所有萌新选手手工过一遍这类题的原因工具永远替代不了你对注入原理的理解尤其是当你面对的是专门用来训练新手的题目时出题人根本不会上太复杂的过滤所有答案都藏在最基础的判断流程里。动手之前我建议你先确认自己掌握了三样东西。第一SQL的基本语法至少要明白SELECT语句是怎么拼出来的第二information_schema这个系统库是干什么用的很多新手卡在这一步不知道去哪找表名和列名第三HTTP请求的基本概念至少要知道GET参数是跟着URL一起传的表单提交的数据最终会变成什么样的参数键值对。这三样东西不需要多深入但是缺一样后面每一步都会走得很痛苦。这道题和级客巅峰web4相似的点在于它考察的就是最纯粹的联合注入链路判断注入点、测列数、找显示位、爆库名、爆表名、爆列名、拿数据。整个流程没有花里胡哨的绕过就是SQL注入的标准教程题。正因为如此它其实是一道非常适合用来检验自己基础扎不扎实的题——如果你能不看任何writeup独立做出来说明你对联合查询的掌握已经到了条件反射的级别。1.1 萌新系列出题的底层逻辑萌新系列的题目从命名就能看出来定位是给完全零基础的人练手的。这里的练手不是说题目简单到没有技术含量而是说每一道题都严格对应一个明确的知识点你只要把那个知识点吃透了就一定做得出来不会出现我明明知道是注入但不知道怎么绕过的挫败感。萌新22对应的知识点就是标准的union联合注入。整个题目环境会模拟一个非常典型的把用户输入直接拼进SQL语句的漏洞场景参数没有任何过滤或者过滤得很浅目的就是让你把注意力集中在注入手法的完整链路上而不是被各种WAF规则带偏节奏。1.2 动手前建议准备的东西做这类题我建议至少准备三样东西。一个是Burp Suite用来抓包和反复修改请求比在浏览器地址栏里改参数效率高得多一个是浏览器的开发者工具至少要看明白网络面板里的请求和响应再一个是本地的命令终端如果题目环境允许直接用curl发请求很多调试过程会更快。至于sqlmap我只建议在手工链路全部跑通之后拿来验证结果不建议一上来就依赖它。2. 注入点的确认先用手再用工具拿到题目后的第一步永远是确认注入点而不是猜测过滤规则。这道题的页面结构很简单一个参数传进去返回的内容会直接反映在页面上。这时候你只需要做三组测试就能非常清楚地判断出注入类型和闭合方式。2.1 第一组测试正常参数与异常参数的响应差异先传一个正常参数比如?id1记录下页面返回了什么内容内容里有哪些字段是来自数据库的。然后传一个不存在的值比如?id9999看看页面是不是变成了空内容或者提示无数据。这个对比很重要它告诉你一个核心信息参数的值确实被查询用来过滤数据了而且查询结果会直接反映到前端。传了?id9999没有正常数据之后我心里就有底了——既然查询结果会显示在页面上只要能用union select构造出一个什么东西都查不到、然后执行我们指定的查询的状态就能拿到所有想拿的数据。这就是联合查询注入的基本前提。2.2 第二组测试单引号判断字符型闭合接着传?id1重点观察页面的反应。如果出现了数据库报错信息说明后端的SQL语句被破坏了而且报错内容通常会把拼好的SQL语句片段暴露出来这时候你就能直接看到开发者把参数包在了什么符号里。如果页面没有任何反应只是返回空内容说明报错被屏蔽了但注入依然是存在的。这道题传了单引号之后是有报错回显的SQL语句大概是select * from xxx where id1这样的形态。看到双单引号紧挨着基本可以确定后端是用把参数包起来的。这是字符型注入最典型的特征。2.3 第二组测试的延伸判断数字型的场景顺带说一句数字型的判断方法因为虽然这道题是字符型但很多同类题目用的是数字型。数字型的标志是你传?id1 and 11有数据传?id1 and 12没有数据因为数字型注入里参数直接拼在SQL语句中and是作为逻辑运算符参与查询的。而字符型注入里如果你只传and 11而不闭合前面的单引号这个and会变成字符串内容的一部分页面不会有任何变化。很多人分不清这两种类型其实就是没有做这组对比测试。你先分别传一个恒真条件和一个恒假条件看返回内容是否出现差异差异存在就是数字型完全没差异就是需要闭合引号的字符型。3. order by确定列数union select拿到回显位确认了注入类型之后接下来的流程就非常标准化了。先测列数再找显示位然后才是爆数据。我见过不少新手跳过测列数直接union select然后死活出不来数据原因就是前后列数不一致导致union语法错误。这道题里测列数的方法就是最经典的order by递增法。3.1 order by递增探测的正确姿势在id参数后面依次拼接order by 1、order by 2、order by 3……每拼一个就刷新页面看反应。如果当前列数等于或者大于实际列数页面正常回显一旦递增到超过实际列数数据库就会报错提示类似Unknown column的信息。这道题我在order by 3的时候一切正常到order by 4立刻报错所以查询结果一共是3列。这里有一个细节值得注意order by 4报错的时候报错信息可能是中文提示也可能是英文提示但无论提示内容是什么页面返回的状态都跟前几次有明显不同。我用BurpSuite对比几次响应内容时看得特别清楚——前3次响应都是完整的正常页面第4次响应直接变成了错误页面或者空响应。这个前后差异本身就是最可靠的判断信号。3.2 union select前面为什么要跟一个假条件列数确定之后就要构造union select了。这中间有一个关键动作是在union前面的原查询上拼接一个恒为假的条件最常见的写法是and 12或者字符串类型的 and 12。这么做的原因很简单让原查询查不到任何数据这样后面的union结果就有机会占用回显位否则页面只会显示原查询的结果你拼接的查询数据会被淹没在正常数据下面。我在写payload的时候会把前面的参数值改成不存在的值比如?id1 and 12这样前面的select结果集为空union select的内容就会直接作为第一行数据显示出来。有些新手习惯用-1作为参数值这在数字型注入里也完全可行本质思路一样就是让前部分查询落空。3.3 information_schema标准四步走显示位确定之后剩下的就是固定套路了。我在浏览器里反复测试之后确定第2个和第3个显示位都能正常回显数据接下来就是标准四步走第一步爆数据库名payload是?id1 and 12 union select 1,database(),3--看到返回了数据库的名字通常是ctfshow之类的名字也可能是web开头或者随机的库名反正从这一步开始你就能切实感受到数据在手的感觉了。第二步爆表名payload是?id1 and 12 union select 1,group_concat(table_name),3 from information_schema.tables where table_schemadatabase()--这里用group_concat把多行表名拼成一行页面显示才会完整。我在这一步看到的表名单里通常有一个跟flag相关的表类似flag或者ctf_flag这样很直白的命名。第三步爆列名payload是?id1 and 12 union select 1,group_concat(column_name),3 from information_schema.columns where table_name刚才看到的表名--注意表名要用单引号包起来因为这本身是一个字符串条件。第四步爆数据payload是?id1 and 12 union select 1,flag字段,3 from 表名--flag就直接显示在页面上了。这四步之间每一步的输入都依赖于上一步的输出所以做题的时候我习惯开两个窗口一个窗口放payload另一个窗口记录上一步拿到的东西。新手经常在这一步栽跟头就是在第一步和第二步之间反复横跳表名忘记加引号、库名写死成database()的下划线拼错之类的低级错误反而拖慢了速度。4. 过滤拦截萌新22里最常见的两道坎联合查询跑通之后很多新手会觉得自己已经会SQL注入了其实真正的分水岭在过滤绕过这一步。萌新22这道题说实话过滤很浅但它确实设置了两个新手最容易卡住的点这两个点如果你没想明白后面的题目基本寸步难行。4.1 空格与注释符的替换思路第一个坎是空格被过滤。我在构造payload的时候一开始老老实实用空格分隔SQL关键字结果页面返回异常。试了几次之后意识到后端很可能把空格直接替换成了空字符串。这种过滤的绕过思路非常固定用SQL支持的注释符代替空格最常见的就是/**/比如union/**/select。这个替换的前提是你要理解为什么/**/能代替空格。SQL标准里注释符在语句中起到的是分隔符的作用MySQL会把/**/当成一个词法单位忽略掉但它前后相邻的关键字仍然会被识别为独立的token。说白了数据库在解析的时候根本不关心关键字之间是空格还是注释它只关心token是否被正确分割。所以union/**/select和union select在MySQL看来是完全等价的。实际测试中我在这个点耗费了不少时间因为payload里不只一处空格如果只替换一个地方其他地方照样报错。我的排查方法是把整个SQL语句拆成几段每一段单独测试确认这一段能跑通了再拼接下一段。比如先测试1/**/union/**/select/**/1,2,3--能不能正常回显能回显就说明空格过滤和引号闭合都解决了再一步步往后面加条件。4.2 关键字被过滤时的绕过思路第二个坎是部分关键字被过滤最常见的是过滤select或者union。在这里我要啰嗦一句看到过滤之后别直接慌先测试一下过滤是怎么实现的。常见的过滤实现方式有大小写不敏感的替换、大小写敏感的精确匹配、只过滤一次等不同的实现方式对应不同的绕过手段。如果后端只是替换了一次关键字那最简单的办法就是双写比如ununionion替换掉中间那个union之后就还原成了union。如果后端是大小写不敏感地替换那就试试内联注释/*!select*/这种MySQL特有的写法把关键字放在/*!和*/之间MySQL会当作正常代码执行。如果连注释里的内容都被过滤了那就要考虑用等价函数或者语法来替代但萌新22这个阶段基本用不上那么复杂的姿势。我在实际测这道题的时候卡在select关键字被过滤这个问题上整整绕了好几分钟后来才意识到后端过滤的是小写的select我改成SELECT就不拦了。这种大小写绕过在真实CTF里已经很少见了但在萌新题目里反而很常见因为出题人的目的是让你理解绕过这个动作本身而不是让你去挑战复杂的WAF规则。4.3 用报错信息反向推断过滤规则这里分享一个很有用的调试思路当你发现payload被过滤的时候可以利用页面的报错信息反推过滤规则。具体做法是故意构造一个能触发SQL语法错误的payload然后观察报错内容里到底保留了哪些字符。比如你提交sel/**/ect如果报错信息里出现了select说明/**/被还原成了空格或者被解析掉了如果报错信息里出现了selct说明中间的内容被整个删掉了。这个反推方法听起来土但实际做题时非常管用。报错信息是后端给你的内部反馈比任何猜测都靠谱。我在这道题里就是用这种方法确认了空格过滤是替换而非删除然后才放心地用/**/来绕过。5. 做题过程中真正容易卡住的地方写完payload整个链路该说点实在的了。我做这道题时踩过几个坑这些坑未必是题目设计的难点但确实是很多新手反复折腾的地方。我把它们单独列出来方便你对照排查。5.1 注入点明明存在为什么加单引号没反应第一种情况是传了?id1之后页面完全没变化既没有报错也没有数据异常。这时候先别急着怀疑注入点找错了第一步应该去看页面返回的HTTP状态码和响应长度。如果是200并且长度和正常请求完全一致那可能单引号被后端函数处理掉了比如addslashes或者mysql_real_escape_string这类转义函数。如果是302跳转或者500那说明确实有问题只是错误信息被屏蔽了。我在做萌新22的时候没遇到转义的情况因为这道题的参数是干净地拼进SQL语句的。但如果你在类似的萌新题目里遇到了单引号无反应的情况我的建议是换一种闭合思路比如试试双引号?id1或者试一下URL编码后的单引号%27有时候后端只是做了一层简单的字符替换编码绕过就直接通了。5.2 union select一直报错的排查顺序第二种情况是query的列数明明用order by测出来了但union select一直报错。我自己就犯过这个低级错误测出3列之后随手写了union select 1,2,3却发现返回的是原查询的数据而不是我构造的数据最后才想起来前面的查询没有用假条件置空。这个错很多人都会犯排查顺序应该是先看前段查询是否为空再看列数是否一致再看显示位是否在回显范围内。还有一种可能是显示位只有2个但你在union select里写了3列中间那列对应不上回显。这种情况在真实题目里很常见因为显示位取决于页面模板怎么渲染字段不一定跟数据库的真实列数一一对应。拿到显示位之后先测试每个位置的渲染情况比如union select 1,2,3看页面上哪几个数字被回显了再决定把数据写到哪个位置。5.3 Burp Suite调试和浏览器直接访问的差别最后说说工具选择。做这类注入题我非常不建议把全部message依赖在浏览器地址栏里。浏览器会自己做URL编码、会处理一些特殊字符还会把空格自动编码成%20有时候过滤规则是发生在Web层还是数据库层在浏览器里根本分辨不出来。用BurpSuite的Repeater你能看到完整的原始请求报文能自己控制每个字符的编码方式也能直接对比多个请求的响应差异。我在做这道题时就是用BurpSuite的Repeater反复发送payload通过观察响应数据包的长短来判断注入是否成功。响应长的说明数据被正常查出来了响应短的说明查询结果为空或者语法报错被吞掉了。这个看响应长度的习惯在做盲注题的时候尤其重要建议现在就养成。6. 从萌新22往后这套套路能迁移到哪些题萌新22做完之后别急着做下一道题先停下来把整套流程在脑子里过一遍。这道题最大的价值不是让你拿到flag而是帮你建立一条完整的SQL注入攻击链路。在之后的刷题路上你会发现不管题目怎么变这条链路的骨架几乎不会变变的只是过滤规则和闭合方式。6.1 一套通用的手工注入流程我自己在赛后复盘时整理的流程是这样的你可以参考第一步找到所有可能和数据库交互的参数包括GET参数、POST参数、Cookie、请求头第二步用单引号、双引号、反引号这些特殊字符去测试闭合方式第三步用order by测列数第四步构造union select找显示位第五步从information_schema拿库名、表名、列名的层级数据第六步如果union被过滤了考虑盲注用substr配合ascii逐字符判断。这套流程听起来简单但我见过太多人拿到一道新题就开始瞎试payload完全没有流程意识。比如拿到题先跑 or 11--发现没反应就慌了其实问题可能只是闭合方式是双引号而不是单引号。把流程固化成肌肉记忆看到一道题先分类再走流程效率会翻好几倍。6.2 和级客巅峰web4这类题的共同规律说回标题里提到的级客巅峰web4。这道题我做完之后专门去对比过发现两题的核心逻辑完全一样一个参数注入点一个字符型闭合一个标准的联合查询流程甚至过滤规则都差不多。这说明了一个问题很多CTF平台在基础web题目的设计上是有通用模板的出题人会在同一个模板上换不同的参数名、换不同的表名列名甚至换一层不同的过滤但核心考点不变。所以我认为萌新22这道题值得反复做两到三遍。第一遍正常做拿到flag第二遍故意给自己加难度比如禁用group_concat模拟空格过滤强行写成不用union的盲注版本。这样练下来面对同一类型的题目你会有完全不同的掌控感。最后再分享一个小技巧做完萌新22之后我习惯把整个注入语句的完整版本记录在本地笔记里包括每个payload的注释说明和适用场景。这不算什么高深的方法但当你刷到第三十道、第五十道sql注入题的时候回头看这些基础笔记会发现当初那些费劲理解的东西早就是条件反射级别的内容了。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻