第一关
先来判断是否可以在url上直接打开地址栏,post传入的内容是否直接会拼接到数据库语句中
?id=1%20%27and%201=2%20--%20ads
%20是空格的url的代码
具体操作流程如下
1,判断是否是注入类型
若输入?id=1页面正常,输入?id=1'页面报错,就证明了单引号闭合的字符型注入存在。
?id=1%20%27and%201=2%20--%20ads
?id=1%20%27and%201=1%20--%20ads
百分号27是单引号
看两个显示结果会不会变
2,判断字段
ORDER BY关键字的核心作用(MySQL 语法):
用于对查询结果集进行排序,语法格式为ORDER BY 字段名/字段索引。这里的关键特性:ORDER BY支持用数字「字段索引」代替字段名,数字N表示 “对查询结果的第 N 个字段进行排序”。
步骤 2:判断字段数(order by)
用order by N猜解查询的字段数量:
输入?id=1' order by 1--+(--+是注释符,让后面的 SQL 失效),页面正常。
输入?id=1' order by 2--+,页面正常。
输入?id=1' order by 3--+,页面正常。
输入?id=1' order by 4--+,页面报错,说明字段数是3。
3,确定回显位置(-1' union select 1,2,3--+)
输入?id=-1' union select 1,2,3--+,页面显示2和3,确定这两个位置为回显位。
数据库原理拆解
UNION SELECT联合查询的核心规则(MySQL 语法):用于将两个或多个
SELECT语句的查询结果集合并为一个结果集返回。必须满足核心前提条件:多个
SELECT语句查询的「字段数量必须相同」,且对应字段的数据类型兼容(否则报错)。例如:
SELECT 1,2,3 UNION SELECT 4,5,6;合法(均为 3 个字段),SELECT 1,2 UNION SELECT 3,4,5;非法(字段数不一致)。
为什么要用
id=-1?原始 SQL 中
id='1'会查询到有效数据(存在id=1的用户),此时UNION SELECT的结果集会被原始查询结果覆盖,页面只显示原始数据,无法看到联合查询的内容。传入
id=-1,拼接后的 SQL 为SELECT * FROM users WHERE id='-1' UNION SELECT 1,2,3--+;,由于数据库中不存在id=-1的用户,原始查询结果集为空。根据 MySQL 联合查询的规则:当第一个
SELECT结果集为空时,会直接显示第二个SELECT(联合查询)的结果集,这是我们能看到1,2,3的关键。
回显位置的含义:
页面最终显示
2和3,说明原始 SQL 查询结果集中,第 2 个和第 3 个字段的内容会被渲染到页面上(第 1 个字段可能不显示或被隐藏)。后续我们只需要将
2和3替换为实际的查询语句(如查数据库名、账号密码),就能让查询结果在页面上显示出来,这是联合查询注入的核心价值。
4:查询数据库信息(核心注入操作)
操作回顾
通过替换回显位的2,3,依次查询数据库名、表名、字段名、账号密码。
数据库原理拆解(分步骤解析)
1. 查询当前数据库名(?id=-1' union select 1,database(),3--+)
database():MySQL 内置系统函数,作用是返回当前正在使用的数据库名称(即原始 SQL 所属的数据库)。拼接后的 SQL 为:
SELECT * FROM users WHERE id='-1' UNION SELECT 1,database(),3--+;原理:联合查询的第 2 个字段被替换为
database(),数据库执行该函数后返回当前数据库名(通常是security),并通过页面的回显位展示出来。
2. 查询数据库中的表名(group_concat(table_name)+information_schema.tables)
核心语法:
SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema=database();各部分原理拆解:
information_schema.tables:系统数据库information_schema中的tables表,存储了 MySQL 中所有数据库的表元数据(包括数据库名table_schema、表名table_name、表类型等)。WHERE table_schema=database():过滤条件,只查询当前数据库(database()返回的结果)中的表,避免查询到其他数据库(如mysql、information_schema)的表。group_concat(column_name):MySQL 内置聚合函数,作用是将多行查询结果拼接为一行字符串返回(用逗号分隔)。若不用
group_concat(),直接SELECT table_name,联合查询只会返回单行结果(默认只取第一行),无法看到所有表名;用
group_concat()可以将所有表名(emails,referers,uagents,users)拼接为一行,一次性在回显位展示完整,这是注入中高效查询多行数据的核心技巧。
拼接后的 SQL 执行后,会从
information_schema.tables中提取当前数据库的所有表名,拼接后通过回显位展示。
3. 查询users表的字段名(group_concat(column_name)+information_schema.columns)
核心语法:
SELECT group_concat(column_name) FROM information_schema.columns WHERE table_schema=database() AND table_name='users';各部分原理拆解:
information_schema.columns:系统数据库information_schema中的columns表,存储了 MySQL 中所有表的字段元数据(包括数据库名table_schema、表名table_name、字段名column_name、字段类型等)。过滤条件
table_schema=database() AND table_name='users':精准定位「当前数据库」中的「users表」,只查询该表的字段信息。同样使用
group_concat(column_name)将所有字段名(id,username,password)拼接为一行,通过回显位完整展示。
4. 查询users表的账号密码(group_concat(username,':',password))
核心语法:
SELECT group_concat(username,':',password) FROM users;各部分原理拆解:
users表:是 SQLi-Labs 中存储管理员账号密码的核心业务表(从步骤 2 中查询得到)。username,':',password:MySQL 中支持用逗号,拼接字符串,这里将username(用户名)、分隔符:、password(密码)拼接为一个字符串(如admin:admin),方便阅读。group_concat():将users表中所有行的账号密码拼接为一行,一次性展示所有数据,避免多次查询,高效获取全部核心信息。拼接后的 SQL 执行后,数据库会从
security数据库的users表中提取所有账号密码,通过回显位展示,完成核心注入目标。
总结
整个注入流程的核心是篡改后台原始 SQL,利用 MySQL 语法特性(注释符、
ORDER BY、UNION SELECT、系统表 / 函数)实现非法查询。关键节点:闭合单引号(突破字符型注入限制)→ 猜字段数(满足联合查询前提)→ 找回显位(获取查询结果出口)→ 查元数据(通过
information_schema获取核心信息)。所有操作的底层支撑都是 MySQL 的基础语法和系统表 / 函数的特性,这也是 SQL 注入的核心
所以为
发现这个界面加载粗我要
第二关
,和上面只有闭合方式的区别,闭合方式为双引号
第三关
,和上面只有闭合方式的区别,闭合为单引号和括号
第四关
,和上面只有闭合方式的区别,闭合为单引号和括号
SQLi-Labs 5-7 关原理讲解(注入类型与核心技巧)
这三关分别对应报错注入、盲注(布尔型)、盲注(时间型)
第五关
核心特征
页面只返回 “正确 / 错误” 提示(无数据回显),但会输出 SQL 语法错误信息(如 “you have an error in your SQL syntax”)。
SELECT * FROM users WHERE id='$id' LIMIT 0,1;
利用MySQL 报错函数(如extractvalue()、updatexml()),让数据库在执行恶意 SQL 时抛出错误,并将查询结果嵌入错误信息中返回,实现 “无回显下获取数据”。
常用报错函数:
extractvalue()语法:
extractvalue(1, concat(0x7e, 查询语句, 0x7e))- 作用:从 XML 文档中提取值,当第二个参数包含特殊字符(如
~)时,会抛出错误并显示参数内容。 - 示例(查数据库名):
plaintext
页面会返回错误:?id=1' and extractvalue(1,concat(0x7e,database(),0x7e))--+XPATH syntax error: '~security~',其中security就是数据库名。
- 作用:从 XML 文档中提取值,当第二个参数包含特殊字符(如
updatexml()原理与
extractvalue()类似,语法:updatexml(1, concat(0x7e, 查询语句), 1)。
核心逻辑
通过构造包含报错函数的 SQL,强制数据库输出错误信息,间接获取查询结果(无需页面回显数据)。
第六关
核心特征
页面仅返回 “存在数据” 或 “不存在数据”(如 “you are in”/“you are not in”),无任何错误提示或数据回显。
原始 SQL
sql
SELECT * FROM users WHERE id="$id" LIMIT 0,1;(字符型注入,双引号闭合)
原理
利用SQL 逻辑判断的布尔结果(true/false),通过 “逐字符猜解” 的方式获取数据。
核心技巧:
逐字符判断(利用
substr()、ascii()函数)substr(目标字符串, 位置, 1):截取字符串指定位置的字符;ascii(字符):将字符转换为 ASCII 码值。示例(猜数据库名第 1 个字符):
plaintext
?id=1" and ascii(substr(database(),1,1))>100--+若页面显示 “you are in”,说明数据库名第 1 个字符的 ASCII 码大于 100;
逐步缩小范围,最终确定字符(如
security的第 1 个字符s的 ASCII 码是 115)。
核心逻辑
通过构造逻辑判断语句,根据页面的 “存在 / 不存在” 提示,逐个字符推导目标数据(效率低,但适用于无回显、无报错的场景)。
第七关
核心特征
页面无任何回显、无错误提示(无论 SQL 是否正确,页面均显示相同内容)。
原始 SQL
sql
SELECT * FROM users WHERE id=((('$id'))) LIMIT 0,1;(字符型注入,单引号 + 括号闭合,需用')))闭合)
原理
利用MySQL 的延时函数(sleep(N)),通过 “是否延时” 判断 SQL 逻辑的布尔结果,进而逐字符猜解数据。
核心技巧:
延时判断(结合
if()函数)语法:if(条件, sleep(3), 1)作用:若条件为真,数据库延时 3 秒执行;若为假,立即执行。
示例(猜数据库名第 1 个字符):
plaintext
?id=1'))) and if(ascii(substr(database(),1,1))=115, sleep(3), 1)--+若页面加载时间超过 3 秒,说明条件为真(数据库名第 1 个字符是
s);若立即加载,说明条件为假,需调整字符的 ASCII 码值。
第八关
第 8 关:布尔盲注(单引号闭合,无任何回显增强版)
核心特征
- 页面仅返回两种状态:
You are in...........(条件为真)、无该提示 / 空白页面(条件为假),无错误回显、无数据回显。 - 与第 6 关核心逻辑一致,仅闭合方式不同,属于纯布尔盲注场景。
原始 SQL
sql
SELECT * FROM users WHERE id='$id' LIMIT 0,1;(字符型注入,单引号闭合,无报错输出,是第 6 关的单引号版本)
原理
与第 6 关布尔盲注核心原理完全一致,利用SQL逻辑判断的布尔结果,通过substr()(截取字符)、ascii()(转换 ASCII 码)逐字符猜解数据。
- 核心差异:第 6 关是双引号闭合,第 8 关是单引号闭合,注入语句前缀需对应调整。
- ?id=1' and ascii(substr(database(),1,1))=115--+
第九关
第十关
第十一关
- 注入点从URL 的 GET 参数转移到页面的 POST 表单参数(用户名
uname、密码passwd)。 - 页面有登录成功 / 失败提示(
Your Login name or Password is wrong),支持报错注入或布尔盲注。
原始 SQL
sql
SELECT * FROM users WHERE username='$uname' AND password='$passwd' LIMIT 0,1;(字符型注入,单引号闭合,POST 参数传递,无数据回显但有登录状态反馈)
原理
POST 注入与 GET 注入的核心区别:
GET 注入:参数通过 URL 传递,可直接在地址栏构造语句。
POST 注入:参数通过 HTTP 请求体传递,需借助工具(如 Burp Suite)或浏览器开发者工具构造表单数据。
注入核心逻辑:
与 GET 型字符注入一致,先闭合单引号,再构造注入语句,最后用注释符注释多余内容。
示例(用报错注入查数据库名,在
uname字段输入):plaintext
admin' and extractvalue(1,concat(0x7e,database(),0x7e))--+密码字段可任意输入(如
123),提交后页面会返回报错信息,包含当前数据库名。
核心逻辑
注入原理与 GET 型注入一致,仅参数传递方式不同,需掌握 POST 表单数据的构造方法。
第十二关
- 注入点为 POST 表单参数(
uname、passwd),与第 11 关场景一致。 - 闭合方式为双引号 + 括号(
id="($id)"),是闭合类型的进阶场景。
原始 SQL
sql
SELECT * FROM users WHERE username=("$uname") AND password=("$passwd") LIMIT 0,1;(字符型注入,双引号 + 括号闭合,POST 参数传递)
原理
- 闭合核心技巧:需要先闭合双引号,再闭合括号,才能让后续注入语句生效。
- 闭合格式:
"(闭合双引号)+)(闭合括号),即admin")。
- 闭合格式:
- 示例(用报错注入查数据库名,在
uname字段输入):plaintext
admin") and extractvalue(1,concat(0x7e,database(),0x7e))--+- 提交后,页面返回报错信息,提取其中的数据库名即可。
第十三关
核心特征
原始 SQL
sql
SELECT * FROM users WHERE username=('$uname') AND password=('$passwd') LIMIT 0,1;(字符型注入,单引号 + 括号闭合,POST 参数传递,无登录状态反馈,仅支持报错注入)
原理
- 注入点为 POST 表单参数,闭合方式为单引号 + 括号。
- 页面无登录成功反馈,仅在 SQL 语法错误时返回报错信息,无其他状态差异,属于POST 型报错盲注。
- 闭合核心技巧:先闭合单引号,再闭合括号,即
admin')。 - 注入核心逻辑:
- 由于无登录状态反馈,无法使用布尔盲注,只能依赖报错注入获取数据。
plaintext
admin') and extractvalue(1,concat(0x7e,database(),0x7e))--+- 提交后,页面会抛出 XPATH 语法错误,包含当前数据库名,实现无状态反馈下的数据获取。
核心逻辑
复合闭合方式 + POST 注入 + 报错盲注的结合,强化 “根据页面反馈选择注入方式” 的能力。
第十四关
- 注入点为 POST 表单参数,闭合方式为纯双引号,与第 6 关、第 10 关的双引号闭合一致。
- 页面有登录成功 / 失败提示,支持报错注入或布尔盲注,是 POST 型双引号注入的基础场景。
原始 SQL
sql
SELECT * FROM users WHERE username="$uname" AND password="$passwd" LIMIT 0,1;(字符型注入,双引号闭合,POST 参数传递,有登录状态反馈)
原理
- 闭合核心技巧:直接用双引号闭合原始字符串,即
admin"。 - 示例(用报错注入查数据库名,在
uname字段输入):plaintext
admin" and extractvalue(1,concat(0x7e,database(),0x7e))--+- 密码字段任意输入,提交后页面返回报错信息,提取数据库名即可。
核心逻辑
POST 注入的基础双引号版本,与 GET 型双引号注入原理一致,进一步巩固 POST 表单的注入技巧。
第十五关
第十七·关·
原理
- 场景差异:UPDATE 注入与 SELECT 注入的核心区别
- SELECT 注入:目的是 “查询数据”,语句构造围绕
SELECT/UNION SELECT/ 报错函数。 - UPDATE 注入:目的是 “通过报错获取数据”(无需更新真实密码),语句构造围绕 “破坏 UPDATE 语法,嵌入报错函数”,利用 UPDATE 执行时的语法错误返回查询结果。
- SELECT 注入:目的是 “查询数据”,语句构造围绕
- 核心前提:用户名需存在(如
admin、dumb),否则 UPDATE 语句不会执行,注入无效。 - 核心技巧:
- 闭合方式:单引号闭合密码字段的原始字符串,嵌入报错函数,最后用
--+注释多余内容。 - 报错函数依旧使用
extractvalue()/updatexml(),与第 5 关报错注入原理一致。
- 闭合方式:单引号闭合密码字段的原始字符串,嵌入报错函数,最后用
- 示例(猜数据库名,用户名填
dumb(存在的账号),新密码字段输入):plaintext
123' and extractvalue(1,concat(0x7e,database(),0x7e))--+- 拼接后的 UPDATE 语句:
sql
UPDATE users SET password='123' and extractvalue(1,concat(0x7e,database(),0x7e))--+' WHERE username='dumb'; - 判断逻辑:执行 UPDATE 语句时,报错函数触发 XPATH 语法错误,页面返回错误信息,其中包含当前数据库名(
security),且不会真正修改dumb的密码(后续内容被注释)。
- 拼接后的 UPDATE 语句:
核心逻辑
基于 UPDATE 语句的报错注入,核心是 “利用存在的用户名触发 UPDATE 执行,通过报错函数间接获取数据,不破坏目标账号的原始数据”,是业务场景中常见的注入类型。
第十九关
核心特征
- 注入点是HTTP 请求头的 Referer 字段(请求来源页面标识),与第 18 关场景高度相似。
- 同样需要登录成功触发 INSERT 语句,页面回显 Referer 信息并支持报错反馈。
原始 SQL(登录日志记录)
sql
INSERT INTO security.referers (referer, ip_address, username) VALUES ('$referer', '$ip', '$uname');(字符型注入,单引号闭合,$referer对应 Referer 头)
原理
- 场景前提:同第 18 关,需先登录成功(如
dumb/dumb),触发 INSERT 语句。 - 注入逻辑:
- 与第 18 关完全一致,仅注入点从 User-Agent 改为 Referer 头。
- 核心操作(Burp 抓包):
- 抓取登录成功的请求包,找到
Referer字段。 - 修改 Referer 字段为注入语句:
plaintext
' and extractvalue(1,concat(0x7e,version(),0x7e))--+ - 发送请求后,页面返回报错信息,包含 MySQL 版本号。
- 抓取登录成功的请求包,找到
核心逻辑
与 User-Agent 头注入原理一致,进一步强化 “HTTP 头任意字段均可作为注入点” 的认知,关键是识别业务逻辑中被代入 SQL 的头信息。
第20关
- 注入点是HTTP 请求的 Cookie 字段(存储登录状态的
uname参数)。 - 登录成功后,页面会回显 Cookie 中的用户名信息,支持报错 / 联合查询注入。
原始 SQL(Cookie 验证)
sql
SELECT * FROM users WHERE username='$uname' LIMIT 0,1;(字符型注入,单引号闭合,$uname对应 Cookie 中的uname参数)
原理
- 场景前提:登录成功后,浏览器会保存
uname=dumb的 Cookie,页面通过读取该 Cookie 显示用户名。 - 注入逻辑:
- Cookie 中的
uname参数被作为字符串代入 SELECT 语句,通过单引号闭合后,可构造报错 / 联合查询语句。
- Cookie 中的
- 核心操作(Burp 抓包):
- 登录成功后,抓取页面请求包,找到
Cookie: uname=dumb字段。 - 修改 Cookie 为注入语句(以联合查询为例):
plaintext
uname=dumb' union select 1,2,database()--+ - 发送请求后,页面回显位会显示当前数据库名(
security)。
- 登录成功后,抓取页面请求包,找到
核心逻辑
注入点转移到 Cookie 字段,原理与 GET 参数注入一致,关键是 “识别 Cookie 参数的 SQL 代入逻辑,通过抓包修改 Cookie 值”。
第二十一关
核心特征
- 注入点是 Cookie 的
uname参数,但Cookie 值被 Base64 编码后传输。 - 闭合方式为单引号 + 括号,增加了解码与闭合的双重难度。
原始 SQL
sql
SELECT * FROM users WHERE username=('$uname') LIMIT 0,1;(字符型注入,单引号 + 括号闭合,$uname是 Base64 解码后的 Cookie 值)
原理
- 编码逻辑:
- 正常情况下,Cookie 中的
uname=dumb会被编码为uname=ZHVtYg==(dumb的 Base64 编码),后端接收后先解码,再代入 SQL。
- 正常情况下,Cookie 中的
- 注入逻辑:
- 需先构造闭合后的注入语句,再对语句进行 Base64 编码,才能被后端正确解码并执行。
- 核心操作(Burp 抓包):
- 构造注入语句(先闭合单引号 + 括号,再写联合查询):
plaintext
dumb') union select 1,2,database()--+ - 对该语句进行 Base64 编码,结果为:
ZHVtYicpIHVuaW9uIHNlbGVjdCAxLDIsZGF0YWJhc2UoKS0tKw==。 - 修改 Cookie 为:
uname=ZHVtYicpIHVuaW9uIHNlbGVjdCAxLDIsZGF0YWJhc2UoKS0tKw==。 - 发送请求后,页面回显位显示当前数据库名。
- 构造注入语句(先闭合单引号 + 括号,再写联合查询):
核心逻辑
Cookie 注入 + Base64 编码 + 复合闭合的结合,核心是 “先构造注入语句,再编码传输”,需掌握常见编码的处理方式。