Aug 4, 2026·7 min read·20 visits
Ghost CMS versions 5.18.0 through 6.21.1 allow unauthenticated attackers to enumerate registered member email addresses due to distinct HTTP response codes and messages returned by the magic link sign-in endpoint. Upgrading to 6.21.1 mitigates this.
CVE-2026-53947 is an observable response discrepancy (CWE-204) in Ghost CMS that permits unauthenticated remote user enumeration via the passwordless magic link sign-in endpoint.
Ghost is an open-source, Node.js-based content management system commonly utilized for publishing, newsletters, and membership platforms. In Ghost 5.18.0, native subscription capabilities were enhanced with passwordless sign-in functionalities, allowing members to access subscriber-only content without maintaining local passwords. This capability is managed through a portal subcomponent that relies on magic links dispatched to registered email addresses.
The unauthenticated nature of the sign-in endpoint introduces a critical attack surface. Specifically, the API endpoint responsible for dispatching these magic links is accessible to any remote client without previous authentication. This design enables external entities to interact with the internal database query mechanisms responsible for verifying registered emails.
Under CWE-204, the application's failure to sanitize responses based on query outcomes yields an observable discrepancy. This behavior exposes structural state data, specifically the presence or absence of an email address in the user repository. Attackers can leverage this systemic leakage to build lists of registered users, facilitating subsequent phishing or brute-force campaigns against associated services.
The logical flaw is located within the API routing controller of Ghost's members service. When a client requests a magic link via the Portal UI, a POST request is initiated against the /members/api/send-magic-link endpoint. This request is processed by the class RouterController within the codebase at ghost/core/core/server/services/members/members-api/controllers/router-controller.js.
The controller executes a sequence of validation tasks before invoking the notification service. First, the application normalizes the inbound email string. Next, it queries the member repository database to retrieve record data. In versions prior to 6.21.1, the backend controller handled non-existent accounts by raising an explicit exception instead of simulating success.
This branching logic created two distinct outcomes. A valid member triggered an HTTP 201 response containing transactional state data. An invalid email immediately terminated the control flow with an HTTP 400 Bad Request error. The returned JSON body contained precise error messages indicating that the account was not found. This distinction allowed external entities to map the underlying database state with certainty.
To remedy the discrepancy, Kevin Ansfield committed a patch under Commit ID fb2bb634653d99de68fc42d415721d755284fe30. The fix alters the execution path for non-existent users by normalizing the return structures. Instead of raising an exception, the application generates mock data that conforms to the expected structure of a successful transaction.
// Prior to patch fb2bb634653d99de68fc42d415721d755284fe30
const member = await this._memberRepository.get({email: normalizedEmail});
if (!member) {
throw new errors.BadRequestError({
message: this._allowSelfSignup() ? tpl(messages.memberNotFoundSignUp) : tpl(messages.memberNotFound)
});
}
// Post patch fb2bb634653d99de68fc42d415721d755284fe30
const member = await this._memberRepository.get({email: normalizedEmail});
if (!member) {
// Return a fake otcRef when OTC was requested so the response
// shape is identical regardless of whether a member exists
return includeOTC ? {otcRef: crypto.randomUUID()} : {};
}This architectural change ensures that the controller returns a standard object shape. If a One-Time Code (includeOTC) is requested, the server generates a cryptographically random UUID using the native crypto module. This mimics a valid database lookup and returns an HTTP 201 Created response to the client.
Simultaneously, client-side UI configurations were modified to prevent localization leaks. Changes within apps/portal/src/components/pages/magic-link-page.js converted definitive UI assertions into conditional statements. The interface now presents the same message regardless of whether the email address is actively registered, completing the user interface side of the normalization process.
The following flowchart displays the execution path differences for registered and unregistered email lookups in both vulnerable and patched states.
Exploitation of CVE-2026-53947 does not require specialized toolkits or elevated operational privileges. An attacker only requires network-level access to the Ghost portal instance and a target list of email addresses. This process is easily automated using standard scripting environments or command-line utilities.
An attacker initiates exploitation by sending an HTTP POST request targeting the endpoint with the query email formatted in JSON. The following request represents a typical payload structure:
POST /members/api/send-magic-link HTTP/1.1
Host: target-ghost-site.com
Content-Type: application/json
{
"email": "target_user@example.com",
"emailType": "signin"
}The server's response code dictates the classification. An HTTP 400 Bad Request indicates that the email is unregistered, while an HTTP 201 Created signals that the address corresponds to a registered member. Because of the predictable nature of the endpoint, this sweep can be performed concurrently against thousands of addresses to perform large-scale user mapping.
While the logical response normalization resolves the application-layer discrepancy, security teams must note that secondary side-channels persist. Specifically, timing side-channels remain a vector for identification. The execution times of the two logical branches are fundamentally different.
In the valid user execution path, Ghost must initialize several external tasks. After verifying the member, it writes transactional tokens to the localized SQL database and initializes network sockets to communicate with SMTP relays (such as Mailgun) to dispatch the authentication email. This process introduces significant network and disk latency, often requiring 150ms to 1000ms to complete.
In contrast, the invalid user execution path bypasses the mailer queue and database serialization. The engine generates a UUID and exits the execution thread immediately, typically in under 15ms. An attacker employing statistical analysis tools can measure response latency over multiple iterations to differentiate between the two scenarios despite identical status codes.
To exploit this timing side-channel, attackers use specialized timing-analysis frameworks. They establish a baseline of the target server's local processing speeds, then send batches of requests. If a significant timing delta is observed, the email is classified as a valid account. This illustrates that complete mitigation of user enumeration requires both logical normalization and timing padding.
The impact of user enumeration via CVE-2026-53947 is categorized as a loss of confidentiality regarding membership directories. While the vulnerability does not directly expose authentication credentials or session identifiers, the information harvested has high utility for targeted attacks.
Attackers can leverage validated email lists to perform targeted spear-phishing campaigns. By knowing that an email is specifically associated with a particular Ghost instance, attackers can construct highly convincing lures impersonating the platform administration. This increases the probability of credential harvest or malware execution.
Additionally, this data feeds directly into credential stuffing pipelines. If an attacker has compromised a database from another platform, they can check those emails against the Ghost site. Upon finding active accounts, they can attempt to reuse known passwords or execute password reset attacks, expanding the threat landscape to include potential takeover of subscriber profiles.
Remediation of CVE-2026-53947 requires updating Ghost CMS to version 6.21.1 or higher. This release integrates the response normalization fixes and the UI adjustments that mitigate application-layer enumeration. Organizations should schedule this update within normal maintenance windows.
If patching is not immediately feasible, organizations should deploy proxy-level protections. Enforcing strict rate limiting on the /members/api/send-magic-link endpoint is a highly effective workaround. Security administrators can leverage Nginx or Web Application Firewalls (such as Cloudflare) to restrict access to a maximum of 3 requests per IP address per minute.
# Example Nginx Rate Limiting Configuration
limit_req_zone $binary_remote_addr zone=magic_link_limit:10m rate=3r/m;
server {
location /members/api/send-magic-link {
limit_req zone=magic_link_limit burst=5 nodelay;
proxy_pass http://ghost_backend;
}
}Additionally, security operations teams should configure detection alerts. Monitoring system logs for high frequencies of POST requests targeting the magic-link endpoint can identify scanning patterns. Alerts should be triggered when a single client IP initiates multiple requests within a short time interval, allowing rapid identification and blocking of hostile enumeration sweeps.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Ghost TryGhost | >= 5.18.0, < 6.21.1 | 6.21.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-204 (Observable Response Discrepancy) |
| Attack Vector | Network (Remote, Unauthenticated) |
| CVSS Score | 5.3 (Medium) |
| EPSS Score | 0.00206 (Percentile: 10.84%) |
| Impact | User Enumeration |
| Exploit Status | None (No public weaponized exploit available) |
| KEV Status | Not Listed in CISA KEV |
The product behaves differently when executing an operation depending on whether a given input is valid, exposing differences that allow attackers to infer state information.
An authorization bypass vulnerability exists in the legacy session export API of Indico, an open-source event management system developed at CERN. Due to a missing object-level access check, authenticated users can bypass configuration-level restrictions to extract private session metadata (including session titles, descriptions, and list of conveners) from events that they are otherwise authorized to view.
An incomplete Server-Side Request Forgery (SSRF) validation check in Indico prior to version 3.3.13 allows authenticated event organizers to bypass outbound network restrictions. By utilizing backslash characters within crafted URLs, attackers can exploit a parser differential between the application's validator and the downstream HTTP client library to access internal network resources.
CVE-2026-107717 represents a critical prompt boundary bypass and chat role injection vulnerability in the Banks Python package (versions prior to 2.5.0). The library parses generated template outputs line-by-line, attempting to validate each segment as a JSON-serialized ChatMessage object without validating the source boundaries of the text. If an application integrates user input directly into a prompt template, a remote, unauthenticated attacker can supply multi-line inputs with structured JSON payloads. This input is then parsed as high-privilege system instructions or tool execution responses, completely hijacking downstream Large Language Model behavior.
Improper pathname limitation and link resolution (CWE-22 and CWE-59) in the banks library prior to version 2.5.1 allow local attackers to read or write arbitrary files via crafted symbolic links in the prompt directory registry.
Improper validation of dynamic class resolution within Hazelcast's Zero Config Compact Serialization allows unauthenticated clients to trigger reflective class instantiation. This flaw can be exploited to read arbitrary JVM heap or off-heap memory, crash cluster nodes, or achieve arbitrary code execution under specific classpath conditions. This issue is resolved in Hazelcast versions 5.4.5, 5.5.10, 5.6.1, and 5.7.0.
An authentication bypass vulnerability in NearForm's fast-jwt before version 6.3.4 allows attackers to replay expired tokens due to an error in the verifier's cache expiration logic. When caching is enabled, the cache TTL defaults to 10 minutes instead of honoring the token's exp claim if the token lacks an iat claim.