皮卡丘靶场解题记录-文件下载
不安全的文件下载
漏洞原理总结:
就像图书馆的”代取书服务”。
图书馆有一个服务:你报书名,管理员去书库帮你取,然后复印一份给你。
正常情况,你报的是一本合法书的名字( kb.png )。管理员去 download/ 书架上取,复印,给你。
漏洞情况,你报了 ../../../etc/passwd 。管理员拿着这张纸条,走到 download/ → ../ (退出 download)→ ../ (退出 Unsafedownload)→ ../ (退出 vul)→ 一直走到根目录 → 进入了 /etc/ 书库 → 取了一本叫做 passwd 的机密档案,复印了一份, 亲手交给了你。
我的测试过程:
见后文,此处略
get型的url
1
http://101.43.30.86/pikachu/vul/unsafedownload/execdownload.php?filename=../../../etc/passwd
核心漏洞代码分析:

没有对filename做过滤处理。
用户的 filename 参数直接拼进文件路径,没有做任何过滤。 后续代码(第 15-38 行)就是把 $file_path 指向的文件读出来,推给浏览器下载。
正常情况: filename=kb.png → 路径为 download/kb.png → 下载科比的头像 ✅
攻击情况: filename=../../../etc/passwd → 路径为 download/../../../etc/passwd → 实际指向 /etc/passwd → 下载系统用户文件 💀
==拓展1:php目录解析顺序==
因为这里download/的前缀直接被目录穿越抵消了嘛,然后我就在想前面一道题,文件包含那里,代码有一个include/的前缀,后面接包含的文件的路径,当时我就在想这个include/的前缀能不能用目录穿越绕过然后后面接php://filter的伪协议。当时没深究,现在遇到了,类似的,就去查了一下。
尝试了这个路径
1
http://101.43.30.86/pikachu/vul/fileinclude/fi_local.php?filename=../php://filter/read=convert.base64-encode/resource=../../inc/config.inc.php&submit=submit
发现不行,但理论上来说又好像是可行的,然后我就让服务器的main agent给我查了一下什么情况,看是不是环境差异的问题。【hhh,为了偷懒和直观读代码用的trae,直接在github上拉的靶场源码,所以可能和服务器上实际的版本有区别】
【然后遇到一个很经典的问题,两边agent的回答在左右互博,很神奇的是,这两个的api我都接的是deepseek,v4pro和v4flash的一战hhh】
然后我想起来之前听大佬说过,遇到语法的问题,不要一直依靠ai,去官方手册找答案
排查了一番,发现可能是这个问题:
当时那个,文件包含的源代码长这样,前缀有个include/
和我传入的这个../php://filter路径拼接之后,变成了include/../php://filter
理论上来说化简之后就是php://filter这个伪协议了对吧,但是这里有一个问题
php的解析机制,是先判断协议格式,再化简路径的
1
2
3Step1:拿到完整传入字符串 → 检查【原始字符串开头】是否匹配 scheme:// 格式 → 选定 wrapper
Step2:如果判定是 file://(本地文件),才进入路径归一化、解析../、.、目录化简
❗ 重点:../路径化简,只属于 file:// 这个本地文件 wrapper 内部的逻辑;它不会回头重新检测协议头!详细一点说是这样的:
1
2
3
4
5
6
7Step1:拿到完整传入字符串 → php_stream_locate_url_wrapper() 只看【原始字符串开头】(main/streams/streams.c:2025-2031):
从第 0 个字符起扫描 scheme 候选(字母/数字/+/-/.),遇到 : 且后面是 //(或精确等于 data:)才算协议;
命中 → 按协议查 wrapper 注册表,先原样查、查不到转小写再查(streams.c:2034-2052);
没命中任何协议、或协议就是 file → 回退 plain files wrapper(streams.c:2055)。
Step2:只有最终落到 plain files wrapper(注意:不只 file://,还包括一切无协议路径:相对路径、./x、绝对路径、Windows 盘符 C:\x)时,才进入路径归一化:
expand_filepath → tsrm_realpath,折叠 ../、.、拼接 cwd、解析符号链接(plain_wrapper.c:1168 / fopen_wrappers.c:787 / zend_virtual_cwd.c:1717)。
❗ 重点:../ 路径化简是 plain files wrapper 选定之后、在文件系统层(tsrm_realpath)内部发生的,它按"普通目录名"逐段处理路径组件;它不会回头重新做协议识别。所以 include/../php://filter/... 里的 php:// 永远只是字符串中间的一段普通文件名,永远不会被当作 wrapper。然后为了进一步验证,我让codex帮我把 PHP 8.5.9(当前最新稳定版)的源码拉下来对照着验证了,里面是这样的流程
1
2
3
4
5
6
7
8flowchart TD
A["include 'include/../php://filter/...'<br/>(字符串以 include/ 开头)"] --> B["php_stream_locate_url_wrapper<br/>main/streams/streams.c:2025-2031"]
B --> C["scheme 扫描:从头数 include = 7 个字符<br/>下一字符是 '/' 不是 ':'"]
C --> D["protocol = NULL<br/>php:// 在字符串中间,永远认不出来"]
D --> E["回退 plain files wrapper<br/>streams.c:2055"]
E --> F["路径归一化 tsrm_realpath<br/>Zend/zend_virtual_cwd.c:1717"]
F --> G["组件 php: 不是真实目录<br/>CWD_REALPATH 下直接返回失败<br/>zend_virtual_cwd.c:606-611"]
G --> H["include 报错 failed to open stream<br/>No such file or directory"]==拓展2:post传输版本的漏洞利用==
同样是之前我思考过的一个问题,这里filename是get请求,改post之后,漏洞仍然是存在的,不因数据传输方式而改变,只是利用手法稍稍变化而已。
改post请求之后如何找到下载入口?
F12 Network 面板直接看。
查看方法如图
bp里面能改文件名也看得见。然后目录穿越,可以多叠加几层,层数少了有时候看不见
还有一个总结,==所有 Web 漏洞的本质都一样:找到输入 → 改输入 → 看输出。F12 Network 找入口,Burp Repeater 测试,GET 和 POST 只是输入通道不同,不影响漏洞存在与否。==
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18打开页面 → F12 Network → 正常操作一次 → 看请求
│
┌──────────────┴──────────────┐
▼ ▼
GET 请求 POST 请求
│ │
直接在地址栏 Burp Repeater 改包
改参数测试 测试
│ │
└──────────────┬──────────────┘
▼
看 Response
│
┌───────┴───────┐
▼ ▼
有异常内容 报错 / 没反应
│ │
✅ 漏洞存在 换 Payload实战延伸(价值所在):
【实战价值与门槛】
1 | # ===== Payload 1:下载系统用户文件 ===== |
【实战技巧】
实战中怎么精准找到下载入口:
1 | ## 方法一:看 F12 网络请求(最准) |
实战流程:F12 → Network → 点下载链接 → 看请求URL → Burp Repeater → 改 filename → 测试
永远不要靠猜。