文件上传学习总结

前言

本文是作者由CSDN博客迁移而来,原文地址:https://blog.csdn.net/2302_81178149/article/details/145433812

我们下面进行下一个漏洞——文件上传的学习。文件上传是常见漏洞之一,是Web安全必学漏洞。

为探讨清楚文件上传漏洞的诸多细节,我们特选经典的upload-labs进行从入门到进阶的专项训练。本文是经由upload-labs训练总结而来,所以配合作者的upload-labs通关全详解食用更佳(怎么可以光学习不训练呢)

 upload-labs通关:upload-labs通关全详解-CSDN博客

前置知识

这里大家最好还要有一些php基础,能大概读懂代码就可以;至于html之类的,大家经过之前的学习已经满足要求了。

  • PHP 文件的默认扩展名是 “.php”
  • PHP 文件中可以包含 html、css、JavaScript 代码
序号 组成 描述
1 <?php ... ?> PHP 标记
2 PHP代码 函数、数组、流程控制、类、方法…
3 ;() 语句结束符
4 空白符 合理使用空白符可增强代码可读性
5 注释 // 单行注释/* 多行注释 */

文件上传基础

定义

文件上传是web系统中常见的一种功能,通过文件上传能实现上传图片、视频,以及其他类型的文件。

文件上传漏洞是指用户利用文件上传功能上传了一个可以执行的脚本文件,并且可以通过该文件获得服务器的权限的一种漏洞。

说白了,文件上传漏洞就是用户通过上传恶意文件去入侵服务器的一种漏洞。这个恶意文件被称为webshell,也可以称为一种网页后门。

关于webshell

  • 拥有较完整功能的webshell,我们一般称为大马。
  • 功能简易的webshell称为小马。
  • 除此之外还存在一句话木马、菜刀马、脱库马等等的名词,是对于webShell功能或者特性的简称。

一句话木马

在upload-labs中我们主要用了一句话木马,故这里对一句话木马进行简单解析。

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

<?php ……?>是PHP标记,是PHP标记,代表着这是一句PHP代码

eval()在PHP手册里的解释:

$_POST[]其实是一种全局变量

变量 描述
$_GET 收集来自 method="get" 的表单中的值
$_POST 收集来自 method="post" 的表单中的值
$_REQUEST 包含 $_POST$_GET$_COOKIE
$GLOBALS 全部变量的全局组合数组
$_COOKIE 常用于识别用户
$_SESSION 存储关于用户会话(session)的信息
$_FILES 用来获取通过 POST 方法上传文件的相关信息
$_SERVER 服务器和执行环境信息
$_ENV 环境变量

这样我们就可以综合起来看这一句话木马的含义,其实就是一句PHP代码,收集所有用POST方法传递的key的值(这个‘key’是我们任意起的名字),并将其执行

比如我们用POST方法传递一个key=phpinfo();

此时一句话木马就是<?php @eval(phopinfo(););?>

那么就会执行phpinfo()函数

然后我们还可以去进一步利用,比如通过system()函数,key=system(ipconfig);去查一下ip等等

不过最便捷的方法还是直接连接中国蚁剑,蚁剑必须要用$_POST的密码,这也是为什么我们优先用这个一句话木马。具体的连接方式请见upload-labs通关。

我们在upload-labs中还用到的

1
2
<?php @eval($_GET['key']);?>
<?php $_GET['function']($_GET['key']);?>

也都是同理。

文件上传详解

文件上传漏洞前提

  1. 文件可上传
  2. 上传路径可知
  3. 上传文件可以被解释执行或被包含

存在的风险点

所有存在文件上传的地方都可能存在相关的漏洞,都可以进行测试,比如说上传头像、上传身份认证等

文件上传测试思路及方法

对于上传点测试思路及其方法如下图,

思路:

方法:

文件上传总结

这里我们根据上面内容进行总结。

js检查

在做文件上传题目时,我们应该直接上传不同的文件,去检测是否有过滤,但是同时也不能忘记查看一下前端代码,看看是否是前端过滤

如果是前端过滤,那么我们应该:

方法一:利用浏览器的机制可以禁用js

方法二:删除浏览器事件

这属于前端验证,下面我们讲后端验证

检查后缀

黑名单

有可能是后缀黑名单,过滤了一部分上传的文件。

1、这里我们考虑上传特殊可解析后缀

常用过滤后缀:php、php2、php3、php4、php5、php6、php7、pht、phtm、phtml

下面我们详细讲解一下。

PHP是一种广泛使用的开放源代码的服务器端脚本语言,主要用于Web开发。默认情况下,PHP文件具有.php扩展名,但是服务器可以被配置为接受其他文件扩展名作为PHP文件来处理。以下是一些可能被服务器配置为识别并解析为PHP脚本的文件扩展名:

  • .php - 这是最常见的PHP文件扩展名。

  • .php3 - 早期的PHP版本使用的扩展名。

  • .php4 - 用于PHP 4.x系列。

  • .php5 - 用于PHP 5.x系列。

  • .php7 - 用于PHP 7.x系列。

  • .php8 - 用于PHP 8.x系列。

  • .phtml - 这是PHP混合HTML文件使用的另一个旧扩展名。

  • .phps - 用来显示源代码的高亮显示而不是执行它。

  • .html or .htm - 有时服务器被配置为执行嵌入在HTML文件中的PHP代码。

  • .inc - 有时用于包含文件,但这不是一个安全做法,因为它可能会暴露敏感代码。

但是注意利用特殊可解析后缀是有条件的:

  • 服务器需要通过修改配置文件来支持上述其他扩展名。

对于Apache服务器,这通常在.htaccess文件中或者直接在主配置文件httpd.conf中,使用AddTypeAddHandler指令来完成。对于Nginx服务器,可以在nginx.conf中通过设置location块并使用fastcgi_pass指令来处理。

1
AddType application/x-httpd-php .php .php3 .php4 .php5 .php6 .php7 .php8 .phtml
  1. 还有一些其他的从后缀下手的方法

    • 大小写绕过

    有时候没有对文件名进行强制小写转换,我们可以通过修改文件名为大写来绕过限制的

    • 空格绕过

    因为Windows特性,在文件资源管理器中对后缀名添加空格的操作是不允许的(会自动去除文件末尾的点和空格的),所以这里我们需要使用Burpsuite抓包修改文件名。

    • 点绕过

    windows环境时会自动去除文件末尾的点和空格的,所以还是bp抓包

    • ::$DATA绕过

    在windows环境下,不光会自动去除文件末尾的点和空格,同时(::$DATA)这个字符串,windows也会认为是非法字符,默认去除掉

    • 双后缀名绕过

    有时候将危险后缀都替换为空,但又不是递归替换,所以我们可以双写绕过

  2. .htaccess文件解析漏洞

像实际中我们并不能修改服务器配置文件,而开发人员又没有设置特殊可解析后缀。这时候我们往往需要利用.htaccess文件解析漏洞。

常见配法有以下几种:

AddHandler php5-script .jpg
AddType application/x-httpd-php .jpg
Sethandler application/x-httpd-php

Sethandler 将该目录及子目录的所有文件均映射为php文件类型。

Addhandler 使用 php5-script 处理器来解析所匹配到的文件。

AddType 将特定扩展名文件映射为php文件类型。

简单来说就是,可以将我们所的文件都解析成php或者是特定的文件解析为php。

那么我们创建一个.htaccess文件

写上内容:

1
Sethandler application/x-httpd-php

这是将本目录及所有子目录的所有文件都解析为php文件。

然后我们就可以上传允许上传的内容,然后解析为php文件

这里我们强调使用.htaccess的条件:

  • .htaccess针对Apache网络服务器
  1. .user.ini文件解析漏洞

这时候我们先需要了解下面几个配置文件的区别

  • php.ini

php.ini是php默认的配置文件,其中包括了很多php的配置,这些配置中,又分为几种:PHP_INI_SYSTEM、PHP_INI_PERDIR、PHP_INI_ALL、PHP_INI_USER。
PHP_INI_USER的配置项,可以在ini_set()函数中设置、注册表中设置,.user.ini中设置。

  • .user.ini

.user.ini文件
.user.ini实际上就是一个可以由用户“自定义”的php.ini,我们能够自定义的设置是模式为“PHP_INI_PERDIR 、 PHP_INI_USER”的设置。
它比.htaccess(分布式配置文件)用的更广,不管是nginx/apache/IIS,只要是以fastcgi(进程管理器)运行的php都可以用这个方法。
Php配置项中有两个比较有意思的项
auto_prepend_file指定一个文件,自动包含在要执行的文件前,类似于在文件前调用了require()函数。
auto_append_file类似,只是在文件后面包含。

  • .htaccess

.htaccess叫分布式配置文件,它提供了针对目录改变配置的方法——在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。并且子目录中的指令会覆盖更高级目录或者主服务器配置文件中的指令。一般来说,如果你的虚拟主机使用的是Unix或Linux系统,或者任何版本的Apache网络服务器,从理论上讲都是支持.htaccess的。
目录规则:一般我们将.htaccess文件放置在网站的根目录,控制所在目录及所有子目录,而如果放置在子目录中,会受上级目录中.htaccess文件影响,是不起任何作用的。
.htaccess可以实现:文件夹密码保护、用户自动重定向、自定义错误页面、改变你的文件扩展名、封禁特定IP地址的用户、只允许特定IP地址的用户、禁止目录列表,以及使用其他文件作为index文件等一些功能。

所以.htaccess相对而言是比较针对Apache网络服务器,而.user.ini文件不管是nginx/apache/IIS,只要是以fastcgi(进程管理器)运行的php都可以用这个方法

user.ini :自 PHP 5.3.0 起,PHP 支持基于每个目录的 .htaccess 风格的 INI 文件。此类文件仅被CGI/FastCGI SAPI 处理。此功能使得 PECL 的 htscanner 扩展作废。如果使用 Apache,则用.htaccess文件有同样效果。

除了主php.ini 之外,PHP 还会在每个目录下扫描 INI 文件,从被执行的 PHP 文件所在目录开始一直上升到 web根目录($_SERVER[‘DOCUMENT_ROOT’] 所指定的)。如果被执行的 PHP文件在 web 根目录之外,则只扫描该目录。

在.user.ini 风格的 INI 文件中只有具有 PHP_INI_PERDIR 和 PHP_INI_USER 模式的 INI设置可被识别。

两个新的 INI 指令,user_ini.filename 和 user_ini.cache_ttl 控制着用户 INI 文件的使用。

user_ini.filename 设定了 PHP 会在每个目录下搜寻的文件名;如果设定为空字符串则 PHP 不会搜寻。默认值是 .user.ini。

user_ini.cache_ttl 控制着重新读取用户 INI 文件的间隔时间。默认是 300 秒(5 分钟)。

我们可以用.user.ini去包含我们的代码,然后执行,.user.ini相当于一个用户自定义的php.ini。

制作一个.user.ini文件

1
auto_prepend_file=1.jpg

“auto_prepend_file指定一个文件,自动包含在要执行的文件前,类似于在文件前调用了require()函数”

所以.user.ini文件里的意思是:所有的php文件都自动包含我们刚刚上传的1.jpg文件,会将其执行。

这里我们强调使用.user.ini的条件:

  • 以fastcgi(进程管理器)运行的php

  • 上传路径下必须有PHP文件,用来包含我们的一句话木马

白名单

后端有可能是MIME检查,那我们就选择可以上传的MIME进行绕过

类型 描述 典型示例
text 表明文件是普通文本,理论上是人类可读 text/plain, text/html, text/css, text/javascript
image 表明是某种图像。不包括视频,但是动态图(比如动态 gif)也使用 image 类型 image/gif, image/png, image/jpeg, image/bmp, image/webp, image/x-icon, image/vnd.microsoft.icon
audio 表明是某种音频文件 audio/midi, audio/mpeg, audio/webm, audio/ogg, audio/wav
video 表明是某种视频文件 video/webm, video/ogg
application 表明是某种二进制数据 application/octet-stream, application/pkcs12, application/vnd.mspowerpoint, application/xhtml+xml, application/xml, application/pdf

•text/html:HTML格式
•text/plain:纯文本格式
•text/xml:XML格式
•text/css:CSS格式
•text/javascript:JS格式
•image/gif:GIF图片格式
•image/jpeg:JPG图片格式
•image/png:PNG图片格式
•image/svg+xml:SVG矢量图格式
•video/mpeg:MPEG动画格式
•application/xhtml+xml:XHTML格式
•application/xml:XML数据格式
•application/json:JSON数据格式
•application/atom+xml:Atom+XML聚合格式
•application/pdf:PDF文档格式
•application/msword:Word文档格式
•application/octet-stream:二进制数据流(如常见的文件下载)
•application/x-www-form-urlencoded:form表单被编码成key/value格式发送到服务器(表单默认提交数据的格式)
•multipart/form-data:POST 提交时伴随文件上传的表单

但是当使用MIME也无法绕过时,我们就要考虑别的方法。

比如说00截断。

【00截断原理】

谈到00截断我们都会想到,有什么0x00截断、%00截断,也有人对两个东西分析一大堆,那么它俩有什么区别呢,什么场合适用哪一个呢?

这就要从00截断的原理说起:

其实截断的原理也很简单,无论0x00还是%00,最终被解析后都是一个东西:chr(0)。

char(0)是什么东西?

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

那么chr(0)就很好理解了,对照ASCII码表可以知道,ASCII码为0-127的数字,每个数字对应一个字符,而0对应的就是NUT字符(NULL),也就是空字符

而截断的关键就是这个空字符,当一个字符串中存在空字符的时候,在被解析的时候会导致空字符后面的字符被丢弃。

这种情况常出现在ASP程序中,PHP 版本<5.3.4时也会有这个情况,JSP中也会出现。

那么就可以知道00截断的原理了,在后缀中插入一个空字符(不是空格),会导致之后的部分被丢弃,而导致绕过的发生。如:在文件1.php.jpg中插入空字符变成:1.php.0x00.jpg中,解析后就会只剩下1.php

至于是利用0x00还是%00,这就要看传递文件名时的方式

用GET传就用%00,用POST传就用0x00

而空字符0x00怎么插入的呢?

通常我们会用Burp抓包后,在文件名插入一个空格,然后再HEX中找到空格对应的16进制编码“20”,把它改成00(即16进制ASCII码00,对应十进制的0),就可以插入空字符了。

PS:这里的空格纯粹只是一个标记符号,便于我们找到位置,其实这里是什么字符都无所谓,只不过空格比较有特异性,方便在HEX中查找位置

检查内容

有时候我们上传文件时,会检查文件中是否含有<?,

这时候,我们就可以用如下木马:

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

当然检查内容最常见的还是图片马,我们下面主要从图片马的角度讲解.

注意图片马有利用条件:

  • 有文件包含漏洞或者是可以上传.user.ini文件,才可以利用图片马
  1. 文件头检查

PEG/IFIF(常见的照片格式):头两个字节为·0 xFF·0xD8。

PNG(无损压缩格式):头两个字节为·0x89·0x50。

GIF(支持动画的图像格式):头两个字节为·0x47·0x49。

BMP(Windows位图格式):头两个字节为·0x42·0x4D。

在同时有图片和一句话木马的文件夹下打开cmd,然后使用以下命令。

1
copy 图片名 /b + 一句话木马的文件名 /a webshell.jpg

意思是将一句话木马文件中的代码追加到图片中并重新生成一个叫webshell.php的代码。

  1. getimagesize()

getimagesize())会对目标的十六进制的前几个字符串进行读取。getimagesize() 函数将测定任何 GIF,JPG,PNG,SWF,SWC,PSD,TIFF,BMP,IFF,JP2,JPX,JB2,JPC,XBM 或 WBMP 图像文件的大小并返回图像的尺寸以及文件类型及图片高度与宽度。

  1. exif_imagetype()

exif_imagetype()读取一个图像的第一个字节并检查其后缀名。

还是可以用之前的图片马绕过

  1. 二次渲染

二次渲染会创建一个新图象,成功则返回图像标识符/图像资源,失败则返回false,从而会导致图片马的数据丢失,上传的图片马无法执行。

二次渲染是后端重写文件内容,常见会用到的函数有:

basename(path[,suffix])没指定suffix则返回后缀名,有则不返回指定的后缀名
 
strrchr(string,char)函数查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符。
 
imagecreatefromgif():创建一块画布,并从 GIF 文件或 URL 地址载入一副图像
 
imagecreatefromjpeg():创建一块画布,并从 JPEG 文件或 URL 地址载入一副图像
 
imagecreatefrompng():创建一块画布,并从 PNG 文件或 URL 地址载入一副图像

按照前面的方式上传,可以上传,但是包含漏洞无法解析。原因就是二次渲染将图片马里面的php代码打乱了。

这里我们需要使用二次渲染专用图。

具体制作二次渲染图片马的方式如下:

https://xz.aliyun.com/news/2337#toc-6

代码逻辑

这里主要指条件竞争,主要是设计逻辑的问题,可以参考upload-labs第18、19关

数组绕过

count()函数漏洞是一个具体情境,这里就不进行总结,具体可看upload-labs第21关

其他绕过

作者在总结文章时,发现其他人整理的其他绕过方法,这里简单列出,仅供参考。

解压缩getshell

渗透的时候,有时候会需要后台通过上传压缩包进行部署的功能,上传成功后自解压到指定目录。

解析漏洞详解

参考文章:https://blog.csdn.net/zzz6583zz/article/details/141608529

除了上面我们提到的两种解析漏洞,还有别的解析漏洞,我们这里对解析漏洞进行详解

解析漏洞解释:解析漏洞就是指服务器应用程序在解析某些精心构造的后缀文件的时候,会将相关的文件解析成网页的脚本,从而导致实现控制网站,同时大部分的解析漏洞都是由应用程序产生的。

一、IIS解析漏洞

  1. IIS5.0/6.0
  • 目录解析

​ 在以. asp文件名的文件夹下的任何文件都将作为asp文件执行。
  也就是当目录为XX.asp的时候,那么当访问下面的图片的时候,如XX.aps/1.jpg就会被当作ASP执行。

  • 文件解析

​ 如果文件为1.asp;.jpg,由于IIS不解析;后面的内容,使用就会把文件当作1.asp进行解析,同时还存在.asa、.cer、.cdx可以进行解析。

  • 修复建议

​ 1)限制上传目录的执行权限,不允许执行脚本。
  2)不允许创建目录。
  3)上传的文件进行重命名。

  1. IIS7.0/IIS7.5
  • IIS7.0/IIS7.5Fast-CGI

​ 在Fast-CGI开启的状态下,在类似1.jpg后面加上/1.php,变成/1.jpg/1.php路径会解析成PHP文件。需要进入php.ini里面修改cig.cgi_ pathinfo=1。
  将1.txt文本文件中的后门代码写入到1.jpg图片的二进制代码:
copy 1.jpg/b + 1.txt/a 1.jpg
  1.txt的文件内容为:

1
2
3
<?PHP 
fputs(fopen('shell.php','w'),'<?php eval($_POST[password])?>');
?>

​ 当在图片文件中写入后门代码,当访问这个图片的时候加上/.php的时候就会在当前目录下生成一句话木马shell.php。

  • 修复建议

​ 配置cgi.fix_pathinfo(php.ini中)为0并重启php-cgi程序
在这里插入图片描述

二、apache解析漏洞

  1. apache解析漏洞
  • 在apache1.x和apache2.x中存在解析漏洞。
      当一个文件为1.php.yy.xx的时候就会被当作php执行,这是由于在apache解析文件的时候有一个原则就是,以.后面的扩展名来解析,当遇见不认识的扩展名的时候,就会向前解析,直到遇到能够解析的后缀名为止。
      如:1.php.yy.xx,首先会解析xx,xx无法解析就会去解析yy,yy无法解析就会去解析php,那么php是能够解析了,那么就会结束。
      这种方式多用于绕过黑名单的检查。

  • 修复建议

​ 1)更新至最新版本。
  2)将上传的文件进行重命名。

三、nginx解析漏洞

  1. nginx<8.03
  • nginx配置文件错误

​ 由于nginx默认是用cgi解析php的,因此和iis一样可以制作图片马进行上传。
  当在图片文件中写入后门代码,当访问这个图片的时候加上/.php的时候就会在当前目录下生成一句话木马shell.php。

  • 修复建议

​ 1)配置cgi.fix_pathinfo(php.ini中)为0并重启php-cgi程序
在这里插入图片描述

​ 2)或如果需要使用到cgi.fix_pathinfo这个特性(例如:Wordpress),那么可以禁止上传目录的执行脚本权限。 或将上传存储的内容与网站分离,即站库分离。

​ 3)或高版本PHP提供了security.limit_extensions这个配置参数,设置security.limit_extensions = .php

  1. nginx 0.5/0.6/0.7<=0.7.65/0.8<=0.8.37
  • nginx空字节任意代码执行

​ 当使用PHP-FastCGI执行PHP时,遇到url里面存在%00空字节时与FastCGI的处理不一致,导致可在非PHP文件中嵌入PHP代码,通过访问url+%00.PHP来执行其中的PHP代码。
  比如上传一张图片马1.jpg,那么当访问这个图片马的时在1.jpg后面添加%00.php就会将图片马以php执行。

  • 修复建议

​ 1)升级nginx版本。

名字改造

一、filename改造

注意是对filename改造,不是对上传文件名改造

(1) 名字特殊符号替换以及构造异常闭合(符号方法很多自己天马星空,我这里就写几个就行了,但是要注意你改造了得让后端识别到,乱改造识别不到等于白搭)

1
2
3
4
filename='shell.jspx.jsp'
filename=shell.jspx.jsp
filename=shell.jspx.jsp'
"filename"=shell.jspx;

(2)重写

1
2
filename=shell.jpg;filename=shell.jspx;
filename=shell.jspx;filename=shell.jpg;

(3)大小写变化

1
FileName=shell.jspx.jsp'

(4)参数污染

1
2
3
4
FileName=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaashell.jspx.jsp'
FileName =shell.jspx(加空格)
filename===="shell.jspx.jsp1"(加等号)
FileName =shell.jspx(前后加空格,中间也可以加特殊符号fuzz)

(5)文件名字编码(filename一般为后端接收参数,编码了可能识别不到,这个就看情况)

1
filename=\u0073\u0068\u0065\u006c\u006c\u002e\u006a\u0073\u0070

(6)回车换行(有时候确实挺好用的,任意位置都可以试一下)

1
2
FileName=shell.jspx.
jsp

或者

1
2
File
Name=shell.jspx.jsp'

二、name改造

name也可以任意改造,改造的方法和filename差不多,就不重复发了,主要是思路重要。

其他的比如奇奇怪怪的正则需要用到的特殊字符都可以在文件名中fuzz一下,看看能否打断waf规则,也就是把我们fuzz后缀的再跑一次,或者再找点其他的正则字母,这里就不重复写了。

还有一些数据溢出啥的绕过方式,这里并不提及,读者可参考下文:

https://blog.csdn.net/zzz6583zz/article/details/141608529

参考文献

WEB入门——文件上传漏洞_htaccess文件上传漏洞-CSDN博客

文件上传漏洞全解析:策略、绕过与安全修复-CSDN博客


文件上传学习总结
http://example.com/2025/05/04/17文件上传学习总结/
作者
sangnigege
发布于
2025年5月4日
许可协议