CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



GHSA-9M84-WC28-W895
High

GHSA-9m84-wc28-w895: Incomplete CSRF Protection and Weak OTC Binding in Ghost

Alon Barad
Alon Barad
Software Engineer

Mar 5, 2026·5 min read·3 visits

PoC Available

Executive Summary (TL;DR)

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.

Vulnerability Overview

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).

Root Cause Analysis

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.

Code Analysis

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}'.`
        });
    }
    // ...
}

Exploitation Scenarios

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.

  1. Preparation: The attacker hosts a malicious webpage containing JavaScript or hidden forms designed to target the Ghost instance's /ghost/api/admin/session/verify or similar authentication endpoints.
  2. Lure: The attacker tricks the administrator into visiting the malicious site via phishing or social engineering.
  3. Execution: The malicious site issues a cross-origin request to the Ghost admin API. Due to the incomplete CSRF protection, the Ghost server accepts the request.
  4. Takeover: If the attack vector triggers an OTC generation (e.g., a password reset request or 2FA challenge), the stateless nature of the OTC allows the attacker to potentially utilize the generated code (if obtained via other means or if the flow state is predictable) in their own session, effectively hijacking the account.

Impact Assessment

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.

  • Confidentiality: Attackers can access private drafts, subscriber lists, and internal settings.
  • Integrity: Attackers can modify content, inject malicious JavaScript into themes (leading to Stored XSS against readers), or deface the site.
  • Availability: Attackers can delete content or disable the site entirely.

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.

Official Patches

GhostGitHub Commit: Fix for CSRF and OTC binding

Fix Analysis (1)

Technical Appendix

CVSS Score
High/ 10

Affected Systems

Ghost CMS (npm package 'ghost')

Affected Versions Detail

Product
Affected Versions
Fixed Version
Ghost
Ghost Foundation
< 5.105.05.105.0
AttributeDetail
Vulnerability TypeCSRF & Session Fixation
CWE IDCWE-352, CWE-613
Attack VectorNetwork (Web)
SeverityHigh
Exploit StatusProof of Concept (Internal)
Componentcore/server/services/auth

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1550Use Alternate Authentication Material
Defense Evasion
T1110Brute Force
Credential Access
CWE-352
Cross-Site Request Forgery (CSRF)

Vulnerability Timeline

Vulnerability Disclosed via GHSA
2025-02-26
Patch Released in Ghost v5.105.0
2025-02-26

References & Sources

  • [1]Fix Commit on GitHub

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.