1. 漏洞概述
文件下载功能在很多 Web 应用中都很常见(如附件下载、图片下载)。如果不安全的文件下载(Unsafe File Download)漏洞存在,攻击者可以通过构造特殊的文件路径(通常利用 ../),跳出 Web 服务器指定的下载目录,去下载服务器系统中的敏感文件(如 /etc/passwd、配置文件、源代码等)。
2. 闯关实操
第一步:分析请求
进入靶场页面,发现是 NBA 球星头像下载页面。点击图片链接,观察 URL 结构:http://localhost:8899/vul/unsafedownload/execdownload.php?filename=ns.png
可以看到参数 filename 直接指定了要下载的文件名。
第二步:漏洞验证
尝试修改 filename 参数,测试是否存在目录遍历漏洞。
Payload:../../../../etc/passwd (Linux 环境通用测试)
或者../../../../windows/win.ini (Windows 环境通用测试)
结果: 成功下载了 /etc/passwd 文件,证明存在任意文件下载漏洞。

第三步:获取特定文件(上帝视角验证)
已知容器内存在测试文件 test/phpinfo.txt。
Payload:../../../test/phpinfo.txt
结果: 浏览器成功弹出下载框或直接展示文件内容,利用成功。

3. 源码分析
本次关卡的核心漏洞代码如下:
$file_path="download/{$_GET['filename']}"; // 漏洞点
// ... 省略部分代码...
// 判断文件是否存在
if(!file_exists($file_path)){skip("你要下载的文件不存在,请重新下载", 'unsafe_down.php');
return ;
}
// ... 省略 Header 设置...
// 读取并输出文件
while(!feof($fp) && $file_count<$file_size){$file_con=fread($fp,$buffer);
$file_count+=$buffer;
echo $file_con;
}
fclose($fp);
分析:
- 直接拼接:
$file_path="download/{$_GET['filename']}";这行代码直接将用户输入的filename拼接到download/目录下。 - 缺乏过滤: 后端虽然使用了
file_exists检查文件是否存在,但 并没有检查该文件是否在允许的目录内,也没有过滤../等敏感字符。 - 逻辑缺陷: 只要用户通过
../抵消掉前面的download/目录,就可以访问文件系统中的任意位置(只要权限允许)。
4. 深度思考:实战中的利用逻辑
只要能跳出开发者预设的目录(这里是 download/)并读取到本不应公开的文件(如 /etc/passwd 或其他路径的文件),即视为漏洞利用成功。
在实战中,盲目猜测文件名效率很低,通常结合以下思路:
- 扫描网站结构(Dirsearch/Gobuster):
你需要知道网站有哪些文件才能去下载它们。例如,如果你扫描出了config.php、web.config或者备份文件index.php.bak,你就可以利用下载漏洞把这些源码下载下来进行白盒审计,寻找数据库密码或新的漏洞。 - 猜测默认配置文件: 针对特定的 CMS 或中间件,尝试下载默认路径的配置文件。
../../wp-config.php(WordPress)../../WEB-INF/web.xml(Java Web)C:/Windows/System32/drivers/etc/hosts(Windows)/root/.bash_history(Linux 操作历史)
- 读取源码:
利用下载漏洞读取当前处理脚本本身的源码(如execdownload.php),查看它是如何写的,或者包含(include)了哪些其他核心文件。
总结: 任意文件下载漏洞通常是进一步攻击的 跳板。通过它获取源码和配置信息,从而拿到数据库权限或配合文件上传 / 包含漏洞 GetShell。
5. 防御修复
- 过滤特殊字符: 严格过滤
.、/、\等字符,禁止用户输入目录跳转符号。 - 白名单机制: 如果下载的文件数量有限,使用 ID 映射文件名(如数据库中
id=1对应ns.png),用户请求只传 ID。 - 限定目录: 在 PHP 中使用
realpath()函数解析绝对路径,判断解析后的路径是否以预定义的download/目录开头。
// 修复示例
$base_dir = '/var/www/html/vul/unsafedownload/download/';
$filename = $_GET['filename'];
$real_path = realpath($base_dir . $filename);
// 检查 realpath 是否存在,且是否以 base_dir 开头
if ($real_path && strpos($real_path, $base_dir) === 0 && file_exists($real_path)) {// 安全,执行下载} else {
// 非法请求
die("Access Denied");
}