DVWA靶场low难度的通关
本文仅用于网络安全学习与防御研究,所有操作均在合法授权的 DVWA 靶场环境中进行,严禁将相关技术用于任何未授权的网络攻击。非法侵入他人网络系统属于违法行为,请遵守《中华人民共和国网络安全法》。
DVWA靶场的难度设置
打开DVWA靶场
点击DVWA Security,将难度设置为low,点击submit,如下图所示:

可从左下角验证修改成功
Brute Force
一 了解Brute Force
1.什么是Brute Force
Brute Force称之为暴力破解,是一种通过不断尝试不同用户名、密码或密钥组合,直到成功登录或验证通过的攻击方式。其核心思想是不依赖漏洞,而是利用计算机高速、重复尝试的能力,对目标进行穷举或字典匹配,从而获取正确的认证信息。
2.什么是字典文件
字典文件本质上是一个普通的文本文件(.txt),其中每一行都存放着一个待尝试的密码或用户名。自动化工具会按照文件中的内容逐行读取,并依次向目标系统发送登录请求,直到找到正确的用户名和密码,或所有内容尝试完成。
3.暴力破解与字典文件的联系
暴力破解主要分为两种:
穷举破解(Exhaustive Search):尝试所有可能的字符组合,理论上一定能够找到正确密码,但耗时较长。
字典破解(Dictionary Attack):利用预先整理好的密码字典进行尝试,相比穷举破解效率更高,也是实际渗透测试中最常见的方法。
注意:在 Kali Linux 中,自带了常用密码字典 rockyou.txt,该字典收录了数百万条真实用户使用过的弱密码,是渗透测试和靶场实验中最常用的密码字典之一,但是这里的实验low难度没必要使用这么大的字典文件,因此去网上找了DVWA靶场相关字典文件(账号字典和密码字典),第四条仅为kali自带密码字典的解压缩操作,本次实验无应用。
4.kali自带字典rockyou.txt的解压缩
首先检验是否存在:

发现存在其压缩包,对其进行解压并验证:

5.什么是Burp Suite
Burp Suite是一款集成化Web应用程序安全测试工具,也是目前 Web 渗透测试领域应用最广泛的工具之一。它能够对浏览器与 Web 服务器之间的 HTTP/HTTPS 请求和响应进行拦截、分析、修改与重放,为漏洞发现、漏洞验证以及安全评估提供了完整的测试环境。
Burp Suite 集成了多种功能模块,包括 Proxy(代理抓包)、Repeater(请求重放)、Intruder(自动化请求)、Decoder(编码解码)、Comparer(响应比较)等。其中,Proxy 模块主要用于拦截浏览器发送的请求;Repeater 模块用于修改并重复发送请求,便于漏洞验证;Intruder 模块则可以根据设定的规则批量发送请求,常用于暴力破解、参数枚举和模糊测试等场景。
6.暴力破解原理
通过第五点的描述可以知道,使用Burp Suite的Proxy和Intruder功能模块可以完成暴力破解(字典破解),具体原理:
Proxy(代理)模块主要用于拦截浏览器与 Web 服务器之间的 HTTP 请求和响应。在 DVWA Brute Force 实验中,首先使用 Proxy 模块拦截浏览器发送的登录请求,分析登录接口的请求方式(GET 或 POST)、请求参数、Cookie 以及其他 HTTP 头信息。Intruder(入侵器)模块主要用于自动化发送 HTTP 请求。在完成 Proxy 抓包后,将登录请求发送至 Intruder,指定需要替换的参数(通常是用户名或密码),再导入用户名或密码字典。随后,Intruder 会按照字典中的内容,自动替换请求中的参数,并不断向目标服务器发送登录请求,每发送一次请求,服务器都会返回对应的响应结果。当某一次请求返回的响应长度、状态码或页面内容与其他请求明显不同时,通常说明该用户名和密码组合登录成功。
7.打开Burp Suite
输入以下命令打开Burp Suite:

打开后选择Temporary project in memory,下一步,选择Use Burp defaults(使用 Burp 默认配置),start Burp

8.Burp Suite的前置条件与配置
由于Burp Suite的默认监听端口是8080,而靶场设置的端口是8080,因此将Burp Suite的监听端口改为8081,点击Proxy-Proxy settings-勾选127.0.0.1:8080-Edit,修改8080为8081

可以使用Burp Suite的内置浏览器,点击Open browser,在地址栏输入localhost:8080,进行登录:


本篇文章以kali自带浏览器firefox为例,那么接下来就要对浏览器进行配置
9.firefox浏览器的配置
首先打开firefox浏览器,点击settings

下拉到最下,点击Network Settings的Settings

按照下图所示进行手动代理配置

firefox默认代理不经过本地,那么现在要对其进行修改,在firefox浏览器地址栏输入about:config,回车

点击Accept the Risk and Continue,在下图搜索栏输入network.proxy.allow_hijacking_localhost,将默认的false改为true:

关闭firefox重新打开,输入localhost:8080打开DVWA靶场,进行登录
10.自制字典文件(含用户名字典文件和密码字典文件)
输入以下命令创建用户名和密码字典文件

之后输入以下命令创建用户名字典文件和密码字典文件

当然nano创建后需要将自备的用户名和密码复制其中,保存退出。
二 Brute Force暴力破解的实战演示
1.优先打开burpsuite
前面对firefox浏览器设置burp代理,firefox浏览器流量全部流向burp 8081端口,之后再由burp确定是否发向其访问的服务器,因此,需要先打开burpsuite才可以访问DVWA靶场(别忘对重新打开的burpsuite配置8081端口)
2.开启Proxy功能模块
点击intercept off切换为intercept on

3.进行登录操作
在DVWA靶场的Brute Force处输入任意用户名和密码(本文全都输入1)点击登录后Burpsuite界面跳转出来

4.发送至Intruder功能模块
在下方Request窗口内容空白处右键,点击send to Intruder,然后点击上方Intruder模块

5.Intruder攻击模式的介绍

- Sniper attack(狙击手模式)
核心逻辑:仅使用 1 组 payload 集合,按顺序逐个对每个注入位置进行遍历替换。
执行规则:当存在多个注入位置时,先保持其他位置原始值不变,仅对第一个注入位置依次代入 payload 集中的每一条内容;完成第一个位置的全部遍历后,再对第二个注入位置执行相同操作,以此类推。
请求总量:payload 条目数 × 注入位置数量
适用场景:单参数爆破场景,例如已知固定用户名、仅爆破登录密码;或逐个测试多个参数的单独注入效果,是单变量爆破的默认选型。 - Battering ram attack(撞击锤模式)
核心逻辑:仅使用 1 组 payload 集合,每次请求将所有注入位置同时替换为同一条 payload 内容。
执行规则:每发起一次请求,所有标记的注入位置都会填入完全相同的 payload 值,同步完成替换。
请求总量:payload 条目数
适用场景:多参数需传入相同值的场景,例如部分系统中用户名与密码一致、多个表单字段需填写同一口令 / 标识的验证场景。 - Pitchfork attack(干草叉模式)
核心逻辑:为每个注入位置分配独立的 payload 集合,各集合按行一一对应、同步并行遍历。
执行规则:第 N 次请求时,第一个位置取第 1 组 payload 的第 N 条,第二个位置取第 2 组 payload 的第 N 条,以此类推;遍历以最短的 payload 集为准,超出部分直接舍弃。
请求总量:所有 payload 集中的最小条目数
适用场景:账号密码成对验证场景,例如已掌握一批已知的账号密码对应关系,需逐条批量验证有效性;或多个参数存在明确的一一对应关系的爆破任务。 - Cluster bomb attack(集束炸弹模式)
核心逻辑:为每个注入位置分配独立的 payload 集合,对所有集合进行全排列组合式遍历。
执行规则:第 1 组 payload 的每一条内容,都会与第 2 组 payload 的每一条内容进行组合,覆盖所有可能的搭配方式,即笛卡尔积遍历。
请求总量:第 1 组 payload 条目数 × 第 2 组 payload 条目数 × … × 第 N 组 payload 条目数
适用场景:多参数未知的穷举爆破场景,是 DVWA Brute Force 等登录口暴力破解的标准选型 —— 用户名字典与密码字典两两组合,遍历所有可能的账号密码组合。
很明显这里需要使用第四种攻击模式-Cluster bomb attack
6.加载payload
切换到集束炸弹模式后,在用户名和密码处添加payload,选取后点击add$,下图第一行的两团阴影处即为添加后形式

7.导入字典
在添加payload后右侧出现的payloads界面有相关两个payload配置界面

点击load添加前面创建的字典文件,注意,按照添加payload顺序确定1-1和2-1代表的意义(本文1-1指的是username)


8.开始攻击
导入字典文件后点击棕色按钮-start attack

根据爆破后返回信息的长度可以判断用户名和密码是否正确。

由上图可知,序号为1的返回长度比其余的长,且其余返回长度相近,那么可以确定序号为1的账号密码为正确
9.先帝创业未半而中道崩殂
kali自带的社区版burp除了第一种攻击模式外其余都不能使用,那么只好使用第一种攻击模式了(后续查资料是破解速率阉割,但是所有返回结果刚好可以确定正确的账号密码),根据信息收集,得知DVWA靶场账号默认admin,那么现在只需要重新刚才登录操作,将用户名输入为admin即可

根据所有返回结果的长度分析可知,序号为1的返回结果为正确密码
10.结果验证
输入密码password,返回结果如下

三 源码分析与修复方案
1.点击Brute Force下方的view source

出现以下源码:
<?php
if( isset( $_GET[ 'Login' ] ) ) {
// Get username
$user = $_GET[ 'username' ];
// Get password
$pass = $_GET[ 'password' ];
$pass = md5( $pass );
// Check the database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
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}\" />";
}
else {
// Login failed
echo "<pre><br />Username and/or password incorrect.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
尽管没系统学习php语言,但是只要学习过c/c++看懂基础php代码还是没问题的。
2.源码分析
首先通过 $_GET 获取用户输入的用户名和密码:
$user = $_GET[ 'username' ];
$pass = $_GET[ 'password' ];
随后使用 md5() 对密码进行处理:
$pass = md5( $pass );
程序将用户名和处理后的密码直接拼接到 SQL 查询语句中:
$query = "SELECT * FROM `users`WHERE user = '$user'AND password = '$pass';";
如果数据库中恰好存在一条匹配记录,则输出登录成功信息和用户头像;否则返回用户名或密码错误。
该页面能够被暴力破解的主要原因是:程序没有限制登录失败次数,也没有设置请求延迟、账户锁定或验证码。Burp Suite 因此可以不断发送不同的用户名和密码组合,并通过响应内容或响应长度判断是否登录成功。
此外,源码使用 GET 方法提交密码,密码会直接出现在 URL 中;用户名也被直接拼接到 SQL 语句中,存在 SQL 注入风险。
3. 修复方案
针对该登录功能,可以采用以下措施:
1)限制同一账号或 IP 在一定时间内的登录次数;
2)多次登录失败后增加延迟、验证码或临时锁定;
3)使用 POST 方法提交用户名和密码,并启用 HTTPS;
4)使用参数化查询,避免直接拼接 SQL 语句;
5)使用 password_hash() 和 password_verify() 保存、验证密码,而不是使用 MD5;
6)记录异常登录行为,便于发现暴力破解攻击。
Command Injection
一 了解Command Injection
1.什么是Command Injection
Command Injection,中文通常称为“命令注入”或“操作系统命令注入”,是指 Web 应用程序将用户输入直接拼接到操作系统命令中,并交给系统 Shell 执行,从而导致攻击者可以在原有命令之外插入并执行其他系统命令的一类漏洞。
2.常用Command都有哪些
注入测试前,需要了解 Linux Shell 中常见的命令连接符。
(1)分号 ;
无论前面的命令是否执行成功,都会继续执行后面的命令。
command1; command2
示例:
127.0.0.1; whoami
(2)逻辑与 &&
只有当前面的命令执行成功时,才会执行后面的命令。
command1 && command2
示例:
127.0.0.1 && whoami
(3)逻辑或 ||
只有当前面的命令执行失败时,才会执行后面的命令。
command1 || command2
示例:
不存在的IP || whoami
(4)管道符 |
将前一个命令的标准输出作为后一个命令的标准输入。
command1 | command2
3.DVWA Command Injection 模块的正常功能
DVWA 的 Command Injection 页面提供了一个 IP 地址输入框,其正常功能是接收用户输入的 IP 地址,并由服务器执行 Ping 命令,以检测目标地址是否能够连通。
二 DVWA靶场Command Injection实战
1.验证Command Injection正常功能
输入127.0.0.1

根据返回结果图可知模块ping命令正常执行
2.分号;检测命令注入漏洞
输入127.0.0.1;whoami

根据返回结果图可知模块在ping执行后继续执行了whoami命令,证明存在命令注入漏洞(whoami命令作用是输出当前操作环境下的有效用户名)
3.其他命令
前面知道whoami输出当前操作环境下的有效用户名,还有一些其他命令可以进行使用,这里不做演示,例如:
id命令可以显示当前用户的 UID、GID 和所属用户组
pwd 命令用于显示当前工作目录
uname -a 用于显示系统内核、主机名和体系结构等基本信息
三 源码分析与修复方案
1.点击View Source查看源码
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = $_REQUEST[ 'ip' ];
// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
// Feedback for the end user
echo "<pre>{$cmd}</pre>";
}
?>
2.源码分析
首先,程序判断用户是否点击了 Submit 按钮:
if( isset( $_POST[ 'Submit' ] ) )
随后通过 $_REQUEST['ip'] 获取用户输入的 IP 地址:
$target = $_REQUEST[ 'ip' ];
程序使用 php_uname('s') 判断服务器操作系统。如果是 Windows,则执行:
shell_exec( 'ping ' . $target );
如果是 Linux 等类 Unix 系统,则执行:
shell_exec( 'ping -c 4 ' . $target );
最后通过 echo 将命令执行结果显示在页面中:
echo "<pre>{$cmd}</pre>";
漏洞产生的原因是:程序将用户输入的 $target 直接拼接到系统命令中,并使用 shell_exec() 执行,没有进行任何格式验证或特殊字符过滤。
因此,当输入:
127.0.0.1; whoami
Linux 服务器实际执行的命令相当于:
ping -c 4 127.0.0.1; whoami
分号将两条命令分开,从而使服务器在执行 Ping 命令后继续执行 whoami,形成命令注入漏洞。
3. 修复方案
可以采用以下方法进行修复:
1)使用 filter_var() 验证输入是否为合法 IP 地址;
2)禁止输入分号;、管道符|、&& 等命令连接符;
3)避免将用户输入直接拼接到系统命令中;
4)确实需要执行系统命令时,可使用 escapeshellarg() 对参数进行转义;
5)使用低权限账户运行 Web 服务,降低漏洞造成的影响。
SQL Injection
一 了解SQL Injection
1.什么是SQL Injection
SQL Injection(SQL注入)是一种针对数据库的Web安全漏洞。
当Web应用程序将用户输入的数据直接拼接到SQL查询语句中,并且没有进行有效过滤或参数化处理时,攻击者可以构造特殊输入,使数据库执行原本设计之外的SQL语句。
2.常用SQL语句及其功能
1)SELECT查询数据
格式:SELECT 字段 FROM 表名;
例如: SELECT * FROM users; 表示查询users表中的所有数据
2)WHERE条件查询
WHERE用于限制查询条件
例如:SELECT * FROM users WHERE id=1; 表示查询users表中id=1的数据
3) UNION联合查询
UNION用于合并多个SELECT查询结果,将多条查询的结果按行纵向(上下)拼接为一个统一的结果集。
使用规则:参与合并的每条 SELECT 语句,返回的字段数量必须完全一致,且对应位置的字段数据类型相互兼容。
例如:SELECT username FROM users UNION SELECT password FROM users;
执行效果:将 users 表中所有用户名与密码数据纵向合并,输出在同一列中;UNION 会自动去除重复行,使用 UNION ALL 可保留全部数据,执行效率更高。
4)SQL注释符
--(SQL通用)
#(MySQL/MariaDB特有)
作用是让数据库忽略后面的SQL语句
5)order by
ORDER BY 是 SQL 中用于对查询结果进行排序的语句。
基本语法:
SELECT 字段名 FROM 表名 ORDER BY 排序字段;
例如:
SELECT * FROM users ORDER BY id; 表示查询 users 表中的所有数据,并按照 id 字段进行升序排列。
默认情况下:
ASC:升序排列(默认)
DESC:降序排列
例如:
SELECT * FROM users ORDER BY id DESC; 表示按照 id 倒序排列。
在 SQL 注入测试中,ORDER BY 通常用于判断当前查询语句返回的字段数量。
6) 什么是information_schema
information_schema是MySQL自带的一个系统数据库,里面保存整个MySQL数据库服务器的元数据信息。
例如:
有哪些数据库
每个数据库有哪些表
每张表有哪些字段
字段的数据类型等
7) LIMIT
LIMIT 用于限制 SQL 查询返回的记录数量,常用于分页查询或逐条查看数据。
格式:LIMIT 偏移量, 返回数量
例如:LIMIT 1,1 表示跳过第1条记录,返回下一条数据。
3.DVWA SQL Injection模块的正常功能
DVWA的SQL Injection模块提供一个User ID查询功能。
用户输入:User ID
服务器根据输入的ID查询数据库中对应用户信息。
二 DVWA靶场SQL Injection实战
1. 验证SQL Injection正常功能
输入1,查看返回结果

由图可知,模块返回id对应的用户信息,功能正常
2. 判断注入点
输入1'观察返回情况

发现报错
进一步输入1' and 1=1 # 观察返回结果

前后两次输入结果说明存在注入点,直接将用户输入的语句拼接到SQL语句中。
3. 判断字段数量(order by)
输入1' order by 1 #

返回结果正常
输入1' order by 2 #

返回结果依旧正常
输入1' order by 3 #

发现返回结果报错,可确定字段数量为2。
4. 确认数据回显位
数据回显位可简单理解为数据库查询结果显示在网页上的位置(并非所有的查询结果都会在网页上显示)
输入1' union select 1,2#

页面首先返回 ID 为1的原始用户信息 admin/admin,随后返回联合查询构造的 1/2。其中数字1显示在 First name 位置,数字2显示在 Surname 位置,说明原查询包含两个字段,且两个字段均能够在页面中回显。
5. 获取数据库基础信息
输入 1' union select 1,concat(user(),',',database(),',',version())#

通过 UNION SELECT 将数据库账号、当前数据库名和数据库版本放入第二个回显位,并使用 concat() 以逗号连接。页面成功返回 app@localhost、dvwa 和 MariaDB 版本信息,说明联合查询执行成功,同时确认第二个字段可以用于回显数据库数据。
6. 枚举当前数据库下的数据表
输入1' union select 1,group_concat(table_name) from information_schema.tables where table_schema='dvwa'#

利用 information_schema.tables 查询 dvwa 数据库中的表名,并使用 group_concat() 将查询结果合并到第二个回显位。页面返回 guestbook,users,说明当前数据库中存在 guestbook 表和 users 表。由于 users 表可能存储用户认证信息,因此下一步对该表的字段名进行枚举。
7. 枚举目标表的字段名
输入1' union select 1,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'#

利用 information_schema.columns 查询当前数据库中 users 表的字段名,并通过 group_concat() 将结果合并显示。页面返回 user 和 password 等字段,说明该表保存了用户认证信息,下一步可以查询这两个字段中的数据。
8. 获取用户数据
输入1' union select user,password from users limit 1,1#

使用 UNION SELECT 查询 users 表中的 user 和 password 字段。页面在第一个回显位置显示用户名 admin,在第二个回显位置显示对应的密码 MD5 摘要,说明已成功获取用户认证数据。LIMIT 1,1 表示跳过一条联合查询结果后只返回一条记录。
三 源码分析与修复方案
1. 点击view source查看源码
<?php
if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input
$id = $_REQUEST[ 'id' ];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
mysqli_close($GLOBALS["___mysqli_ston"]);
}
?>
2. 源码分析
首先,程序判断用户是否点击了 Submit 按钮:
if( isset( $_REQUEST[ 'Submit' ] ) )
随后通过 $_REQUEST['id'] 获取用户输入的 ID:
$id = $_REQUEST[ 'id' ];
程序将用户输入的 $id 直接拼接到 SQL 查询语句中:
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
该语句原本用于根据用户 ID 查询 users 表中的姓名信息。例如输入:
1
实际执行的 SQL 语句为:
SELECT first_name, last_name
FROM users
WHERE user_id = '1';
随后,程序通过 mysqli_query() 执行 SQL 查询:
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query);
再使用 mysqli_fetch_assoc() 逐行读取查询结果:
while( $row = mysqli_fetch_assoc( $result ) )
从每条记录中获取 first_name和last_name 字段:
$first = $row["first_name"];
$last = $row["last_name"];
最后通过 echo 将查询结果显示在页面中:
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
漏洞产生的原因是:程序将用户输入的 $id 直接拼接到 SQL 语句中,没有使用参数化查询,也没有对单引号、UNION SELECT 和注释符等内容进行安全处理。
因此,当输入:
1' OR '1'='1'#
实际执行的 SQL 语句相当于:
SELECT first_name, last_name
FROM users
WHERE user_id = '1' OR '1'='1';
由于 '1'='1' 永远成立,因此数据库会返回 users 表中的多条用户记录,形成 SQL 注入漏洞。
同样可以利用 UNION SELECT 将其他查询结果合并到原查询中,并通过 First name 和 Surname 两个位置显示出来。
3. 修复方案
1)使用参数化查询或预编译语句,禁止直接拼接用户输入;
2)如果 id 只能为数字,应验证输入是否为合法整数;
3)不向页面直接返回详细的数据库错误信息;
4)为数据库账号配置最小权限,降低漏洞造成的影响;
5)对异常查询和输入行为进行日志记录。
File Upload
一 了解File Upload漏洞
1.什么是File Upload漏洞
File Upload(文件上传)漏洞是指Web应用程序在实现文件上传功能时,由于没有对用户上传的文件进行有效的安全校验,导致攻击者可以上传恶意文件,从而可能造成服务器文件泄露、代码执行等安全问题。
2.文件上传漏洞的产生原因
文件上传漏洞产生的主要原因包括:
1)未对上传文件类型进行严格限制;
2)仅通过文件扩展名判断文件类型,缺少文件内容检测;
3)未限制上传目录执行脚本文件;
4)未对上传文件名称进行安全处理;
5)未限制上传文件大小;
6)服务器端缺少有效的权限控制。
3.DVWA File Upload模块的正常功能
DVWA File Upload模块用于模拟Web应用中的文件上传功能。用户可以选择本地文件并上传到服务器指定目录,服务器接收文件后保存,并返回上传结果。
二 DVWA靶场 File Upload实战
1.验证DVWA靶场 File Upload模块的正常功能
点击browse上传文件,选择我们自己做的test1.txt文件

点击upload上传,发现上传成功

访问上传文件进行验证
将上图返回结果拼接到网页url,可以看到上传的test1.txt文件内容,验证上传成功

2.上传PHP文件
自制php文件,内容如下,作用是输出服务器的php版本信息

上传此文件,发现上传成功

将返回结果拼接,尝试访问文件,发现成功返回服务器的php版本信息

3.(扩展)一句话木马和WebShell
一句话木马是一种极其简短的 WebShell。
它的特点:代码非常短,但是可以接收外部输入并执行服务器代码。
为什么叫“一句话”?
因为很多 WebShell 可以压缩成一行代码。
例如 PHP 类型:
<?php @eval($_POST['cmd']); ?>
这一句话包含核心功能:
1)接收 POST 参数 cmd
2)将内容作为 PHP 代码执行
WebShell 是一种通过 Web 服务运行的脚本程序,它提供了远程控制服务器的能力。
在真实渗透测试中,攻击者通常会进一步利用文件上传漏洞上传WebShell,例如一句话木马,并通过WebShell管理工具进行后续操作。但由于本实验主要验证DVWA Low难度文件上传漏洞,因此仅验证服务器是否能够解析上传文件。
三 源码分析与修复方案
1.点击view source查看源码
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
// Can we move the file to the upload folder?
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
// No
echo '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
?>
2.源码分析
首先,程序判断用户是否点击了上传按钮:
if( isset( $_POST[ 'Upload' ] ) )
当用户点击 Upload 后,程序进入文件上传处理流程。
随后定义文件上传保存路径:
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
其中:
DVWA_WEB_PAGE_TO_ROOT 表示 DVWA 网站根目录;
hackable/uploads/ 是文件上传后的存储目录。
因此,用户上传的文件最终会被保存到:
hackable/uploads/目录下。
接下来程序通过:
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
获取用户上传的文件名,并拼接到目标路径后面。
其中:
$_FILES['uploaded']['name']表示用户上传文件的原始名称。
例如上传:
test.jpg
那么最终路径:
hackable/uploads/test.jpg
然后程序使用:
move_uploaded_file(
$_FILES[ 'uploaded' ][ 'tmp_name' ],
$target_path
)
将上传文件从临时目录移动到目标上传目录。
其中:
$_FILES['uploaded']['tmp_name']表示 PHP 接收到文件后暂时保存的位置;
$target_path表示最终保存路径。
如果移动失败:
echo '<pre>Your image was not uploaded.</pre>';页面提示上传失败。
如果移动成功:
echo "<pre>{$target_path} succesfully uploaded!</pre>";页面显示文件保存路径。
3.修复方案
1)对上传文件进行严格的类型检查,限制允许上传的文件扩展名和 MIME 类型;
2)对上传文件内容进行检测,避免攻击者通过伪造文件后缀绕过检查;
3)不直接使用用户上传的文件名保存文件,应使用随机文件名,避免文件名冲突和恶意文件名;
4)将上传目录设置为不可执行目录,禁止服务器解析上传目录中的脚本文件;
5)限制上传文件大小,防止攻击者上传超大文件导致服务器资源耗尽;
6)对上传文件进行权限控制,避免上传文件被直接访问或执行;
7)对异常上传行为进行日志记录,便于发现和追踪恶意上传行为。
CSRF
一 了解CSRF
1.什么是CSRF
CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种利用用户已登录身份,诱导用户向目标网站发送非本人意愿请求的攻击。
攻击者通常会构造恶意链接或网页,当已登录目标网站的用户访问时,浏览器会自动携带Cookie等身份凭证,使服务器误以为该请求是用户本人发起的,从而执行修改密码、修改邮箱、转账或删除数据等操作。
CSRF的核心特点是:攻击者伪造的是用户的操作,而不是直接窃取用户的账号密码。
2.CSRF漏洞的产生原因
CSRF漏洞通常由以下原因共同导致:
1)浏览器会自动携带身份凭证
用户访问目标网站时,浏览器会自动在请求中携带Cookie、Session等认证信息。
2)服务器仅依赖Cookie识别用户身份
服务器只判断用户是否已经登录,却没有确认请求是否由用户主动发起。
3)敏感操作缺少防伪验证
修改密码、修改资料等操作没有使用CSRF Token、二次验证或来源校验。
4)请求参数容易被攻击者构造
敏感操作的请求地址和参数固定,攻击者可以提前构造相同格式的恶意请求。
因此,CSRF攻击成立通常需要满足:用户已经登录目标网站、浏览器保存着有效身份凭证,并且目标网站没有有效的CSRF防护措施。
3.CSRF漏洞的危害
CSRF漏洞可能导致攻击者借助受害者身份执行未授权操作,例如:
1.修改用户密码、邮箱和个人资料;
2.发送消息、发布内容或关注指定账号;
3.删除数据或修改系统配置;
4.发起转账、下单等敏感业务操作;
5.利用管理员身份执行高权限操作。
CSRF的危害程度取决于受害者拥有的权限。普通用户受到攻击时可能影响个人账户;管理员受到攻击时,则可能危及整个系统。
二 CSRF漏洞实战复现
1.验证DVWA靶场CSRF模块的正常功能
由下图明显看出该模块功能为更改DVWA靶场的密码,现将其更改为11,同时下图URL处可直观看到

现在对更改的密码进行验证,退出重新输入原本密码,发现登录失败

输入更改后的密码11,发现登录成功
2.构造CSRF恶意请求
在CSRF功能模块重新输入密码22,进行更改,将URL:http://localhost:8080/vulnerabilities/csrf/?password_new=22&password_conf=22&Change=Change#
更改为:http://localhost:8080/vulnerabilities/csrf/?password_new=33&password_conf=33&Change=Change# 在相同浏览器打开新界面,输入更改后的URL,发现成功登录

3.漏洞验证
对密码进行验证,输入22,发现登录失败,而输入33登录成功


三 源码分析与修复方案
1.点击view source查看源码
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
2.源码分析
首先,程序判断用户是否提交了修改密码请求:
if( isset( $_GET[ 'Change' ] ) )
当请求中存在Change参数时,程序进入密码修改流程。
随后程序通过:
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
获取用户输入的新密码和确认密码。
其中:
$_GET['password_new']表示用户输入的新密码;
$_GET['password_conf']表示用户再次输入的确认密码。
接下来程序通过:
if( $pass_new == $pass_conf )
判断两次输入的密码是否一致。
如果两次密码不一致:
echo "<pre>Passwords did not match.</pre>";页面提示两次输入的密码不匹配。
如果两次密码一致,程序通过:
$pass_new = mysqli_real_escape_string(
$GLOBALS["___mysqli_ston"],
$pass_new
);
对密码中的特殊字符进行转义,避免输入内容破坏SQL语句结构。
随后程序使用:
$pass_new = md5( $pass_new );对新密码进行MD5哈希处理。
然后程序构造SQL语句:
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
其中:
users表示用户信息表;
password表示用户密码字段;
dvwaCurrentUser()表示获取当前登录用户的用户名。
因此,该SQL语句会将当前登录用户的密码修改为新的密码。
接下来程序通过:
mysqli_query($GLOBALS["___mysqli_ston"], $insert)执行SQL语句,更新数据库中的用户密码。
如果密码修改成功:
echo "<pre>Password Changed.</pre>";页面提示密码修改成功。
最后程序通过:
mysqli_close($GLOBALS["___mysqli_ston"])关闭数据库连接。
该代码仅通过当前登录用户的Session判断用户身份,没有验证请求来源,也没有使用CSRF Token确认请求是否由用户本人主动发起。因此,攻击者可以构造包含password_new、password_conf和Change参数的恶意请求,利用用户当前的登录状态修改用户密码,从而形成CSRF漏洞。
3.修复方案
1)修改密码等敏感操作应使用POST请求,避免密码参数直接出现在URL、浏览器历史记录和服务器日志中;
2)在修改密码表单中添加CSRF Token,服务器处理请求时验证Token是否与Session中保存的Token一致;
3)修改密码前要求用户输入当前密码,防止攻击者仅凭用户登录状态直接修改密码;
4)检查请求中的Origin或Referer字段,判断请求是否来自合法网站;
5)为用户身份认证Cookie设置SameSite属性,限制浏览器在跨站请求中自动携带Cookie;
6)不应使用MD5存储密码,应使用password_hash()等安全的密码哈希函数;
7)使用预处理语句更新数据库,避免直接拼接SQL语句带来的安全风险;
8)对修改密码等敏感操作进行日志记录,便于发现和追踪异常操作。
XSS
一 了解XSS
1.什么是XSS
XSS(Cross-Site Scripting,跨站脚本攻击)是一种Web安全漏洞,为了避免与CSS(层叠样式表)缩写混淆,通常将跨站脚本攻击简称为XSS。
当网站将用户输入的内容直接输出到网页中,并且没有进行正确过滤或编码时,攻击者可以向页面中插入恶意脚本。其他用户访问该页面后,浏览器可能把这些内容当作正常代码执行。
XSS的核心是:攻击者将恶意脚本注入网页,并使脚本在其他用户的浏览器中执行。
2.XSS漏洞的产生原因
1)网站直接接收用户提交的数据,没有进行严格的输入验证;
2)用户输入在输出到网页前,没有进行HTML实体编码;
3)程序将不可信数据直接拼接到HTML、JavaScript或URL中;
4)前端程序直接使用innerHTML、document.write()等危险方式处理用户输入;
5)网站缺少内容安全策略(CSP)等辅助防护措施。
XSS漏洞产生的根本原因是:
程序没有正确区分用户输入的数据和网页中可以执行的代码。
3.XSS的种类
1)反射型XSS(Reflected XSS)
恶意代码通常包含在URL参数或请求参数中。服务器接收到参数后,将其直接返回到页面中,浏览器随即执行恶意脚本。
反射型XSS一般不会长期保存在服务器中,通常需要诱导用户点击恶意链接。
2)存储型XSS(Stored XSS)
攻击者提交的恶意代码会被保存到数据库、留言板或用户资料等位置。
其他用户访问相关页面时,服务器读取并输出恶意代码,导致脚本在浏览器中执行。存储型XSS可以持续影响多个用户,危害通常较大。
3)DOM型XSS(DOM-based XSS)
DOM型XSS主要发生在浏览器前端。
前端JavaScript读取URL、表单等位置中的不可信数据,并将其写入网页DOM结构,从而导致恶意脚本执行。
该过程可能完全由浏览器完成,不一定需要服务器将恶意代码返回到页面中。
4.三种XSS的区别
| XSS类型 | 恶意代码来源 | 是否存储在服务器 | 主要处理位置 | 常见触发方式 |
|---|---|---|---|---|
| 反射型XSS | URL或请求参数 | 否 | 服务器返回页面 | 用户点击恶意链接 |
| 存储型XSS | 留言、评论或用户资料 | 是 | 服务器和数据库 | 用户访问含恶意内容的页面 |
| DOM型XSS | URL、锚点或前端输入 | 通常不存储 | 浏览器前端 | 前端脚本处理恶意数据 |
三种XSS的主要区别在于:
反射型XSS由服务器将恶意输入立即返回;
存储型XSS会将恶意代码保存在服务器中;
DOM型XSS主要由浏览器中的JavaScript处理不可信数据而产生。
5.XSS漏洞的危害
1)窃取用户的Cookie、Token等身份认证信息;
2)冒充用户执行操作,例如发送消息、修改资料或提交表单;
3)篡改网页内容,插入虚假信息或钓鱼页面;
4)获取用户在页面中输入的账号、密码等敏感信息;
5)将用户重定向到恶意网站;
6)利用管理员身份执行高权限操作;
7)传播恶意脚本,持续影响更多用户。
XSS漏洞的危害程度取决于脚本能够访问的数据、目标用户权限以及网站的安全防护情况。
二 XSS漏洞实战复现
1. XSS(Reflected)
1.1 Reflected XSS模块的正常功能
输入 1,返回Hello 1,说明该模块会接收用户输入,并将输入内容返回到页面中。

1.2 构造测试代码
输入<script>alert("x");</script> 页面弹出x

1.3 使用开发者工具进行验证
按F12打开开发者工具,进入查看器,搜索alert("x")

该内容没有被转换为普通文本,而是被浏览器识别为JavaScript代码并执行。结合前面成功弹出的提示框,可以确认该模块存在反射型XSS漏洞。
2. XSS(Stored)
2.1 Stored XSS模块的正常功能
分别输入11和Hello

点击Sign Guestbook后,页面下方显示刚刚提交的姓名和留言,说明该模块会接收用户输入,将其保存后显示在留言板中。

2.2 构造测试代码
分别输入22和<script>alert("x");</script>点击Sign Guestbook后,浏览器弹出内容为x的提示框,说明提交的JavaScript代码被浏览器执行。

刷新页面,发现依旧弹出x

2.3 使用开发者工具进行验证
按下F12,搜索alert

可以在留言板对应的DOM结构中找到:<script>alert("x");</script>
该内容没有被转换为普通文本,而是被保存并作为JavaScript代码执行。结合刷新页面后仍然弹出提示框,可以确认该模块存在存储型XSS漏洞。
3. XSS(DOM)
3.1 DOM XSS模块的正常功能
进入DOM XSS模块后,页面提供语言选择功能。

选择不同语言后,浏览器地址栏中的default参数会发生变化,例如:
?default=English

这说明页面中的JavaScript会读取URL中的default参数,并根据参数内容修改页面中的语言选项。
3.2 构造测试代码
直接在URL处输入<script>alert("x");</script> 弹出x

3.3 使用开发者工具进行验证
按下F12,输入alert搜索

可以发现URL参数中的测试代码已经被前端JavaScript写入页面DOM结构,并被浏览器识别为JavaScript代码执行。
三 源码分析与修复方案
1.点击view source查看三种XSS源码
1.1 Reflected XSS
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}
?>
1.2 Stored XSS
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitize name input
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
1.3 DOM XSS
<?php
# No protections, anything goes
?>
2.源码分析
2.1 Reflected XSS
首先,程序通过:
header ("X-XSS-Protection: 0");关闭浏览器旧版的XSS过滤功能,使浏览器不会自动拦截页面中的反射型XSS代码。
随后程序判断URL中是否存在name参数,并且参数内容不为空:
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL )
其中:
array_key_exists("name", $_GET)用于判断GET请求中是否存在name参数;
$_GET['name'] != NULL用于判断用户输入是否为空。
接下来程序通过:
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';将用户输入的内容直接输出到页面中。
程序没有使用htmlspecialchars()等函数对<、>等特殊字符进行HTML编码,因此当用户输入:
<script>alert("x");</script>浏览器会将其识别为JavaScript代码并执行,从而产生反射型XSS漏洞。
2.2 Stored XSS
首先,程序判断用户是否点击了留言提交按钮:
if( isset( $_POST[ 'btnSign' ] ) )当请求中存在btnSign参数时,程序进入留言处理流程。
随后程序获取用户提交的留言内容和用户名:
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
其中:
$_POST['mtxMessage']表示用户提交的留言内容;
$_POST['txtName']表示用户提交的用户名;
trim()用于删除字符串两端的空白字符。
接下来程序通过:
$message = stripslashes( $message );删除留言内容中的反斜杠。
随后使用:mysqli_real_escape_string(...)对用户名和留言内容中的特殊字符进行SQL转义,降低SQL注入风险。
但是,mysqli_real_escape_string()只能对SQL语句中的特殊字符进行处理,不能阻止HTML标签和JavaScript代码被浏览器执行。
然后程序构造SQL语句:
$query = "INSERT INTO guestbook ( comment, name )
VALUES ( '$message', '$name' );";
其中:
guestbook表示留言表;
comment表示留言内容字段;
name表示用户名字段。
最后程序通过:
mysqli_query($GLOBALS["___mysqli_ston"], $query)将用户名和留言内容保存到数据库中。
由于程序没有对用户输入进行HTML编码,恶意脚本可以被保存到数据库。当留言展示页面再次读取并直接输出该内容时,浏览器会执行其中的JavaScript代码,从而产生存储型XSS漏洞。
2.3 DOM XSS
该源码中只有:
# No protections, anything goes这表示Low难度下没有设置任何防护措施。
但是,这段PHP代码本身并没有处理用户输入,DOM型XSS漏洞主要存在于页面前端的JavaScript代码中。
前端JavaScript会读取URL中的default参数,并将参数内容动态写入页面DOM结构。
当URL参数中包含:
<script>alert("x");</script>且前端程序使用document.write()、innerHTML等方式直接写入页面时,浏览器会将其识别为JavaScript代码并执行。
因此,DOM型XSS的根本原因是:
程序将URL中的不可信数据直接写入DOM结构,没有进行过滤或HTML编码。
3.修复方案
3.1 Reflected XSS
1)对用户输入进行严格验证,只允许符合业务要求的字符和长度;
2)在将用户输入输出到HTML页面前,使用htmlspecialchars()进行HTML实体编码;
3)根据数据所在位置采用对应的输出编码,避免将不可信数据直接拼接到HTML或JavaScript中;
4)不要依赖浏览器自带的XSS过滤功能,应由服务器端完成输入验证和输出编码;
5)设置内容安全策略CSP,限制页面中未经授权的脚本执行;
6)对异常输入和XSS测试行为进行日志记录。
3.2 Stored XSS
1)对用户名和留言内容进行输入验证,限制允许输入的字符、长度和格式;
2)留言内容写入数据库前可以进行必要过滤,但不能只依赖SQL转义防止XSS;
3)从数据库读取留言并输出到页面时,必须使用htmlspecialchars()进行HTML实体编码;
4)使用预处理语句操作数据库,避免直接拼接SQL语句;
5)根据业务需求使用安全的HTML过滤库,仅保留允许使用的HTML标签和属性;
6)设置CSP,降低恶意脚本被浏览器执行的风险;
7)对留言内容进行审核,并记录异常提交行为。
3.3 DOM XSS
1)不要将URL参数直接传入document.write()、innerHTML等危险函数;
2)向页面中插入普通文本时,应使用textContent或innerText;
3)对URL中的参数进行白名单验证,只允许程序预设的合法值;
4)需要动态创建页面元素时,应使用createElement()等安全DOM操作方式;
5)避免使用eval()、document.write()等能够执行或解析不可信数据的函数;
6)对必须写入HTML结构的数据进行正确编码或使用可靠的前端过滤库;
7)设置CSP,限制内联脚本和未知来源脚本的执行。
File Inclusion
一 了解File Inclusion
1.什么是File Inclusion
File Inclusion(文件包含)漏洞是指 Web 应用程序根据用户可控的输入动态加载文件,但没有对文件名、路径或协议进行严格限制,导致用户能够让服务器加载原本不应访问的文件。
在 PHP 中,常见的文件包含函数包括:
include()
include_once()
require()
require_once()
这些函数原本用于复用页面组件或加载配置文件。例如:include("header.php");
如果程序把用户输入直接传给 include():
$file = $_GET['page'];
include($file);
用户就可能通过修改 page 参数访问其他文件,从而形成文件包含漏洞。
2.本地文件包含与远程文件包含
File Inclusion漏洞主要分为两种:
1)本地文件包含
程序包含的是服务器本地文件
2)远程文件包含
程序包含的是远程服务器上的文件
注意:在此DVWA靶场本文仅复现本地文件包含漏洞。
3.什么是路径穿越
路径穿越是指通过../等路径符号跳出程序预期目录,访问上级目录中的文件。
在Linux中:../表示当前目录的上一级目录。
例如当前脚本目录为:/var/www/html/DVWA/vulnerabilities/fi/
不断增加../就可以逐级返回到系统根目录,随后访问/etc/passwd等文件。
../../../../../../etc/passwd
注意:实际需要使用的../数量与DVWA的安装路径有关,要结合自身实际情况去判断。
4.PHP 伪协议
PHP 提供了一些特殊的数据流封装器,常见的有:
php://filter
php://input
data://
file://
其中 php://filter 可以在读取文件前对内容进行编码或转换。例如:
php://filter/convert.base64-encode/resource=include.php
当 PHP 源文件被普通 include() 加载时,其中的 PHP 代码通常会被服务器执行,浏览器只能看到执行结果,不能直接看到源代码。使用 php://filter 对内容进行 Base64 编码后,源代码不会直接作为 PHP 执行,可以通过解码查看。
注意:本文只在自己的 DVWA 靶场中读取无敏感信息的教学文件 include.php,不读取真实业务配置或凭据。
5.File Inclusion漏洞的产生原因
1)将 URL 参数直接传入 include()、require() 等函数;
2)没有使用允许文件列表;
3)没有限制用户只能访问指定目录;
4)没有阻止 ../、绝对路径或 PHP 伪协议;
5)服务器文件权限过大,Web 服务进程能够读取不必要的系统文件;
6)启用了不需要的远程文件包含配置;
7)错误信息直接显示了服务器的真实目录结构。
漏洞产生的根本原因是:程序把用户输入当成了服务器文件路径,而没有在用户输入与实际文件之间建立安全、固定的映射关系。
6.File Inclusion 漏洞的危害
1)读取系统文件、日志文件和应用配置;
2)泄露数据库地址、账号、密钥等敏感信息;
3)暴露网站源代码和服务器目录结构;
4)与文件上传、日志写入等漏洞组合后扩大影响;
5)在满足特定环境条件时,可能导致服务器执行非预期代码;
6)为后续权限提升和横向分析提供信息。
二 DVWA靶场File Inclusion实战
1.验证File Inclusion模块的正常功能
点击该模块,观察URL为http://localhost:8080/vulnerabilities/fi/?page=include.php 其中page=include.php表示页面通过page参数指定需要包含的文件。

点击页面中的file1.php,file2.php等链接,观察URL中的page参数以及页面内容。


发现page参数和页面内容发生变化,说明该模块会根据page参数加载不同文件。
2.使用绝对路径复现本地文件包含漏洞
将URL的page参数改为/etc/passwd 观察页面返回结果。

此图说明服务器把用户控制的绝对路径传入了文件包含函数,成功读取了Linux系统文件。
3.使用路径穿越验证本地文件包含
为进一步验证漏洞,可以使用相对路径,将page参数改为:../../../../../../etc/passwd观察返回结果

返回相同结果,可以确认该模块同时存在不受限制的文件包含和路径穿越问题。
4.使用php://filter读取文件源码
将page参数修改为php://filter/convert.base64-encode/resource=include.php

将返回的Base64字符串解码,在终端输入echo 'Base64字符串' | base64 -d

解码成功,返回该文件源码。
三 源码分析与修复方案
1.点击view source查看源码
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
?>
2.源码分析
程序通过:$file = $_GET[ 'page' ];获取 URL 中的 page 参数。
3.修复方案
1)不要把用户输入直接传给 include()、require() 等文件操作函数;
2)使用固定白名单,将用户输入的页面标识映射到服务器预先定义的文件;
3)使用 realpath() 获取规范化路径,并确认目标文件位于允许目录内;
4)关闭不必要的 allow_url_include,避免远程文件包含;
5)限制 Web 服务进程的文件读取权限;
6)生产环境关闭详细 PHP 错误显示,避免泄露真实目录;
7)记录包含异常路径、../、绝对路径和伪协议的请求;
8)将上传目录与可执行、可包含的代码目录隔离。
SQL Injection(Blind)
一 了解SQL Injection(Blind)
1.什么是Blind SQL Injection
Blind SQL Injection(SQL 盲注)是 SQL 注入的一种形式。
普通 SQL 注入可能会直接在页面中显示数据库查询结果、报错信息或联合查询结果;SQL 盲注通常不会直接返回查询到的数据,只会根据 SQL 条件的真假表现出不同的页面内容、HTTP 状态码或响应时间。
因此,盲注的核心思想是:
向数据库提出一个只能回答“是”或“否”的问题
观察页面内容、状态码或响应时间
逐步推断数据库中的信息
例如,程序不会直接告诉测试者当前数据库名是什么,但可以依次判断:
数据库名长度是否为4?
第1个字符是否为d?
第2个字符是否为v?
通过不断提出真假问题,最终仍然能够推断出完整数据。
2.布尔盲注与时间盲注
1)布尔盲注(Boolean-based Blind SQL Injection)
布尔盲注通过构造真假条件,根据响应内容或状态码的差异判断条件是否成立。
例如:
1' AND 1=1# 条件 1=1 为真,页面返回用户存在。
1' AND 1=2# 条件 1=2 为假,页面返回用户不存在。
2)时间盲注(Time-based Blind SQL Injection)
当页面对真假条件没有明显内容差异时,可以让数据库在条件成立时延迟响应。
例如:1' AND IF(1=1,SLEEP(3),0)#如果响应延迟约 3 秒,说明条件成立。
3.普通 SQL 注入与 SQL 盲注的区别
| 对比项目 | 普通 SQL 注入 | SQL 盲注 |
|---|---|---|
| 数据是否直接回显 | 通常可以 | 通常不可以 |
| 常见判断依据 | 查询结果、报错、回显位 | 页面真假差异、HTTP 状态码、响应时间 |
| 常用方法 | UNION SELECT、报错注入 | 布尔条件、时间延迟 |
| 获取数据方式 | 一次返回多条数据 | 逐个条件、逐个字符推断 |
| 测试效率 | 较高 | 较低 |
4.盲注常用函数
1)DATABASE()
返回当前数据库名:
SELECT DATABASE();
2)LENGTH()
返回字符串长度:
LENGTH(DATABASE())
3)SUBSTRING()
截取字符串中的指定字符:SUBSTRING(字符串,起始位置,截取长度)
SUBSTRING(DATABASE(),1,1) 表示从数据库名的第1个字符开始截取长度为1。
4)ASCII()
将字符转换为 ASCII 数值:
ASCII('d')
结果为:
100
5)IF()
根据条件返回不同结果:
IF(条件, 条件为真时执行, 条件为假时执行)
6)SLEEP()
使数据库查询延迟指定秒数:
SLEEP(3) 表示延迟3秒
7)LIMIT
从多条查询结果中选择指定记录:LIMIT 偏移量, 返回条数
LIMIT 0,1 表示跳过0条记录,返回1条记录。(也就是直接返回第一条记录)
5.SQL 盲注漏洞的产生原因
1)程序将用户输入直接拼接到 SQL 语句;
2)没有使用预编译语句或参数化查询;
3)程序虽然不显示数据库数据,但向用户暴露了真假差异;
4)不同查询结果使用不同的响应内容或 HTTP 状态码;
5)数据库支持条件表达式和延迟函数;
6)数据库账号权限过大;
7)缺少对异常请求频率和重复条件测试的监控。
6.SQL 盲注漏洞的危害
1)推断数据库名、表名和字段名;
2)逐字符获取用户数据;
3)泄露认证信息和其他敏感数据;
4)通过大量时间延迟查询消耗数据库连接;
5)为进一步攻击提供数据库结构和业务信息;
6)说明应用程序的数据库查询边界已经被破坏。
即使页面没有直接回显数据库数据,也不代表不存在 SQL 注入风险。
二 DVWA靶场 Blind SQL Injection实战
1.验证模块正常功能
在User ID处输入1,提交,查看返回结果

由上图可知,该用户存在,那么现在对其输入9999这个不存在的用户ID,查看其返回结果

2.判断是否存在布尔盲注
首先输入恒等式:1' AND 1=1# 查看返回结果:用户存在

然后输入恒假式:1' AND 1=2# 查看返回结果:用户不存在

根据两次条件不同以及对应的返回结果不同,可确定此模块存在布尔盲注。
3.判断当前数据库名的长度
使用LENGTH(DATABASE())判断数据库名字的长度。
可输入:1' AND LENGTH(DATABASE())=数字#
经过手动不断尝试,发现当数字为4时,返回结果为用户存在,可知该数据库名长度为4

4.逐字符判断数据库名
已经知道数据库名长度为4,接下来使用ASCII(SUBSTRING(DATABASE(),起始位置,截取长度))判断每个字符。
由于手动输入太慢了,因此我们可以借助工具Burp Suite的Intruder模块进行枚举。测试范围从32到126。
具体操作步骤参考前文Brute Force模块,这里仅给出结果:

由图返回结果的长度或者状态码都可以确认,该数据库名的第一个字符的ASCII码是100,对应为d,以此类推,得出该数据库名为dvwa。
5.判断users表是否存在
使用EXISTS()对information_schema.tables进行条件判断:1' AND EXISTS(SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='users')#

由结果可知,存在users表。
6.逐字符推断用户名称
以user_id=1对应的用户名为例,先判断用户名长度:1' AND LENGTH((SELECT user FROM users WHERE user_id=1 LIMIT 0,1))=数字# 手动输入测得长度为5

开始逐字符测试:1' AND ASCII(SUBSTRING((SELECT user FROM users WHERE user_id=1 LIMIT 0,1),起始位置,1))=数字# 此处依旧使用Burp Suite的Intruder模块,测出该用户名为admin。
7.使用时间盲注进行验证
输入1' AND IF(1=1,SLEEP(3),0)# 发现页面响应时间增加大约3秒,说明条件成立,sleep函数被执行,而输入1' AND IF(1=2,SLEEP(3),0)# 发现页面立即响应,条件不成立。两次执行结果说明确实存在时间盲注漏洞。
此处可以对数据库名长度进行验证,输入:1' AND IF(LENGTH(DATABASE())=4,SLEEP(3),0)# 页面响应时间增加约3秒,条件成立,说明数据库名长度确实是4。还可以通过这个去验证数据库名以及用户名等等,此处不做演示。(当然,返回结果都是用户不存在,因为条件无论成不成立,最后返回结果都是0,(sleep函数执行完之后返回结果是0))
三 源码分析与修复方案
1.点击view source查看源码
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Get input
$id = $_GET[ 'id' ];
// Check database
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // Removed 'or die' to suppress mysql errors
// Get results
$num = @mysqli_num_rows( $result ); // The '@' character suppresses errors
if( $num > 0 ) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
}
else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
2.源码分析
首先,程序判断 GET 请求中是否存在 Submit:
if( isset( $_GET[ 'Submit' ] ) )
随后获取用户输入:
$id = $_GET[ 'id' ];
程序将 $id 直接拼接到 SQL 查询中:
$getid = "SELECT first_name, last_name
FROM users
WHERE user_id = '$id';";
当用户输入:
1
实际查询相当于:
SELECT first_name, last_name
FROM users
WHERE user_id = '1';
当用户输入:
1' AND 1=1#
实际查询相当于:
SELECT first_name, last_name
FROM users
WHERE user_id = '1' AND 1=1;
# 将原查询后面的单引号和分号注释掉。由于 1=1 为真,数据库能够返回记录。
程序随后通过:
$num = @mysqli_num_rows($result);
获取查询结果的记录数,随后通过`if($num > 0)判断是否存在匹配记录。
如果存在记录,页面输出:
User ID exists in the database.
如果不存在记录,程序返回 HTTP 404,并输出:
User ID is MISSING from the database.
虽然页面没有显示 first_name 和 last_name 的具体内容,但真假条件产生了可以稳定观察的响应差异。测试者可以利用这个差异不断询问数据库,从而逐步推断信息。
漏洞产生的根本原因仍然是:
用户输入被直接拼接进 SQL 语句
隐藏查询结果只能降低直接回显程度,不能修复 SQL 注入。
3.修复方案
1)使用参数化查询或预编译语句,禁止直接拼接用户输入;
2)如果用户 ID 只能为数字,使用严格整数验证;
3)对数据库账号实施最小权限原则;
4)不要把数据库错误信息直接返回给用户;
5)对重复真假条件、异常请求频率和大量延迟查询进行日志记录和告警;
6)设置合理的数据库查询超时和连接资源限制;
7)返回统一的业务错误页面,但不能把“统一响应”当成 SQL 注入修复;
8)在修复后使用真假条件重新测试,确认输入只能作为普通参数处理。
Weak Session IDs
一 了解Weak Session IDs
1.什么是Session
Session 是服务器为某个客户端保存的一组会话状态数据,以及围绕这些数据建立的管理机制.
HTTP 协议本身是无状态的。服务器收到一次 HTTP 请求后,默认不会自动知道该请求和之前的请求是否来自同一个用户。为了在多个 HTTP 请求之间保存用户状态,Web 应用通常会使用 Session。Session 一般保存在服务器端,可以存储用户 ID、登录状态、权限和操作状态等信息。
2.什么是Cookie
Cookie 是服务器通过 HTTP 响应发送给浏览器的一小段数据。浏览器保存 Cookie 后,会在后续符合条件的请求中自动携带它。
Cookie 可以用于保存偏好设置、登录标识、会话标识等信息。
注意:Cookie 保存在客户端,用户可以查看和修改,因此服务器不能直接信任 Cookie 中的内容。
3.什么是Session ID
Session ID,也称为会话标识或会话令牌,是服务器为一次会话分配的唯一标识。
Session ID 本身通常不应该直接包含用户名、密码、权限等敏感信息,它主要用于关联服务器端保存的会话数据。
典型处理流程如下:
用户登录
↓
服务器创建Session
↓
服务器生成Session ID
↓
通过Cookie发送给浏览器
↓
浏览器在后续请求中携带Session ID
↓
服务器根据Session ID找到对应的Session数据
4.Session、Cookie与Session ID之间的关系
| 对象 | 主要保存位置 | 主要作用 |
|---|---|---|
| Session | 服务器端 | 保存用户登录状态、权限和业务数据 |
| Session ID | 通常由浏览器和服务器共同传递 | 标识某一次服务器端 Session |
| Cookie | 浏览器端 | 保存并在请求中携带 Session ID 等数据 |
5.什么是Weak Session IDs
Weak Session IDs,即弱会话标识,是指Web应用生成的Session ID或类似会话令牌缺少足够的随机性、唯一性或不可预测性。攻击者只要观察到其中一个值,就可以推测前后可能出现的值。
其他常见弱会话标识还包括:时间戳、简单MD5值,可预测随机数等。
6.弱会话标识的常见表现
1)Session ID 按照固定数字递增;
2)不同用户获得相同的 Session ID;
3)重新登录后仍然使用原来的 Session ID;
4)Session ID 中直接包含用户名、用户 ID 或时间;
5)Session ID 的一部分固定,只有少量字符发生变化;
6)使用普通伪随机函数而不是密码学安全随机数;
7)Session ID 长期有效,退出登录后仍然可以继续使用;
8)登录前后、权限变化前后不更新 Session ID。
7.弱会话标识漏洞的产生原因
1)使用计数器生成 Session ID;
2)使用时间戳作为 Session ID;
3)使用用户名、用户 ID 等可推测内容;
4)使用随机性不足的随机函数;
5)没有使用框架自带的安全会话管理机制;
6)用户登录或权限变化后没有重新生成 Session ID;
7)会话没有设置合理的过期时间;
8)用户退出登录后,服务器端会话没有真正失效;
9)Cookie 缺少 Secure、HttpOnly 或合理的 SameSite 属性;
10)缺少对会话枚举、重复使用和异常访问的监控。
8.弱会话标识漏洞的危害
1)攻击者预测其他用户的 Session ID;
2)攻击者枚举有效会话;
3)未经授权访问其他用户账户;
4)冒充普通用户或管理员执行操作;
5)读取或修改用户敏感信息;
6)配合 XSS、会话固定等漏洞扩大影响;
7)绕过正常登录流程;
8)造成会话劫持和权限失控。
二 DVWA靶场 Weak Session IDs实战
1.验证Weak Session IDs模块的正常功能
进入后可看到生成会话标识的Generate按钮,开启burp suite Proxy模块,可看到生成的Cookie

连续生成并观察


可发现PHPSESSID不变,dvwaSession每次+1。(PHPSESSID 是 PHP 自身的真实 Session 标识,用于维持用户登录状态;dvwaSession 是 DVWA 为了演示“弱会话标识”漏洞而额外生成的测试 Cookie。)
得出结论:生成的 dvwaSession 不具备随机性,每次请求都在原有值的基础上固定增加 1。
2.查看请求与响应的不同
点击HTTP history,随便点击一个,发现请求与响应的Cookie不同

请求中的Cookie表示浏览器发送的原有值,而响应中的Cookie表示服务器要求浏览器保存的新值。
3.使用Burp Suite Repeater重复验证
在history中右键将该请求发送到Repeater,然后重复发送,可发现Set-Cookie逐渐+1
4.验证服务器不使用传入的dvwaSession计算新值
在Repeater请求中,将当前值修改为999,保留PHPSESSID不变,然后发送请求。
发现新Set-Cookie在原本值+1,而并非变为1000.

三 源码分析与修复方案
1.点击view source查看源码
<?php
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
if (!isset ($_SESSION['last_session_id'])) {
$_SESSION['last_session_id'] = 0;
}
$_SESSION['last_session_id']++;
$cookie_value = $_SESSION['last_session_id'];
setcookie("dvwaSession", $cookie_value);
}
?>
2.源码分析
程序首先定义一个空字符串:$html = "";在当前展示的代码中,$html 没有参与后续会话标识的生成,不是漏洞产生的关键位置。
随后判断当前请求是否为 POST:if ($_SERVER['REQUEST_METHOD'] == "POST")
$_SERVER['REQUEST_METHOD'] 用来获取 HTTP 请求方法。
只有点击 Generate 并提交 POST 请求后,程序才会进入生成逻辑。普通 GET 请求不会执行下面的代码。
程序接着判断服务器端 Session 中是否已经存在:$_SESSION['last_session_id']
对应代码为:
if (!isset($_SESSION['last_session_id'])) {
$_SESSION['last_session_id'] = 0;
}
isset() 用于判断变量是否已经设置。
如果是当前 PHP 会话第一次生成 dvwaSession,那么 last_session_id 不存在,程序会把它设置为:0
随后执行:$_SESSION['last_session_id']++;将原有数值增加 1。
程序随后把计数结果保存到 $cookie_value:$cookie_value = $_SESSION['last_session_id'];
最后调用:setcookie("dvwaSession", $cookie_value);
向浏览器设置名为 dvwaSession 的 Cookie。
对应 HTTP 响应类似:Set-Cookie: dvwaSession=5
整个数据流为:
POST请求
↓
检查last_session_id是否存在
↓
不存在则初始化为0
↓
固定增加1
↓
赋值给cookie_value
↓
通过Set-Cookie返回dvwaSession
3.修复方案
1)使用密码学安全随机数生成 Session ID,避免使用递增数字、时间戳、用户信息等可预测内容作为会话标识;
2)使用 Web 框架或编程语言自带的安全 Session 管理机制,不自行实现不安全的 Session ID 生成逻辑;
3)保证 Session ID 具有足够长度和随机性,增加攻击者枚举和预测的难度;
4)用户登录成功后重新生成 Session ID,避免攻击者利用旧 Session ID 进行会话固定攻击;
5)用户权限发生变化(例如普通用户提升为管理员)时重新生成 Session ID,防止权限切换过程中会话被利用;
6)设置合理的 Session 过期时间,避免长期有效的会话标识被攻击者重复使用;
7)用户退出登录后,应及时销毁服务器端对应 Session 数据,使原 Session ID 失效;
8)Cookie 中的 Session ID 应设置 HttpOnly 属性,降低 XSS 窃取 Session 的风险;
9)Cookie 中的 Session ID 应设置 Secure 属性,保证其只通过 HTTPS 传输,防止中间人窃取;
10)合理设置 SameSite 属性,限制跨站请求自动携带 Session Cookie,降低 CSRF 攻击风险;
11)对异常 Session 使用行为进行监控,例如大量枚举 Session ID、同一 Session 异常来源访问等;
12)避免将敏感信息直接存储在 Session ID 中,Session ID 仅用于关联服务器端 Session 数据。

浙公网安备 33010602011771号