DASCTF赛warmup-php

今天对四月份的DASCTF赛中的web题进行复现,在此总结一下。这道题是典型的代码审计,代码量非常的多。很容易就被劝退了。

先看代码:

<?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_);
        }
    }

}
<?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)).'"';
    }
}
<?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];
    }
}
<?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";
    }

}

大体看一下代码,他们之间是有继承关系的。

Filter.php和ListView都继承于Base。而TestView继承于ListView。这是题目附件中的四个源码文件。 先看题目,

 我们可以通过action进行GET传参,通过properties进行POST传参。下面的foreach数组遍历可以看出properties是要传数组进去。通过action传入的值会被当作类名,从而调用该类的run方法。

在四个类中检索run方法。只有在ListView.php中存在run方法。TestView类是子类中的子类,所以用action参数传入TestView类,实例化这个类,这四个类所以的方法都可以调用。妙~

接着浏览父类Base,看一下有没有执行命令的地方,有一个eval函数。

 这个eval函数在这个evaluateExpression方法里面,然后在其他类中检索有没有可以来调用这个evaluateExpression方法的函数,因此在Filter.php中找到getEtagValue()和getLastModifiedValue()

 

 再查看这两个类可不可以被调用。发现Base类中的_get魔术方法可以调用任意类。但是,没什么可以来触发_get方法的。

换个思路,来从run()函数走,在ListView类中有run()函数,可以调用renderContent()函数,

往下看,renderContent()函数功能是将template参数的内容当作renderSection函数的内容传入。那么接着往下看,传入的内容与render拼接,然后当作函数名调用。这样的话,我们就可以随意调用以render开头的函数了 。全局搜索以render为首的函数。

 通过代码分析,我们只能去调用无参的函数,我们调用TestView类中的renderTableBody函数,在该函数调用renderTableRow函数,这个函数中有我们要用的目的函数,就达成了命令执行。

整理一下思路:

run() -->renderContent() -->renderSection() -->renderTableBody() -->renderTableRow()-->evaluateExpression()

为什么可以这么调用,因为子类可以用父类的方法,而父类不能用子类的方法。回看上面写的继承

关系,应该就明白了。

目前来说,分析怎么构造payload。前面已说action=TestView,现在看properties参数。

其中template变量要赋值TableBody,来调用renderTableBody函数。因为preg_replace_callback函数进行正则匹配时被{}包裹,所以传参也需要{}包裹,不然不能匹配。

 在这个函数中需要满足data变量大于0才能调用renderTableRow函数。

接着就是对目标函数传参,进行命令执行了。该目标函数的参数就是rowHtmlOptionsExpression,

对这个参数传入恶意代码即可。

最终payload:

?action=TestView

properties[template]={TableBody} &properties[data]=1&properties[rowHtmlOptionsExpression]=system('/readflag'); 

得出flag。

在审计大量代码时,最痛苦的就是无用的东西太多,有用的信息太难找,在审计这些大量的代码的时候,一定不能浮躁。我们不需要读懂每一行代码,精读有用的代码,找出首尾,并且找出一个个有用的函数,形成一条逻辑思路。这样审计代码就不会太难。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

XiLitter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值