本文教两种方法
1.万能密码 (看懂代码后直接套,只适用于low等级)
2.bp爆破 (操作规范专业一点)
前面先对方法大概总结与介绍,后面有各个等级的详细步骤与代码解释
方法简介
1.万能密码
其实就是admin' or '1'='1
万能密码的概念
"万能密码"通常指某些系统或设备中预设的通用密码,用于管理员维护或紧急访问。这类密码可能存在于路由器、监控设备、数据库或软件系统中。
常见场景中的万能密码
路由器后台
部分品牌路由器存在默认管理员密码,例如:
- 华为/中兴:
admin/admin - TP-Link:
admin/admin(早期型号) - 小米路由器:部分型号需通过手机APP绑定。
数据库系统
MySQL早期版本可能存在空密码或root/123456等弱密码。
监控设备
海康威视、大华等设备的默认密码可能为admin/12345或空密码。
安全风险与注意事项
- 使用万能密码可能违反法律法规,未经授权访问系统属于违法行为。
- 厂商会通过固件更新修复默认密码漏洞,部分设备首次登录会强制修改密码。
- 建议通过正规渠道获取访问权限,如联系管理员或重置密码。
合法替代方案
若忘记密码,可尝试以下方法:
- 路由器:长按复位键恢复出厂设置,重新配置。
- 数据库:通过配置文件或安全模式重置密码。
- 监控设备:联系厂商技术支持提供身份验证后重置。
注:具体密码因设备和版本而异,需参考官方文档或合法授权途径。
2. BP爆破详解
BP爆破(Buffer Overflow Exploitation)是一种常见的漏洞利用技术,主要针对存在缓冲区溢出漏洞的程序。以下是BP爆破的详细步骤和技术要点:
2.1 漏洞原理
缓冲区溢出发生在程序向固定长度的缓冲区写入超过其容量的数据时,导致相邻内存区域被覆盖。攻击者通过精心构造的输入数据可以:
- 覆盖函数返回地址
- 修改关键变量值
- 执行任意代码
典型漏洞场景:
c
void vulnerable_function(char *input) {
char buffer[64];
strcpy(buffer, input); // 无边界检查的拷贝操作
}
2.2 爆破步骤
-
漏洞确认
- 使用fuzzing工具发送异常长度数据
- 观察程序崩溃时的寄存器状态(EIP值)
- 确认崩溃是否由可控输入导致
-
偏移量计算
pythonpattern_create.rb -l 200 # 生成定位字符串 pattern_offset.rb -q [崩溃时EIP值] -
坏字符检测
- 发送包含\x00-\xff所有字符的payload
- 排除导致截断或异常的字符(如\x00空字节)
-
shellcode构造
nasmxor eax,eax push eax push 0x68732f2f ; "//sh" push 0x6e69622f ; "/bin" mov ebx,esp mov ecx,eax mov edx,eax mov al,0xb int 0x80 -
利用框架示例(Python)
pythonfrom struct import pack offset = 76 ret_addr = pack("<I", 0xbffff7a0) # 栈地址 nop_sled = "\x90"*32 payload = "A"*offset + ret_addr + nop_sled + shellcode
2.3 防护绕过技术
- ASLR绕过:通过信息泄露获取内存地址
- DEP绕过:使用ROP(Return-Oriented Programming)
- Stack Canary绕过:通过格式化字符串泄露canary值
2.4 实际案例
某FTP服务器漏洞利用过程:
- 发送超长PASS命令触发溢出
- 覆盖返回地址跳转到包含shellcode的缓冲区
- 获取系统shell权限
注意:BP爆破需要配合调试器(如GDB/Immunity Debugger)进行分析,实际攻击需获得合法授权。
DVWA暴力破解详解
DVWA(Damn Vulnerable Web Application)是一个用于安全测试的漏洞练习平台,其中包含暴力破解(Brute Force)漏洞模块。以下是针对DVWA暴力破解的详细解析和操作方法。
暴力破解原理
暴力破解是一种通过尝试大量可能的用户名和密码组合来获取系统访问权限的攻击方式。DVWA的暴力破解模块模拟了这种漏洞,允许测试者通过工具或脚本尝试不同的凭证组合。
环境准备
确保DVWA已正确安装并运行。登录DVWA后,将安全级别设置为"Low"或"Medium"以便进行测试。暴力破解模块位于DVWA的"Brute Force"选项下。
手动测试方法
打开DVWA的暴力破解页面,输入常见的用户名(如admin)和密码(如password)进行尝试。观察系统的响应,判断是否成功登录。
使用Burp Suite拦截登录请求,将请求发送到Intruder模块,设置攻击类型为"Cluster bomb",分别对用户名和密码字段进行暴力破解。
自动化工具使用
Hydra是一款流行的暴力破解工具,适用于DVWA测试。命令示例如下:
hydra -l admin -P passwords.txt localhost http-form-post "/vulnerabilities/brute/:username=^USER^&password=^PASS^&Login=Login:S=Welcome to the password protected area:H=Cookie: security=low; PHPSESSID=your_session_id"
其中passwords.txt是密码字典文件,your_session_id需替换为实际的会话ID。
防御措施
限制登录尝试次数,例如在多次失败后锁定账户或增加延迟。引入验证码机制,防止自动化工具的攻击。使用强密码策略,避免简单密码被破解。
安全级别调整
DVWA提供不同安全级别(Low/Medium/High),每个级别对暴力破解的防护措施不同。High级别可能包含更复杂的防御机制,如令牌验证或严格的速率限制。
注意事项
仅在授权环境下进行测试,避免对未授权系统实施暴力破解攻击。测试完成后,恢复DVWA的安全设置并清理测试数据。
low级别
方法一:万能密码admin' or '1'='1
代码解释

最核心的部分是
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
- 它直接把用户输入的
$user和$pass拼接到 SQL 语句里,没有任何转义或预处理,这就是 SQL 注入漏洞的根源。- 注意:
$pass是先做了md5()哈希处理再拼进去的,所以直接在密码字段注入是无效的 ,必须在$user字段注入!
✅ 适配这个源码的万能密码 payload
根据拼接逻辑,我们可以构造 $user 字段的值,让 SQL 条件恒为真:
1. 最通用的 payload(推荐)
Username 输入:
admin' or '1'='1
此时拼接后的 SQL 语句会变成:
SELECT * FROM `users` WHERE user = 'admin' or '1'='1' AND password = 'xxxxxx';
- 逻辑上,
or '1'='1'会让整个条件恒为真,不管密码是否正确,数据库都会返回用户数据,直接登录成功。
2. 带注释符的 payload(兼容性更强)
Username 输入:
admin' or 1=1 --
拼接后的 SQL 语句:
SELECT * FROM `users` WHERE user = 'admin' or 1=1 -- ' AND password = 'xxxxxx';
--是 SQL 注释符,会把后面的AND password = 'xxxxxx'直接注释掉,条件直接变成user = 'admin' or 1=1,恒为真。
3. 进阶 payload(支持 MySQL)
Username 输入:
admin' or 'x'='x'#
#是 MySQL 的注释符,效果和--一样,会屏蔽后面的密码验证逻辑。
❌ 为什么不能在密码字段注入?
源码里 $pass 会先执行 md5($_GET['password']);,再拼接到 SQL 里:
-
你输入的
' or 1=1会先被 MD5 加密,变成一串 32 位的哈希值,比如' or 1=1的 MD5 是b461b0525a0f07e84f181e0f4c70f7e3,拼接后变成:SELECT * FROM `users` WHERE user = 'admin' AND password = 'b461b0525a0f07e84f181e0f4c70f7e3'; -
这个哈希值在数据库里不存在,所以注入完全无效,必须在
username字段注入。
🛠️ 实操步骤(对应这个 low.php)
- 在 DVWA Brute Force 登录页面:
- Username: 输入
admin' or '1'='1 - Password: 随便填一个值(比如
123456)
- Username: 输入
- 点击
Login,直接登录成功,进入保护页面。 - 如果失败,可以用 Burp 抓包,修改请求里的
username参数为 payload,避免前端输入框的过滤。
💡 补充说明
- 这个漏洞只存在于 Low 难度,Medium/Impossible 难度会对输入做转义或使用预处理语句,无法直接用万能密码绕过。
- 如果你想在 Medium 难度下尝试,我可以帮你分析它的源码,给出对应的绕过方法。
方法二:bp爆破
设置 Positions
一、先搞懂原理
Intruder 的 Positions 是用来标记要爆破的参数位置 的。在你的场景里,我们只需要标记 password 字段,不需要标记 username 或其他内容。
二、完整设置步骤(对应你的 DVWA Low 场景)
步骤 1:发送请求到 Intruder
- 用浏览器访问 DVWA 的 Brute Force 页面,输入:
- Username:
admin - Password: 随便填一个值(比如
test)
- Username:
- 点击
Login按钮,Burp 会拦截这个请求。 - 在 Burp 的拦截界面,右键请求 → 选择
Send to Intruder(快捷键Ctrl+I)。
步骤 2:进入 Positions 标签页
在 Burp 的 Intruder 模块,点击上方的 Positions 标签,你会看到完整的 HTTP 请求内容。
步骤 3:清除默认标记
- 点击右侧的
Clear §按钮,把所有自动添加的§标记全部清空。- 目的:防止 Intruder 对
username、Login这些不需要爆破的字段进行替换。
- 目的:防止 Intruder 对
步骤 4:手动标记 password 字段
-
在请求中找到
password=这部分(你的请求里是password=password)。 -
用鼠标选中
password=后面的参数值(也就是password这几个字)。 -
点击右侧的
Add §按钮,你会看到选中的内容前后被加上了§,变成:plaintext
password=§password§- 这一步的作用:告诉 Intruder,这个位置是我们要替换成字典里密码的地方。
步骤 5:确认标记
此时你的请求应该只有 password 字段被 § 包裹,其他所有字段(username=admin、Login=Login)都保持原样,没有标记。
三、常见问题 & 避坑
- 标记错了怎么办? 直接重新
Clear §,再重新选一次password的值即可。 - 为什么要清空默认标记? Burp 默认会标记所有请求参数,但在这个场景里,
username是固定的admin,不需要爆破,只需要标记password。 - GET 和 POST 请求标记有区别吗? 没有区别:
- GET 请求:标记 URL 里的参数(比如
?password=xxx) - POST 请求:标记请求体里的参数(比如
password=xxx)你的截图里是 GET 请求,直接标记 URL 里的password参数值即可。
- GET 请求:标记 URL 里的参数(比如
四、设置完成后下一步
设置好 Positions 后,你就可以切换到 Payloads 标签页,加载你的密码字典,然后点击 Start attack 开始爆破了。

加载密码字典的完整步骤👇
一、先确认前置条件
- 你已经在 Positions 标签页 标记好了要爆破的
password字段(前面已经教过你了)。 - 你有一个密码字典文件(比如
passwords.txt,一行一个密码)。
二、加载字典的详细步骤
- 切换到 Payloads 标签页(就是你截图里右侧的页面)
- 确认
Payload type是 Simple list(默认就是这个,不用改) - 在
Payload configuration区域,点击Load...按钮- 在弹出的文件选择框里,找到你本地的密码字典文件(比如
passwords.txt),点击「打开」
- 在弹出的文件选择框里,找到你本地的密码字典文件(比如
- 加载成功后,你会看到所有密码都出现在下方的列表里了
- (可选)可以点
Deduplicate按钮,自动去重,避免重复尝试同一个密码 - (可选)也可以直接点
Add手动添加单个密码,或者Paste粘贴多行密码
- (可选)可以点
三、补充设置(提升爆破效率 & 成功率)
1. 选项设置(Options 标签页)
- Request Engine (请求引擎):
Number of threads:建议设为 5-10(线程数太高容易被服务器拦截,太低太慢)Retries on network failure:设为 2,网络波动时自动重试
- Payload Processing :如果你的字典里有特殊字符,可以在这里设置 URL 编码(
URL-encode),避免乱码 - Grep - Match :可以勾选
Flag results with response containing,输入incorrect password这类失败提示,方便后续快速筛选成功的请求
2. 关于 DVWA 的特殊注意事项
- Low 难度 :加载字典后直接
Start attack即可,通过Length列判断结果(长度和其他不一样的就是成功的) - Medium 难度 :会有 2 秒延迟,在
Options→Request Engine里可以设置Delay between requests为 2000ms,避免被服务器拦截 - Impossible 难度 :有
user_token防护,直接爆破会失败,需要额外配置 Burp 宏自动更新 token,需要的话我可以给你补充步骤
四、爆破完成后怎么找结果
攻击结束后,看 Results 表格:
- 找到 Status 码为 302/200 且 Length 与其他请求明显不同 的那一行,对应的
Payload就是正确密码 - 也可以看响应内容,找是否包含
Welcome to the password protected area这类登录成功的提示

附:一份 DVWA 暴力破解专用的简易字典
可以在我的资源中下载字典大全,


你可以直接复制保存为 passwords.txt 用:
password
admin
123456
12345678
qwerty
letmein
welcome
123456789
12345
monkey


可能出现的问题:怎么明明密码就是password但是没有破解出来
🔍 最可能的 5 个原因 & 解决办法
1. 你的爆破标记(Positions)错了(最常见)
看你之前的截图,你标记了 username 和 password 两个位置,这是问题根源:
- 当你标记了
username=§admin§,Burp 会把admin也当成 payload 去替换,而不是固定用户名。 - 正确的标记方式:
- 点击
Clear §清空所有标记 - 只选中
password=后面的占位符(比如test),点击Add § - 最终效果:
username=admin&password=§test§(只有 1 个标记)
- 点击
2. 字典里的 password 格式不对
- 检查你的字典文件:
- 是不是写成了
Password(首字母大写)? - 是不是有多余的空格、换行?比如
password(后面带空格)?
- 是不是写成了
- 快速验证:直接在 Burp 的 Payload 列表里手动添加一行
password,然后单独跑这一条 payload,看能不能成功。
3. 没有处理 user_token(CSRF 防护)
你截图里的请求带了 user_token=xxx,这是 DVWA 的 CSRF 防护:
- 每次登录请求的
user_token都是动态变化的,你用固定的 token 爆破,请求会直接被服务器拒绝。 - 解决办法:
- Low 难度 :先在 DVWA 里把安全等级改成
low,此时没有user_token,可以直接爆破。 - Medium/Impossible 难度:需要在 Burp 里配置「宏(Macro)」,每次爆破前先请求页面获取新的 token,再带着 token 去发登录请求(免费版 Community 没有这个功能,只能用专业版)。
- Low 难度 :先在 DVWA 里把安全等级改成
4. 请求方式 / 参数写错了
POST /vulnerabilities/brute/ HTTP/1.1
...
username=admin&password=test&Login=Login&user_token=xxx
- 检查参数名是不是和实际表单一致:
- 有的 DVWA 版本里,登录表单的参数是
username、password,但你要确认没有写错(比如passwd、user)。 - 可以用浏览器抓包,看正常登录时的请求参数,和你 Burp 里的对比一下。
- 有的 DVWA 版本里,登录表单的参数是
5. 服务器响应长度 / 状态码判断被干扰
你爆破的时候,是不是所有请求的 Status 都是 200,而且 Length 都一样?
- 因为带了错误的
user_token,服务器返回的页面内容都是 "token 无效" 的错误页,所以长度和状态码都一样,你看不到差异。 - 解决办法:先去掉
user_token参数,或者把安全等级改成 Low 再试。
✅ 一步到位的测试方法(快速定位问题)
- 先在浏览器里正常登录,确认
admin/password能登录成功。 - 把 DVWA 安全等级改成
low,此时请求里不会有user_token。 - 用 Burp 抓一次正常登录的请求,把它发送到 Intruder。
- 清空 Positions 标记,只标记
password字段。 - 在 Payload 列表里只放
password这一条,点击Start attack。 - 看这次请求的响应:如果能成功跳转,说明标记和字典都没问题,之前的问题就是
user_token或标记错误。
medium级别
还是用于刚才相同的方式进行爆破,区别在于,点击start attack后,后发现爆破的速度明显下降,耗时明显增加

DVWA的Medium级别Brute Force(暴力破解)仍可被攻破,核心原因是其防护仅通过sleep(2)增加单次请求延迟,但未限制并发请求。通过多线程爆破可绕过时间限制。
详细操作步骤:
一、关键结论
Medium级别暴力破解可以成功,因为:
- 防护仅增加单次请求2秒延迟 ,但未限制并发连接数。
- 多线程爆破 能并行发送请求,抵消延迟影响(50线程 ≈ 耗时减少50倍)。
- 成功登录的特征是302重定向(而非Low级别的200响应)。
二、操作步骤
1. 配置Burp Suite代理
操作流程
- 启动Burp Suite :
- 打开Burp Suite → Proxy → Options。
- 确保 Proxy listeners 中
127.0.0.1:8080处于 Running 状态。
- 配置浏览器代理 :
- 浏览器安装 FoxyProxy 插件(推荐Firefox)。
- 新建规则:
- Proxy Type: HTTP
- Proxy IP :
127.0.0.1 - Port :
8080 - URL pattern :
*dvwa*(仅代理DVWA流量)。
- 验证代理生效 :
- 在浏览器访问
http://靶机IP/dvwa/。 - 若Burp Proxy → Intercept 捕获到请求,说明代理成功。
- 在浏览器访问
2. 捕获登录请求并标记参数
操作流程
- 拦截测试请求 :
- 在DVWA的Brute Force页面(
/vulnerabilities/brute/),输入任意错误密码 (如username=admin,password=123)。 - 点击Login前 ,在Burp中开启 Proxy → Intercept → Intercept is on。
- 在DVWA的Brute Force页面(
- 发送请求到Intruder :
- 提交登录后,Burp会捕获请求 → 右键 → Send to Intruder。
- 切换到 Intruder → Positions 标签页。
- 标记爆破参数 :
- 点击 Clear 清除所有自动标记。
- 在请求中选中
password=123的值部分 (仅123)。 - 点击 Add 将其设为唯一变量 (
$password$)。
注:已知用户名为admin,故仅爆破密码。


关键验证点
- 请求格式应类似 :
GET /vulnerabilities/brute/?username=admin&password=§123§&Login=Login HTTP/1.1
(§123§表示变量位置,仅密码是变量)。
3.配置多线程资源池
以绕过Medium级别的2秒延迟防护。关键点:单线程会受sleep(2)限制(仅30次/分钟),但50线程并发可将速度提升至约1500次/分钟。以下是精确操作步骤:
一、关键配置说明
Medium级别防护仅通过sleep(2)增加单请求延迟 ,但未限制并发连接数 。通过以下配置可并行发送请求 ,使实际爆破速度不受2秒延迟影响:
- Resource Pool :必须设置并发请求数 >1(默认单线程会严格受延迟限制)。
- 重定向处理 :成功登录会返回
302重定向,需捕获最终响应。
二、具体操作步骤
- 配置多线程资源池(核心步骤)
操作流程
- 创建资源池 :
- 切换到 Intruder → Resource Pool 标签页。

- 点击 Create new resource pool → 命名
BF_Medium_Bypass。
- 切换到 Intruder → Resource Pool 标签页。
- 设置并发参数 :
- Max concurrent requests :
50(必须 >1 才能绕过延迟)。 - Throttle :
0(禁用请求间隔,避免额外延迟)。 - Retry on failure :
No(避免重试干扰延迟计数)。 - 其他保持默认 → 点击 OK。
- Max concurrent requests :
- 绑定资源池 :
- 切换回 Positions 标签页 → 在 Resource Pool 下拉菜单中选择
BF_Medium_Bypass。
- 切换回 Positions 标签页 → 在 Resource Pool 下拉菜单中选择
Burp Suite 的版本不同,界面布局确实会有差异。在较新的版本(Community 202x+)中,界面可能确实是创建资源池的地方,但**"Throttle"(限速)** 和**"Retry on failure"(失败重试)**这两个具体选项被移动到了其他标签页。比如我这个
请按照以下步骤在对应的位置找到并设置它们:
第一步:确认"并发数"设置(就在你当前的界面)
界面(Resource Pool 标签页):
- 确保勾选了
Maximum concurrent requests。- 确保数值填的是
50。
- 用来对抗
sleep(2)延迟的关键。第二步:设置"失败不重试"(在 Settings 标签页)
- 点击 Intruder 模块上方的 Settings 标签页(通常在 Positions, Payloads, Resource Pool 的旁边或后面)。
- 向下滚动找到 Retries (重试)部分也可能叫Error handling(错误处理)。
- 设置为 0 (或者取消勾选 Enable retries)。
- 解释:Medium级别如果请求被服务器丢弃,Burp默认会重试,这会导致爆破时间变长。设为0是为了确保速度。
- 这就是我们要找的"Retry on failure: No"。改成0意味着如果请求失败了就直接过,不浪费时间重试。
第三步:设置"请求间隔"(在 Resource Pool 标签页)
回到那个 Resource Pool 界面:
- 找到 Delay between requests(请求之间的延迟)。
- 不要勾选 前面的复选框(即保持未选中状态)。
- 解释:未勾选即代表 0 延迟。如果勾选了并填入数字,会人为增加等待时间,导致爆破变慢。
第四步:处理重定向(在 Settings 标签页)
- 在 Settings 标签页中,找到 Redirections(重定向)部分。(位置偏下)
- 选择 Always (总是跟随重定向)或者 On-site only (仅限本站)。
- 解释:DVWA 登录成功时会返回 302 跳转。如果 Burp 不自动跳转,你可能只能看到 302 状态码,而无法看到成功登录后的页面长度变化。
做到这里我补充一下选择攻击模式部分,攻击模式有四种:
1. Sniper Attack(狙击手):
- 功能:Sniper 攻击是一种单点攻击模式,主要用于测试单个参数的漏洞。攻击者逐一对目标中的每个单独参数进行攻击。
- 使用场景:适用于暴力破解或枚举一个参数的不同值(例如用户名、密码或其他输入字段)。
- 举例:在登录表单中,Sniper 攻击会逐一尝试每个可能的用户名和密码组合。
2. Battering Ram Attack(攻城锤):
- 功能:Battering Ram 攻击模式将相同的攻击负载应用到多个参数上。该模式通过对所有指定参数使用相同的攻击数据来进行测试。
- 使用场景:适用于所有参数应该具有相同值的情况,例如在多个字段中输入相同的密码或令牌进行暴力破解。
- 举例:在一个多字段的表单中,Battering Ram 攻击会对每个字段使用相同的密码进行测试,适用于密码重置表单等情况。
3. Pitchfork Attack(叉草):
- 功能:Pitchfork 攻击模式用于同时攻击多个参数,但每个参数都使用不同的攻击数据。多个攻击字典可以同时用于多个参数。
- 使用场景:适用于需要同时用多个词典对多个参数进行测试的情况。例如,用户的用户名和密码可以分别来自两个不同的字典。
- 举例:在登录页面,Pitchfork 攻击可以分别使用一个字典对用户名字段进行攻击,并使用另一个字典对密码字段进行攻击。
4. Cluster Bomb Attack(集束炸弹):
- 功能:Cluster Bomb 攻击模式通过对多个参数使用不同的字典组合进行攻击。每个参数都会分别使用不同的词典,产生所有可能的组合。
- 使用场景:适用于多个字段中需要尝试所有可能组合的情况。它比 Pitchfork 攻击更复杂,因为它会生成所有的组合。
- 举例:在一个表单中,Cluster Bomb 攻击可能会将字典1应用于用户名字段,将字典2应用于密码字段,并将字典3应用于邮箱字段,然后生成所有可能的组合。
回归本题,设置多线程资源池为什么有效?
- Medium级别代码中
sleep(2)仅阻塞当前请求线程 ,服务器会并行处理其他线程的请求。 - 50线程并发时,实际耗时 ≈ 总密码数/50 × 2秒 (单线程则为
总密码数 × 2秒)。
- 启用重定向捕获
操作流程
- 配置重定向选项 :
- 切换到 Intruder → Options → Redirections。
- 选择 Always(确保捕获302重定向后的响应页面)。
- 验证关键点 :
- 成功登录会返回
HTTP/1.1 302 Found+Location: index.php。 - 失败响应长度固定 (Medium级别错误提示统一为
Username and/or password incorrect)。
- 成功登录会返回
三、启动爆破与结果识别
- 加载密码字典
- 切换到 Payloads 标签页:
- Payload set :
1。 - Payload type :
Simple list→ 点击 Load → 选择字典(如password字段常用字典:123456, admin, password, letmein等)。 - 注:Medium级别无需爆破用户名(已知
admin)。
- Payload set :
-
启动攻击并筛选结果
-
开始爆破 :
- 点击 Start attack → 观察 Status 列(正常为
200,成功为302)。等待爆破
- 点击 Start attack → 观察 Status 列(正常为
-
快速定位成功密码 :
- 点击 Length 列标题排序(成功响应长度通常比失败响应长15-20%)。
- 成功特征 :
- 状态码为
302(而非Low级别的200)。 - 响应长度明显异常(失败响应长度固定,成功响应包含重定向页面内容)。
- 在 Response 标签页中可见
Welcome to the password protected area admin。
- 状态码为

典型成功响应示例
| Payload | Status | Length | Response特征 |
|---|---|---|---|
password |
302 | 492 | 包含 Location: index.php 和欢迎信息 |
123456 |
200 | 4612 | 固定错误提示 Username and/or password incorrect |
若结果中无302响应,需检查:
- Resource Pool是否设置为50并发(非默认单线程)。
- 是否启用了 Redirections → Always。
四、防御机制失效原因(此部分结尾总结了常见错误)
1. Medium级别防护缺陷
- 延迟机制可绕过 :
sleep(2)仅作用于单个请求 ,未限制并发连接数,多线程可并行突破。 - 无失败次数锁定 :
与High级别不同,Medium不会锁定账户,允许无限次尝试。
2. 真实场景加固方案
- 必须添加全局速率限制 :
按IP/会话限制每分钟请求次数(如 >5次/分钟则锁定5分钟)。 - 动态延迟递增 :
失败次数越多,延迟时间越长(例如:第1次失败延迟1秒,第5次延迟30秒)。 - 关键操作强制验证码 :
登录失败超过3次后,需输入一次性动态验证码(服务端严格校验)。
为什么明明密码是password,但是就是爆破不出来
既然你确定
password是正确密码,但 Burp 跑出来的结果却是 200 (失败)且 Length 4897 (失败页面长度),那问题肯定不在 Burp 的配置,而是在请求本身的内容上。这通常只有两个原因:Token 失效 或者 参数名不对。
请按以下顺序排查,必能解决:
第一步:检查是否漏掉了
user_token(最常见原因)DVWA Medium 级别虽然防暴力破解,但登录表单里依然包含一个隐藏的
user_token。
- 现象 :如果你只标记了
password,而没有把user_token也标记为变量(或者没有设置递归抓取),Burp 发送的所有请求用的都是你第一次拦截时的那个"旧 Token"。- 后果:服务器收到旧 Token,认为请求非法或会话过期,直接拒绝登录,返回登录失败页面(200 + 4897)。
- 解决方法 :
- 回到 Positions 标签页。
- 检查 URL 或 Body 里是否有
user_token=...。- 如果有 :必须把它也标记为
§user_token§。- 设置 Attack Type :改为 Pitchfork (双叉攻击)。
- Payload Set 1: 选择你的密码字典。
- Payload Set 2: 选择 Recursive grep(递归抓取),并配置提取规则(这步比较难,如果不想做这步,请看下面的"简单替代法")。
第二步:简单替代法(推荐新手使用)
如果你不想搞复杂的 Token 抓取,可以用这个"笨办法"绕过 Token 验证:
- 回到 Positions 标签页。
- 删除 所有
§标记。- 只标记密码 :把
password=...的值标记为§password§。- 关键一步 :把 Attack Type 改回 Sniper。
- 手动添加 Header (欺骗服务器):
- 去 Headers 标签页(或者在 Positions 的请求包里)。
- 确保请求头里包含:
Cookie: security=medium; PHPSESSID=你的会话ID。- 注意:如果你的 PHPSESSID 过期了,也会爆破失败。
第三步:检查参数名
DVWA Medium 级别的代码逻辑是检查
$_POST['username']和$_POST['password']。
- 看看你标记的是不是
pass、pwd或者passwd?- 必须确保标记的参数名是
password。终极验证法(手动测试)
为了证明是 Token 的问题,请你做这个测试:
- 把 Burp 的拦截关掉(Intercept is off)。
- 在浏览器里正常输入
admin和password,点击登录,确保能登进去。- 在 Burp 的 Proxy -> HTTP history 里找到这一次成功的登录请求。
- 右键 -> Send to Intruder。
- 在 Intruder 里,只标记
password的值。- 直接跑(不用改资源池,不用改字典,就用这一个包)。
如果这一次跑出来是 302,说明之前的包里的 Token 确实是过期的。
总结:你现在的截图里全是 200,说明服务器根本没认可你的登录请求。90% 的概率是因为你用的请求包里的
user_token已经失效了。
如果确定不是上面的那些原因,你可以试一下随便输入一个密码(因为你没解出来,不知道密码)你看到的 "account has been locked" 提示,正是导致爆破失败的原因。这是因为 DVWA 的 Medium 级别为了防止暴力破解,设置了"连续输错密码会锁定账户"的机制。
为什么会被锁定?
虽然 Medium 级别没有像 High 级别那样直接加验证码,但它引入了账户锁定机制。
- 机制:如果你在短时间内连续输错密码(比如超过 3-5 次),数据库里的账户状态就会变成"锁定"。
- 后果:此时,无论你输入正确的密码还是错误的密码,系统都会直接返回"锁定"提示,或者一直返回登录失败。
解决方法
你有两个办法可以立刻解决这个问题:
方法一:重置数据库(最快)
这是最直接的办法,把 DVWA 恢复到初始状态,解锁所有账户。
- 点击左侧菜单栏的 DVWA Security (或者在 URL 里找
security.php)。- 把安全级别改成 Low(为了测试方便,或者保持 Medium 也行)。
- 点击左侧菜单最上面的 Setup / Reset DB。
- 点击页面下方的 Reset Database 按钮。
- 这会重置用户密码和锁定状态。
- 重新登录 DVWA,然后重新进行 Burp 爆破。
方法二:等待 15 分钟
截图里的红字提示 "try again in 15 minutes"。
- 如果你不想重置数据库,就只能干等 15 分钟,等系统自动解锁。
- 但在做实验时,通常直接重置数据库更高效。
重置后,如何避免再次被锁?
在你重置完数据库,准备重新爆破时,要注意一点:
- Medium 级别的锁定机制通常基于"连续失败次数"。
- 如果你的字典里,正确密码(password)排在很后面,那么在前面的几十个错误密码试过去之后,账户可能就已经被锁了,导致永远试不到正确的那个。
建议操作:
- 编辑你的字典 :把
password这个词放到字典的第一行。- 或者使用更小的字典:只用几个常见的密码测试,确保在触发锁定阈值(比如 5 次)之前,能轮到正确密码。
总结步骤:
先去点
Setup / Reset DB重置数据库 -> 把正确密码放到字典第一位 -> 重新发包爆破。
代码详细解释

这张截图展示了 DVWA Medium 级别暴力破解的完整 PHP 源码。这段代码解释了为什么你之前的爆破会遇到困难(延迟、锁定、Token 缺失)。
下面我逐行拆解代码,并解释每一部分对你爆破的影响:
第一部分:接收参数
代码段:
if( isset( $_GET[ 'Login' ] ) ) {
// Sanitise username input
$user = $_GET[ 'username' ];
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitise password input
$pass = $_GET[ 'password' ];
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass = md5( $pass );
解释:
$_GET['Login']:说明登录请求是通过 GET 方法发送的(URL 参数里会有数据,而不是 POST 表单)。$_GET['username']和$_GET['password']:获取你输入的用户名和密码。mysqli_real_escape_string:这是一个转义函数,用来防止 SQL 注入(比如把单引号转义掉)。这意味着你很难通过 SQL 注入绕过登录。$pass = md5( $pass ):关键点! 密码在存入数据库比对之前,先进行了 MD5 加密 。- 这意味着数据库里存的不是 "password",而是 "password" 的 MD5 值(
5f4dcc3b5aa765d61d8327deb882cf99)。
- 这意味着数据库里存的不是 "password",而是 "password" 的 MD5 值(
第二部分:数据库查询
代码段:
// Check the database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( ... );
解释:
- 代码直接在数据库中查找 用户名匹配 且 密码(MD5后)匹配 的记录。
- 因为用了
mysqli_real_escape_string,这里的 SQL 注入难度很大,所以只能老实爆破密码。
第三部分:登录成功
代码段:
if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];
// Login successful
echo "<p>Welcome to the password protected area {$user}</p>";
echo "<img src=\"{$avatar}\" />";
}
解释:
- 如果数据库找到了这一行数据(
num_rows == 1),说明密码正确。 - 页面会输出 "Welcome..." 和图片。
- 爆破特征 :在 Burp 中,成功的包 Length(长度)会很大,且包含 "Welcome" 字样。
第四部分:登录失败(核心防御机制)
代码段:
else {
// Login failed
sleep( 2 );
echo "<pre><br />Username and/or password incorrect.</pre>";
}
解释:
sleep( 2 ):这是 Medium 级别的核心防御!- 如果密码错了,服务器会强制暂停 2 秒再返回结果。
- 对你的影响 :如果你爆破 100 个密码,哪怕你并发开得很高,每个包都要等 2 秒,总时间会非常长。这也是为什么我之前让你设置 并发数 50 的原因,就是为了抵消这个延迟。
echo ... incorrect:返回固定的错误提示。
第五部分:为什么你会被锁定?
注意:你发的这段源码里,并没有包含"账户锁定"的逻辑。
这意味着:
- 锁定逻辑在别处 :DVWA 的"账户锁定"功能可能是在
vulnerabilities/brute/source/medium.php之外的其他文件里(比如login.php或者数据库触发器),或者是你使用的 DVWA 版本对 Medium 级别做了额外修改。 - 或者你记混了级别 :通常 DVWA 的 Impossible 级别才会有严格的账户锁定逻辑。
- 但在你的截图中:既然浏览器明确显示了 "account has been locked",说明你的环境确实有这个机制。
总结:代码给你的爆破线索
- 请求方式 :必须是 GET 请求(参数在 URL 里)。
- 防御手段 :
sleep(2)导致速度慢,必须开高并发。 - 成功标志:Length 变长,出现 "Welcome"。
- 失败标志:Length 固定(约 4897),出现 "incorrect"。
建议: 既然被锁了,先去点 Setup / Reset DB 重置数据库,然后确保你的 Burp 请求是 GET 方式,并且把正确密码放在字典第一位,争取在触发锁定前直接跑出来。
high级别
继续使用相同方式去爆破
要攻破 High 级别,必须解决 Token 动态变化 的问题。在 Burp Suite 中,我们需要使用 Pitchfork(双叉攻击) 模式配合 Recursive Grep(递归抓取) 来提取每次请求后的新 Token。
第一步:准备环境
- 重置数据库 :去 DVWA 的
Setup / Reset DB重置一下,防止账户被锁。 - 抓包:打开 Burp Suite,在 DVWA 登录页面输入任意用户名密码(比如 admin/admin),点击登录,拦截这个请求包发送到 Intruder。



第二步:配置 Intruder(关键步骤)
-
设置 Attack Type
- 在 Intruder 的
Positions标签页,将 Attack Type 选为 Pitchfork。
- 在 Intruder 的
-
设置变量
- 清除所有变量 :点击
Clear §。 - 标记密码 :选中密码的值(例如
password=admin中的admin),点击Add §。此时变成password=§admin§。 - 标记 Token :选中
user_token的值(那一长串随机字符),点击Add §。此时变成user_token=§a1b2c3d4...§。 - 最终效果 :你的 URL 参数里应该有两个变量:
...password=§...§&user_token=§...§&Login=Login。
- 清除所有变量 :点击
-
配置 Payloads
- Payload Set 1(密码字典) :
- 点击
Payloads标签页。 - 在
Payload Set下拉菜单选 1 。(其实只要点一下第一个,颜色加深之后旁边就显示对1的set)
- 加载你的密码字典(比如
password.txt)。
- 点击
- Payload Set 2(Token 抓取) :
- 在
Payload Set下拉菜单选 2。(其实就是点一下第二个,就是对2的set) - Payload Type 选择 Recursive grep 。


- 从响应中提取 Token :
- 点击
Add按钮添加一个提取规则。 - 在弹出的窗口中,Burp 会让你选择一个请求。选择第一个请求(也就是你发出去的那个带旧 Token 的请求)。
- Burp 会显示该请求的响应内容。你需要在这个 HTML 源码中找到
<input type='hidden' name='user_token' value='这里是一串字符' />。 - 选中那串字符(即
value='和'中间的部分),点击Start after和End before(或者手动调整边界),让 Burp 只抓取那个变化的 Token 值。 - 点击
OK保存规则。
- 点击
- 测试 :点击
Test按钮,如果配置正确,下方的表格里应该会显示抓取到的 Token 值。 - 如果第三步找不到Add按钮,就看下面灰框
你现在的 Burp Suite 界面里右侧栏里,根本没有显示 Payload Options区域。
- 在
- Payload Set 1(密码字典) :
这是 Burp Suite 的一个特性(或者说 bug):
当你选择了
Payload type: Recursive grep时,如果没有配置过任何 "Extract grep item" ,它有时候会隐藏 掉下面的Payload Options区域,或者把它折叠得你找不到。解决办法(必须按顺序):
- 先别管 Add 按钮了。
- 看截图中间那个巨大的空白框 (在
Select the "extract grep" item...下面)。- 先去 Settings 选项卡(在你的 Burp 主界面的上方,和 Intruder、Target 并列的那个标签页)。
- 在 Settings 里,找到 "Grep - Extract" 。
- 在这里添加一个新的 Extract 规则 (把你想要提取的 Token 的正则或者边界写进去)。
- 回到 Intruder 的 Payloads 界面。
- 刷新一下(或者重新选一下 Payload type)。
- 这时候,那个巨大的空白框里应该会出现你刚才在 Settings 里定义的规则名字。
- 选中那个规则名字。
- 这时候,
Payload Options区域才会真正出现,[Add]按钮才会显现。- 总结:你得先去 Settings 里定义"要提取什么",这里才会让你"添加初始值"。
在这里添加一个新的 Extract 规则要写啥,后面接着写了怎么看token,就是如此保姆级
这取决于你要提取的 Token 长什么样(是数字、字母,还是混合的)。
简单来说,Burp 需要你告诉它:"从哪句话开始找" ,以及 "找到哪句话结束"。
这里有三种最常用的写法,你可以根据你的实际情况选一个:
方法一:最简单直接(推荐新手)
如果你的 Token 长这样:
<input type="hidden" name="token" value="这里是要提取的内容">
- 左边界 (Left boundary) :填入
name="token" value="
- 解释:这是 Token 前面的固定文字。
- 右边界 (Right boundary) :填入
"
- 解释:Token 后面紧接着的一个双引号。
注意:输入完左右边界后,Burp 通常会自动在下面预览出提取到的内容。如果预览是对的,就点 OK。
方法二:正则表达式(适合高手或复杂情况)
如果边界不好找,或者你想写得更专业一点,可以切换到 Regex 模式。
- Regex :填入
name="token" value="(.+?)"
- 解释:
(.+?)的意思就是"提取中间任意长度的字符"。方法三:如果 Token 是纯数字
如果 Token 只有数字(比如
123456789),为了防止提取多了,可以加个限制。
- 左边界 :
name="token" value="- 右边界 :
"- (或者用正则) :
name="token" value="(\d+)"
- 解释:
\d+专门匹配数字。操作步骤总结
- 在 Left boundary 里填入 Token 前面 的那串固定代码(比如
value=")。- 在 Right boundary 里填入 Token 后面 的那串固定代码(比如
")。- 看一眼下面的 Preview(预览),确保它只显示了那一串乱码一样的 Token,没有包含其他多余的 HTML 代码。
- 点击 OK。
要看 Token,得先去 "抓包记录" 里找。你现在的界面是在 Intruder 里,这里是配置攻击的地方,还没有开始真正跑数据呢。请按这个步骤操作:
第一步:去 Proxy 或 Target 里找
- 点击 Burp Suite 顶部菜单栏的
Proxy选项卡。- 点击子菜单里的
HTTP history。- 在下面的列表里,找到你刚才访问的那个页面(比如登录页面、或者包含表单的页面)。
- 点击这一行,然后在右边的
Response面板里,选择HTML或者Raw视图。- 按
Ctrl + F搜索关键词 :
- 搜
token- 搜
csrf- 搜
state- 搜
nonce- 或者直接搜
<input(很多 Token 都是藏在 input 标签里的)第二步:观察 Token 的样子
你会看到类似下面这样的代码:
<input type="hidden" name="user_token" value="8a7d9f1e2c3b4a5d6e7f8g9h0i1j2k3l" />
- 这里我们要提取的内容(Payload) :就是
value="和"中间那一长串乱码。- 左边界 :就是
name="user_token" value="- 右边界 :就是
"第三步:回到刚才的设置界面
现在你知道边界是什么了,回到刚才那个
Grep - Extract的设置窗口:第一步:勾选复选框
看到那个
Extract the following items from responses:前面的小方框了吗?
- 一定要先勾上它!(不勾选的话,下面的规则是不会生效的)。
第二步:点击
[Add]现在点击这个列表旁边的
[Add]按钮。
- 这时候会弹出一个新的窗口,叫
Add grep item。第三步:填写边界(关键步骤)
在这个弹出的新窗口里,我们要告诉 Burp 怎么找到那个 Token。
- 确认模式 :确保选中的是
Simple string(通常默认就是这个,比正则简单)。- 填写 Left boundary(左边界) :
- 在输入框里填入:
user_token=- (注意:就是你刚才在截图里看到的,等号前面那几个字母)。
- 填写 Right boundary(右边界) :
- 在输入框里填入:
&- (注意:就是 Token 后面那个用来分隔的符号)。
- 然后点ok
第四步:检查并保存
- 填完后,看窗口下方的
Preview from response区域。(版本不一样可能不会显示,只要第五步显示对了就行)- 如果配置正确,这里应该会显示出那一长串乱码(比如
125c078b90d0d0a73a10c6029c78e124)。- 如果预览是对的,点击
OK。第五步:回到 Payloads
- 现在你应该回到了
Grep - Extract的主界面,列表里应该多了一行user_token=。- 点击 Intruder 顶部的
Payloads标签页。- 见证奇迹的时刻 :那个消失已久的
[Add]按钮应该出现了!- 快去试试,勾选上那个框,然后点 Add!
-
设置线程
- 去
Resource pool标签页。 - Number of threads :设置为 1。
- 原因:Token 是串行的,必须拿到第一个请求返回的新 Token,才能发第二个请求。如果多线程并发,Token 就会乱套。
- 去
-
设置选项
- 去
Settings标签页。 - Grep - Extract:确保没有勾选会导致请求变慢的无关选项。
- Retries :设置为 0(防止网络波动导致重试,浪费 Token)。
- 去
第三步:开始攻击
- 点击
Start attack。 - 观察结果:
- 你会看到每个请求的
user_token都是不一样的(Burp 自动帮你从上一个响应里提取并替换了)。 - 虽然速度很慢(因为代码里有
sleep(rand(0,3)),平均每个请求要等 1.5 秒),但最终会跑完。 - 找到那个 Length(长度) 不一样的行,那就是正确密码。
- 你会看到每个请求的
总结
High 级别的核心在于:你必须让 Burp 像个真正的浏览器一样,每提交一次密码,就去新的页面里把新的 Token 抠出来,放到下一次请求里。 这就是 Recursive grep 的作用。
还是不行的话:
你需要把攻击模式改成
Cluster bomb(集束炸弹)。这种模式允许:
- 位置 1:跑一个字典(你的密码字典)。
- 位置 2:跑一种逻辑(自动提取并更新 Token)。
第一步:改攻击模式
在 Intruder 左上角,把
Pitchfork改成Cluster bomb。第二步:配置位置 1(密码)
- 在右侧
Payloads选项卡上方,找到Payload set,选择1。Payload type选Simple list(或者你之前用的字典类型)。- 这时候下面的
Add按钮就回来了!- 把你想要爆破的密码(比如
123、password等)填进去。第三步:配置位置 2(Token)
- 在右侧
Payloads选项卡上方,找到Payload set,选择2。Payload type选Recursive grep。Select the "extract grep" item...:选择你刚才配置好的那个From [user_token=] to [&]。Initial payload for first request:
- 把你截图里现在填的那串
27f9488...留着别动(这就是初始 Token)。- 勾选下面的
Stop if duplicate payload found(防止重复发包,建议勾上)。总结一下
- Set 1 = 密码字典(Simple list)
- Set 2 = 自动抓 Token(Recursive grep)
- Attack Type = Cluster bomb
改完这三个地方,点击
Start attack,你的爆破就能跑起来了!

详细代码解释

核心逻辑: 引入 Token 验证 + 混淆成功/失败的响应特征。
输入过滤
$user = $_GET[ 'username' ];
$user = stripslashes( $user );
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// 密码处理同上...
$pass = md5( $pass );
这里的输入过滤逻辑比 Medium 复杂,但本质上还是为了防止 SQL 注入。那个超长的三元运算符只是 PHP 版本兼容性的写法,功能等同于 mysqli_real_escape_string。
Anti-CSRF Token 验证(核心差异 1)
if( isset( $_GET[ 'Login' ] ) ) {
// 检查 Token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
...
}
- 原理 :服务器在生成登录页面时,会生成一个随机的
user_token存入 Session,并放在表单的隐藏域中。 - 防御:当你提交登录请求时,服务器会对比提交的 Token 和 Session 中的 Token。
- 爆破影响 :如果你用 Burp 直接爆破,没有带上最新的 Token,或者 Token 不匹配,
checkToken函数会直接把你踢回index.php。这意味着你的爆破 payload 必须包含动态提取 Token 的步骤。
数据库查询
$query = "SELECT * FROM users WHERE user = '$user' AND password = '$pass';";
$query .= " LIMIT 1;";
- 多了一个
LIMIT 1,这是个好习惯,查到一条就停止,提高数据库效率。
验证与混淆(核心差异 2)
多了一个 LIMIT 1,这是个好习惯,查到一条就停止,提高数据库效率。
验证与混淆(核心差异 2)
- 防御机制 :
sleep( rand( 0, 3 ) )。 - 原理 :
- Medium 级别是固定睡 2 秒,太规律了,容易被识别。
- High 级别是随机睡 0 到 3 秒。
- 爆破影响 :
- 如果密码正确:响应时间极短(比如 0.05 秒)。
- 如果密码错误:响应时间可能是 0.1 秒,也可能是 2.9 秒。
- 这就造成了干扰:虽然正确密码通常还是最快的,但在网络波动的情况下,一个错误的密码(随机到了 0.1 秒)可能比正确密码(网络慢了点到 0.2 秒)还要快。这增加了通过"时间盲注"判断密码的难度。
总结对比
| 特性 | Medium 级别 | High 级别 |
|---|---|---|
| 请求方式 | GET | GET |
| SQL 注入防护 | 有 (转义) | 有 (转义) |
| 密码处理 | MD5 加密 | MD5 加密 |
| 防爆破机制 | 固定延时 (sleep(2)) |
随机延时 (sleep(rand(0,3))) |
| 核心防御 | 无 Token,仅靠延时 | Anti-CSRF Token + 随机延时 |
| 爆破难度 | 中等(看响应时间) | 困难(需处理 Token + 延时干扰) |
一句话总结:
Medium 级别只是让你"慢点猜",而 High 级别不仅让你"猜不准"(随机延时),还加了一把"动态锁"(Token),如果你不把每次请求的钥匙(Token)换新的,门都敲不开。
impossible级别

一、整体结构概览
Impossible 难度的核心设计目标是:彻底阻断 SQL 注入 + 暴力破解 + CSRF 攻击,主要防护手段包括:
- CSRF Token 校验
- SQL 预处理语句(彻底防注入)
- 账户锁定机制(防暴力破解)
- 密码错误延迟(进一步拖慢爆破)
二、逐段代码分析
1. 初始校验与 CSRF Token 防护
if( isset( $_POST[ 'Login' ] ) && isset( $_POST['username'] ) && isset( $_POST['password'] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
- 首先检查请求是否携带了
Login、username、password三个参数 - 关键:调用
checkToken()验证user_token,这是 DVWA 的 CSRF 防护机制- 每个会话的
session_token都是随机生成的 - 每次登录请求必须携带和服务器端一致的
user_token,否则请求直接被拒绝 - 这会让 Burp Intruder 等工具的直接爆破失效,因为 token 是动态变化的
- 每个会话的
2. 输入过滤与转义
// Sanitise username input
$user = $_POST[ 'username' ];
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitise password input
$pass = $_POST[ 'password' ];
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass = md5( $pass );
- 对
username和password都调用了mysqli_real_escape_string()转义特殊字符(如单引号'),防止 SQL 注入 - 密码字段还额外做了
md5()哈希处理,即使绕过转义,也无法直接构造注入 payload - 到这里,万能密码注入已经完全失效
3. 账户锁定逻辑(核心防护)
// Default values
$total_failed_login = 3;
$lockout_time = 15;
$account_locked = false;
// Check the database (Check user information)
$data = $db->prepare( 'SELECT failed_login, last_login FROM users WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
$row = $data->fetch();
// Check to see if the user has been locked out.
if( ($data->rowCount() == 1) && ($row[ 'failed_login' ] >= $total_failed_login) ) {
// User locked out. Note, using this would allow for user enumeration.
echo "<pre><br />This account has been locked due to too many incorrect logins.</pre>";
// Calculate when the user would be allowed to login again
$last_login = strtotime( $row[ 'last_login' ] );
$timeout = $last_login + ($lockout_time * 60);
$timenow = time();
/*
print "The last login was: " . date ("h:i:s", $last_login) . "<br />";
print "The timenow is: " . date ("h:i:s", $timenow) . "<br />";
print "The timeout is: " . date ("h:i:s", $timeout) . "<br />";
*/
// Check to see if enough time has passed, if it hasn't locked the account
if( $timenow < $timeout ) {
$account_locked = true;
// print "The account is locked<br />";
}
}
- 定义了锁定规则:连续
3次登录失败后,账户会被锁定15分钟 - 使用 PDO 预处理语句 查询用户信息,这是防御 SQL 注入的最强手段之一
- 如果用户失败次数 ≥3,且当前时间未超过锁定时间,就会标记
$account_locked = true,直接拒绝登录请求 - 即使你知道正确密码,锁定期间也无法登录,这就是你之前看到的
账户已锁定提示的来源
4. 登录验证逻辑(预处理语句防注入)
// Check the database (if username matches the password)
$data = $db->prepare( 'SELECT * FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR);
$data->bindParam( ':password', $pass, PDO::PARAM_STR );
$data->execute();
$row = $data->fetch();
- 这里使用了 PDO 预处理语句,将用户输入作为参数绑定到 SQL 语句中,而不是直接拼接
- 这意味着:无论用户输入什么特殊字符,都只会被当作普通字符串处理,彻底杜绝了 SQL 注入漏洞
- 即使你输入万能密码 payload,也只会被当成用户名 / 密码的一部分,不会改变 SQL 逻辑
5. 登录成功 / 失败处理
// If its a valid login.
if( $data->rowCount() == 1 && ($account_locked == false) ) {
// Get users details
$avatar = $row[ 'avatar' ];
$failed_login = $row[ 'failed_login' ];
$last_login = $row[ 'last_login' ];
// Login successful
echo "<p>Welcome to the password protected area <em>{$user}</em></p>";
echo "<img src=\"{$avatar}\" />";
// Had the account been locked out since last login?
if( $failed_login >= $total_failed_login ) {
echo "<p><em>Warning</em>: Someone might of been brute forcing your account.</p>";
echo "<p>Number of login attempts: <em>{$failed_login}</em>.<br />Last login attempt was at: <em>${last_login}</em>.</p>";
}
// Reset bad login count
$data = $db->prepare( 'UPDATE users SET failed_login = "0" WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
} else {
// Login failed
sleep( rand( 2, 4 ) );
// Give the user some feedback
echo "<pre><br />Username and/or password incorrect.<br /><br/>Alternative, the account has been locked because of too many failed logins.<br />If this is the case, <em>please try again in {$lockout_time} minutes.</em></pre>";
// Update bad login count
$data = $db->prepare( 'UPDATE users SET failed_login = (failed_login + 1) WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
}
// Set the last login time
$data = $db->prepare( 'UPDATE users SET last_login = now() WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
}
// Generate Anti-CSRF token
generateSessionToken();
?>
- 登录成功时 :重置用户的
failed_login计数为 0,并更新last_login时间 - 登录失败时 :
- 随机延迟
2-4秒,大幅降低暴力破解的效率 - 将用户的
failed_login计数 +1,当计数达到 3 时触发锁定
- 随机延迟
- 每次请求结束都会生成新的
user_token,为下一次请求做准备
三、防护机制总结表
| 防护手段 | 具体实现 | 作用 |
|---|---|---|
| CSRF Token | checkToken() 校验 user_token |
防止跨站请求伪造,同时让工具爆破需要动态获取 token |
| SQL 注入防护 | mysqli_real_escape_string() + PDO 预处理语句 |
彻底杜绝万能密码等注入方式 |
| 账户锁定 | 3 次失败后锁定 15 分钟 | 阻止暴力破解的持续尝试 |
| 错误延迟 | sleep(rand(2,4)) |
大幅降低爆破效率,同时避免暴力破解的自动化识别 |
💡 实战结论
Impossible 难度下:
- 万能密码完全无效,因为预处理语句彻底阻断了注入
- 直接暴力破解几乎不可行,因为 token 动态变化 + 账户锁定 + 错误延迟三重限制
- 要想爆破,需要同时解决三个问题:动态 token、账户锁定、延迟响应,这已经超出了基础渗透测试的范畴,更偏向高级绕过场景。
代码解释汇总
先看这前三段源码,结论先放前面:万能密码只对 Low 难度有效,Medium 和 High 难度都无法直接用万能密码绕过,下面给你拆解清楚原因👇
一、先看你的三段源码差异
1. Low 难度源码
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
- 没有任何过滤 / 转义,直接把用户输入拼接到 SQL 语句中
$pass虽然做了 MD5,但$user字段完全可控,所以可以在username字段注入万能密码,让条件恒为真。
2. Medium 难度源码
$user = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user);
$pass = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass);
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
-
关键:
mysqli_real_escape_string()会对用户输入的特殊字符(单引号'、双引号"等)进行转义 -
你输入的万能密码 payload 里的
'会被转义成\',SQL 语句就会变成:sql
SELECT * FROM `users` WHERE user = 'admin\' or \'1\'=\'1' AND password = 'xxx'; -
这时候注入的内容会被当成普通字符串,条件不会恒为真,万能密码失效。
3. High 难度源码
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
$user = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user);
$pass = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass);
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
- 不仅有
mysqli_real_escape_string()转义,还加了:- Anti-CSRF token(
user_token):每次请求都需要携带合法的动态 token,否则请求直接被拒绝 - 错误延迟(
sleep(rand(0,3))):爆破效率大幅降低,同时账号会被锁定
- Anti-CSRF token(
- 转义 + token 双重防护,万能密码完全无法生效。
二、总结适配情况
| 难度 | 万能密码是否有效 | 原因 |
|---|---|---|
| Low | ✅ 有效 | 无转义,直接拼接 SQL,可在username字段注入 |
| Medium | ❌ 无效 | mysqli_real_escape_string() 转义了单引号,注入 payload 失效 |
| High | ❌ 无效 | 转义 + CSRF token 双重防护,无法注入,且有账号锁定机制 |
💡 补充说明
- Medium/High 难度下,万能密码这条路已经走不通了,只能通过暴力破解(Medium)或绕过 token + 爆破(High)来尝试登录。
- 如果你想练习万能密码,只能在 Low 难度下操作,这也是 DVWA 设计的初衷 ------ 不同难度对应不同的防护与绕过场景。











