Mar 5, 2026·5 min read·3 visits
Ghost < 5.105.0 fails to bind One-Time Codes (OTCs) to specific sessions and lacks strict CSRF origin validation. Attackers can exploit this to bypass authentication challenges or hijack administrator accounts via malicious cross-origin requests.
Ghost, a popular open-source publishing platform, contains critical vulnerabilities in its authentication mechanisms affecting versions prior to 5.105.0. The platform failed to cryptographically bind One-Time Codes (OTCs) to the initiating browser session and implemented insufficient Cross-Site Request Forgery (CSRF) protections on sensitive endpoints. These architectural flaws allow attackers to potentially bypass authentication challenges or hijack administrator accounts by leveraging cross-origin requests and reusing valid OTCs across different sessions.
The Ghost content management system (CMS) utilizes One-Time Codes (OTCs) for critical authentication flows, including multi-factor authentication (2FA) and password resets. A security audit revealed that these OTCs were generated statelessly, meaning they were not tied to the specific browser session of the user requesting them. Additionally, the platform's CSRF protection middleware lacked strict validation of request origins.
This combination of flaws creates a high-severity authentication bypass vector. An attacker who can coerce a victim into visiting a malicious site can trigger cross-origin requests against the Ghost admin panel. Because the OTCs are not session-bound, a code generated in the victim's session could theoretically be accepted in an attacker's session if intercepted or if the authentication flow allows for state manipulation. The vulnerability is tracked as GHSA-9m84-wc28-w895 and addresses weaknesses mapped to CWE-352 (Cross-Site Request Forgery) and CWE-613 (Insufficient Session Expiration).
The vulnerability stems from two distinct but compounding implementation errors in ghost/core/core/server/services/auth.
1. Stateless OTC Generation (CWE-613)
Prior to the fix, the generation of One-Time Codes relied exclusively on a global admin_session_secret and the target userId. The Time-based One-Time Password (TOTP) generation logic did not include any session-specific entropy. Consequently, a valid OTC for a specific user was globally valid for that user across any browser or client. This lack of session binding meant that if an attacker could observe or predict an OTC, they could use it in a separate session to authenticate as the victim, bypassing the intended security model where an OTC should only verify the session that requested it.
2. Permissive CSRF Validation (CWE-352)
The cookieCsrfProtection middleware was responsible for verifying that requests to sensitive endpoints originated from trusted domains. However, the implementation was overly permissive. It checked for the presence of a session origin but failed to rigorously compare the HTTP Origin or Referer headers against the server's configured admin_url or site_url. This logic allowed cross-origin requests (such as those initiated via <form> submissions from malicious third-party sites) to reach authentication endpoints without being rejected, facilitating CSRF attacks.
The remediation introduced in commit ec065a774 enforces strict session binding and origin checking. The analysis below highlights the critical changes in the OTP service and CSRF middleware.
OTP Service Binding (otp.js)
The generate and verify functions were updated to accept a context parameter. This context is a session-specific challenge (e.g., req.session.auth_code_challenge), ensuring the generated code is mathematically bound to the user's current session.
// BEFORE: Code depends only on secret and userId
function generate(userId, secret) {
return totp.generate(`${secret}${userId}`);
}
// AFTER: Code depends on secret, userId, AND session context
function generate(userId, secret, context = '') {
// context is a random challenge stored in the session
return totp.generate(`${secret}${userId}${context}`);
}CSRF Middleware Hardening (session-service.js)
The middleware was refactored to explicitly validate the request origin against the configured admin URL. If the Origin header does not strictly match the expected adminOrigin, the request is immediately rejected.
// AFTER: Explicit Origin Check
function cookieCsrfProtection(req, session) {
const origin = getOriginOfRequest(req);
const adminUrl = urlUtils.getAdminUrl() || urlUtils.getSiteUrl();
const adminOrigin = new URL(adminUrl).origin;
// Strict equality check
if (origin !== adminOrigin) {
throw new BadRequestError({
message: `Request made from incorrect origin. Expected '${adminOrigin}' received '${origin}'.`
});
}
// ...
}To exploit these vulnerabilities, an attacker typically requires the victim (a Ghost administrator) to have an active session or to be in the process of authenticating. The attack flow leverages the lack of session binding.
/ghost/api/admin/session/verify or similar authentication endpoints.This vulnerability poses a High risk to Ghost installations. Successful exploitation leads to Account Takeover (ATO) of administrative users. An attacker with administrative access to Ghost can fully compromise the application.
The vulnerability is particularly dangerous because it bypasses the intended security controls of Multi-Factor Authentication (MFA) by decoupling the verification code from the session it was intended for.
| Product | Affected Versions | Fixed Version |
|---|---|---|
Ghost Ghost Foundation | < 5.105.0 | 5.105.0 |
| Attribute | Detail |
|---|---|
| Vulnerability Type | CSRF & Session Fixation |
| CWE ID | CWE-352, CWE-613 |
| Attack Vector | Network (Web) |
| Severity | High |
| Exploit Status | Proof of Concept (Internal) |
| Component | core/server/services/auth |