DVWA学习之旅之Brute Force
Author: hxd
Date: 2025年4月11日
DVWA的简介
DVWA(Damn Vulnerable Web Application)一个用来进行安全脆弱性鉴定的PHP/MySQL Web 应用,旨在为安全专业人员测试自己的专业技能和工具提供合法的环境,帮助web开发者更好的理解web应用安全防范的过程。
DVWA 一共包含了十个攻击模块,分别是:Brute Force(暴力(破解))、Command Injection(命令行注入)、CSRF(跨站请求伪造)、- File Inclusion(文件包含)、File Upload(文件上传)、Insecure CAPTCHA (不安全的验证码)、SQL Injection(SQL注入)、SQL Injection(Blind)(SQL盲注)、XSS(Reflected)(反射型跨站脚本)、XSS(Stored)(存储型跨站脚本)。包含了 OWASP TOP10 的所有攻击漏洞的练习环境,一站式解决所有 Web 渗透的学习环境。
另外,DVWA 还可以手动调整靶机源码的安全级别,分别为 Low,Medium,High,Impossible,级别越高,安全防护越严格,渗透难度越大。
一般 Low 级别基本没有做防护或者只是最简单的防护,很容易就能够渗透成功;而 Medium 会使用到一些非常粗糙的防护,需要使用者懂得如何去绕过防护措施;High 级别的防护则会大大提高防护级别,一般 High 级别的防护需要经验非常丰富才能成功渗透;
最后 Impossible 基本是不可能渗透成功的,所以 Impossible 的源码一般可以被参考作为生产环境 Web 防护的最佳手段。
下载和搭建过程我就省略了,网上都有,下面让我们直接开始:
Burp Force (暴力破解)
Low Level(开发人员完全忽视了任何保护方法,允许任何人多次任意访问,可以在没有任何影响的情况下对任意用户进行登录)
源码审计
|
源码如下,代码将获取用户输入的用户名和密码并将其进行 md5 加密,然后使用 SQL SELECT 语句进行查询。由于进行了 md5 加密,因此直接阻止了 SQL 注入,因为经过 md5 这种摘要算法之后 SQL 语句就会被破坏(不过这里用 SQL 注入可以登陆成功)。注意到此时服务器只是使用了 isset() 函数验证了参数 Login 是否被设置,参数 username、password 没有做任何过滤,更重要的是没有任何的防爆破机制。
Gesture
首先打开BP抓包,使用Sniper狙击手模式,只更改password,同时加载Payload,如下:
爆破如下,发现password长度不一样:
说明Low Level的用户名是admin,密码是password,登陆如下:
Medium Level(此阶段在验证失败的登陆屏幕上添加睡眠,这意味着当你登录不正确时,在页面可见之前将有两秒钟的等待。这只会减慢一分钟内可处理的请求量,使暴力攻击的时间更长)
代码审计
|
源码如下,Medium 级别的代码主要增加了 mysql_real_escape_string 函数,该函数会对字符串中的特殊符号进行转义,从而对用户输入的参数进行了简单的过滤。相比 low 级别的代码,当登录验证失败时界面将冻结 2 秒,从而影响了爆破操作的效率,不过如果是一个闲来无事并且很有耐心的白帽黑客,爆破出密码仍然是时间问题。
Gesture
继续使用BP抓包,跟Low Level一样的操作,只是由于登录验证失败时界面会冻结2秒,所以会慢一点:
发现密码还是password:
成功登录:
High Level(开发者使用了”CSRF”的反伪造请求,有一个旧的说法表示这种保护可以阻止暴力攻击,但事实并非如此。这个级别扩展了中等级别,在登录失败时等待,但这次是 2 到 4 秒之间的随机时间,这样做的目的是试图混淆任何时间预测。使用验证码表单可能会产生与 CSRF 令牌类似的效果。)
代码审计
|
High 级别的代码使用了stripslashes 函数,进一步过滤输入的内容。同时使用了 Token 抵御 CSRF 攻击,在每次登录时网页会随机生成一个 user_token 参数,在用户提交用户名和密码时要对 token 进行检查再进行 sql 查询。
Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码
写一段python脚本查询:
import requests |
发现每次的user_token都是不一样的,如下:
这里有两种思路:
第一种方法:
也是先使用BP抓包,然后我们使用Pitchfork模式,为password和token添加变量进行爆破,如下:
第一个username导入本地爆破字典,然后第二个token我们打开右边的setting找到Grep-Extract点击add添加
Grep-Extract的基本用法
Grep-Extract功能允许用户通过正则表达式来匹配和提取HTTP请求或响应中的特定数据。用户可以在Burp Suite的Intruder模块中使用Grep-Extract,通过设置特定的正则表达式来过滤和提取需要的数据。例如,在SQL注入测试中,可以使用Grep-Extract来提取数据库中的表名或列名
上述的value值等会自动生成,然后点击OK。在Payloads加载界面为token变量设置Payload type为Recursive grep递归模式,同时设置initial payload for first request要为刚刚在Grep-Extract中的token值,如下:
然后爆破找到密码为password:
然后成功登录:
附录
Low Level
✅ 正常功能部分
if( isset( $_GET[ 'Login' ] ) ) { |
- 如果 URL 中设置了
Login
参数,就执行登录流程。
$user = $_GET[ 'username' ]; |
- 从 GET 参数中获取用户名和密码,并用
md5
对密码进行哈希(这已经过时,后面详说)。
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';"; |
- 构造 SQL 查询语句来验证用户身份。
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( ... ); |
- 使用 mysqli 查询数据库,如果失败就打印错误。
if( $result && mysqli_num_rows( $result ) == 1 ) { |
- 如果查到一个匹配的用户,就说明登录成功。
$row = mysqli_fetch_assoc( $result ); |
- 显示欢迎信息和头像。
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); |
- 关闭数据库连接。
⚠️ 存在的安全问题分析
1. 🚨 SQL 注入漏洞
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';"; |
直接把用户输入拼接进 SQL 语句,容易被 SQL 注入攻击。
示例攻击:
username=admin' --&password=123
这会让原始 SQL 查询变成:
SELECT * FROM `users` WHERE user = 'admin' --' AND password = 'md5hash';
--
表示注释,后面被忽略,导致密码验证被绕过。
✅ 修复方法:
使用 预处理语句(prepared statements):
$stmt = $mysqli->prepare("SELECT * FROM users WHERE user = ? AND password = ?"); |
2. 🔒 弱密码哈希(使用了 md5)
md5
已被认为不安全,容易被彩虹表或暴力破解。应使用
password_hash()
和password_verify()
进行密码加密与校验:
// 注册时保存: |
3. 📥 使用 GET 提交密码
$_GET['password'] |
密码作为 URL 参数发送,很容易泄露,例如:
浏览器历史记录
日志文件
网络嗅探
✅ 应使用 POST 提交表单数据,并通过 $_POST
获取。
4. 📤 XSS(跨站脚本)风险
echo "<p>Welcome to the password protected area {$user}</p>"; |
- 如果用户名中有 HTML/JS 代码,会被直接输出,导致 XSS。
✅ 使用 htmlspecialchars()
进行转义:
echo "<p>Welcome to the password protected area " . htmlspecialchars($user) . "</p>"; |
🔚 总结:这段代码的问题汇总
问题 | 风险级别 | 修复建议 |
---|---|---|
SQL 注入 | 高 | 使用预处理语句 |
使用 md5 加密密码 | 高 | 使用 password_hash |
GET 提交敏感信息 | 中 | 使用 POST 方法 |
没有转义用户输出(XSS) | 中 | 使用 htmlspecialchars |
弱错误处理方式 | 低 | 日志记录+用户友好提示 |
Medium Level
这段代码相比你上一次发的版本,做了一些改进,主要是增加了对用户输入的转义处理(mysqli_real_escape_string
),来减缓 SQL 注入的风险,并在登录失败时加入了一个 sleep(2)
延迟来防止暴力破解。但仍然存在很多安全问题和过时的做法。我们继续逐点分析:
✅ 改进的地方
1. 使用 mysqli_real_escape_string
对用户名和密码进行转义
$user = mysqli_real_escape_string($conn, $_GET['username']); |
✅ 这比直接拼接更安全,防止一些基本的 SQL 注入攻击。
2. 登录失败加了 sleep(2)
sleep( 2 ); |
✅ 这是一个很基础的反暴力破解保护,防止脚本快速尝试密码。但仅靠这个远远不够。
❌ 仍然存在的问题和风险
1. ❗ SQL 注入风险仍然存在
虽然用了 mysqli_real_escape_string
,但这不是完全防护,尤其是面对复杂或绕过型注入。正确的做法是使用预处理语句(prepared statements)。
✅ 正确用法示例:
$stmt = $mysqli->prepare("SELECT * FROM users WHERE user = ? AND password = ?"); |
2. ❗ 使用 md5
加密密码 —— 非常不安全
$pass = md5($pass); |
md5
已被完全淘汰,能被秒破,甚至有公开反查库。应使用
password_hash()
+password_verify()
。
✅ 替代方式:
// 注册时存入: |
3. ❗ 仍使用 GET 请求传递敏感信息
$_GET['username'], $_GET['password'] |
使用 GET 会将用户名和密码暴露在 URL 中(浏览器历史、日志、代理服务器都会记录)。
应使用 POST 提交表单。
✅ 用法:
<form method="POST" action="login.php"> |
$_POST['username'] |
4. ❗ 输出未经过转义,存在 XSS 风险
echo "<p>Welcome to the password protected area {$user}</p>"; |
- 如果用户名是
<script>alert('XSS')</script>
,就会被浏览器执行。
✅ 使用 htmlspecialchars
转义输出:
echo "<p>Welcome to the password protected area " . htmlspecialchars($user, ENT_QUOTES, 'UTF-8') . "</p>"; |
5. ❗ 不安全的数据库错误信息暴露
die( '<pre>' . mysqli_error(...) . '</pre>' ); |
把数据库错误暴露给用户是一个安全隐患。攻击者可以据此构造攻击。
正确做法是:记录日志,展示通用错误信息。
✅ 总结与建议
问题/改进 | 现状 | 建议 |
---|---|---|
SQL 注入防护 | 使用了 mysqli_real_escape_string ,但还不够 |
✅ 使用 prepared statements |
密码哈希 | 使用 md5 (不安全) |
✅ 改用 password_hash / password_verify |
请求方式 | 使用 GET(不安全) | ✅ 改用 POST |
XSS 防护 | 用户输出未转义 | ✅ 使用 htmlspecialchars() |
错误信息 | 暴露数据库错误 | ✅ 隐藏详细错误,只写日志 |
暴力破解防护 | 使用 sleep(2) |
✅ 可以进一步引入登录失败计数机制 |
High Level
这段 PHP 代码是一个带有 CSRF 防护、基本 SQL 查询和登录逻辑 的登录处理脚本。它在你前面发的版本基础上,又增加了一些安全性,比如:
✅ 增加了 CSRF Token 检查
✅ 添加了
stripslashes()
去除魔术引号(在老版本 PHP 中可能有用)✅ 登录失败时加入了 随机 sleep 时间 来抵抗爆破
❌ 但仍然存在几个关键的安全问题
🧩 一步步分析
✅ 1. 检查是否提交了登录请求
if( isset( $_GET[ 'Login' ] ) ) { |
- 表示是一个通过 GET 请求提交的登录表单(不推荐,敏感信息暴露在 URL 中)
✅ 2. CSRF Token 检查
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); |
这是对跨站请求伪造(CSRF)的防护。
user_token
是表单提交的,session_token
是服务端生成的。如果验证失败,通常会重定向或退出(
checkToken()
函数你没贴出来,但应该做了这些)。
✅ 3. 输入处理:去除转义 + SQL 转义
$user = stripslashes( $_GET[ 'username' ] ); |
✅
stripslashes()
:去掉魔术引号(PHP 5.x 遗留问题)✅
mysqli_real_escape_string()
:防止基本 SQL 注入❌
md5($pass)
:非常不安全,见下文
✅ 4. 构造并执行 SQL 查询
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';"; |
这是典型的用户名 + 密码校验语句
❌ 使用了拼接 SQL 语句而不是参数化查询(仍有注入风险)
✅ 5. 判断登录是否成功
if( $result && mysqli_num_rows( $result ) == 1 ) { |
如果只有一条记录,表示用户名和密码匹配
成功时显示头像并欢迎用户
echo "<img src=\"{$avatar}\" />"; |
❗ 未经过 htmlspecialchars()
转义,仍有 XSS 风险
✅ 6. 登录失败处理
sleep( rand( 0, 3 ) ); |
- ✅ 添加了随机延迟,降低爆破攻击效率
✅ 7. 生成 CSRF Token
generateSessionToken(); |
表示页面加载时会为用户创建一个随机的 session token
页面上应该会包含
<input type="hidden" name="user_token" value="...">
❗ 安全问题总结与建议
问题 | 解释 | 建议 |
---|---|---|
❌ 使用 GET 提交表单 | 用户名、密码会暴露在 URL | ✅ 改用 POST |
❌ 使用 md5 存储密码 | 不安全,易被破解 | ✅ 使用 password_hash 和 password_verify |
❌ SQL 拼接 | 即使转义了,仍可能有注入风险 | ✅ 使用 prepared statements |
❌ 输出未转义 | 用户名或头像路径可被注入 JS 脚本 | ✅ 使用 htmlspecialchars() |
⚠ 依赖老函数(如 stripslashes) | 可能说明 PHP 配置问题 | ✅ 建议检查 magic_quotes_gpc 是否关闭 |