BraveCMS stores page and article body content entered through the CKEditor rich-text editor verbatim in the database, with no HTML sanitization applied at write or read time. The content is later rendered using Laravel Blade's unescaped output directive {!! !!}, causing any JavaScript injected by an editor-role user to execute permanently in every visitor's browser upon page load. If the victim is an administrator, their session cookie can be replayed to take full control of the CMS.
Summary
| Field |
Value |
| CVE ID |
CVE-2026-41524 |
| Advisory |
GHSA-xj46-722x-6433 |
| Severity |
High |
| CWE |
CWE-79 (Stored Cross-site Scripting) |
| Package |
BraveCMS-2.0 (PHP) |
| Affected |
2.0 |
| Patched |
None |
| Vulnerability |
Stored XSS via unsanitized CKEditor output |
| Impact |
Session hijacking, full CMS takeover via admin cookie theft |
Article and page content submitted through the CKEditor rich-text editor is saved to the database without any sanitization. When the content is retrieved and rendered in the front-end templates, it passes through Laravel Blade's {!! !!} directive, which explicitly bypasses HTML escaping and outputs the stored string raw. An author-level user can inject arbitrary JavaScript that will execute in the browser of every visitor who loads the affected page, including administrators.
How it works
The vulnerability is a combination of two missing controls: no sanitization at write time and unsafe rendering at read time.
On the write path, the controller saves the raw request body directly to the database:
// Controller stores content verbatim, no sanitization
$article->content = $request->input('content');
$article->save();
On the read path, the Blade templates render the stored content with the unescaped directive across all four affected template files:
{{-- Unescaped output: raw HTML including any injected scripts is sent to the browser --}}
{!! $page->content !!}
{!! $article->content !!}
Laravel's {{ }} directive would HTML-encode the output and neutralize any injected markup. The {!! !!} directive does the opposite: it sends the stored string to the browser exactly as it was saved, treating it as trusted HTML. Because the content originates from user input, this trust is misplaced.
Root Cause
The application intentionally uses {!! !!} to preserve the rich HTML formatting that CKEditor produces. This is a common pattern, but it is only safe when the HTML has been sanitized through an allowlist-based purifier before storage or before rendering. Neither step is present here. The CKEditor toolbar gives authors the ability to insert arbitrary HTML, and the template renders whatever they submit without filtering.
Prerequisites
- A valid BraveCMS account with at least Author privileges.
- Access to the article or page creation and editing endpoints.
- A victim must load the published page for the payload to execute in their browser.
Fix
Two approaches are available depending on whether rich HTML output is required.
If only plain text is needed, replace the unescaped directive with the safe escaped version across all four affected templates:
{{-- Before (vulnerable): raw HTML output --}}
{!! $page->content !!}
{{-- After (safe for plain text): HTML-encoded output --}}
{{ $page->content }}
If rich HTML formatting must be preserved, integrate an allowlist-based HTML purifier such as mews/purifier and sanitize the content before rendering:
composer require mews/purifier
{{-- After (safe for rich HTML): sanitized output through an allowlist purifier --}}
{!! Purifier::clean($page->content) !!}
{!! Purifier::clean($article->content) !!}
The purifier strips any tag or attribute not explicitly permitted by the allowlist, including <script>, event handler attributes (onerror, onload, onclick), and javascript: URIs, while preserving legitimate formatting elements such as headings, paragraphs, lists, and links.
Apply the same fix to all four affected templates and audit any other {!! !!} usage across the codebase for additional occurrences of user-supplied data being rendered without sanitization.
Key Takeaways
- Using
{!! !!} in Laravel Blade is a deliberate opt-out of output encoding. Every instance that renders user-supplied data must be paired with a sanitization step; there are no exceptions.
- Rich HTML support and security are not mutually exclusive, but they require an explicit allowlist-based purifier. Relying on the editor to produce safe HTML is not a control.
- Stored XSS is more severe than reflected XSS. The payload is delivered to every visitor without any further attacker involvement, and it persists until the content is deleted or sanitized. A single poisoned article can compromise every administrator session that visits it.
- Author-level accounts are a meaningful attack surface. In any CMS where editor accounts can publish content that administrators review, stored XSS in the content body is a reliable path to privilege escalation.