> ## Content Index
> Fetch the complete content index at: https://plaintext.block8.ai/llms.txt
> Use this file to discover other available public pages before exploring further.

# The Blog Post That Writes Scripts (CVE-2026-78746)
- URL: https://plaintext.block8.ai/blog/the-blog-post-that-writes-scripts-cve-2026-78746/
- Published: 2026-10-25T22:43:30.000Z
- Updated: 2026-10-27T01:52:24.000Z
- Author: Lukasz S
- Tags: CVE

## TL;DR

Pluck CMS 4.7.20 and earlier ships a stored cross-site scripting flaw in its blog module (CVE-2026-78746). When an administrator saves a blog post, the content is stored through a sanitiser that is quietly told *not* to encode HTML. The raw markup is then echoed straight into the page when anyone reads the blog. An authenticated admin who pastes `<img src=x onerror=alert(1)>` into a post turns that post into a script that runs in every visitor's browser. Block8.ai's Security Research Team rates it CVSS 3.1 of 4.8 (Medium), CWE-79.

## What a blog module is supposed to do

A blog module has one modest job: take some text an author types, keep it safe on disk, and show it to readers later. The author expects formatting to survive. The reader expects to read, not to be reprogrammed.

The trust assumption hiding in there is simple. The author is allowed to write words. The reader's browser should only ever *display* those words, never treat them as instructions. The moment a reader's browser starts executing what an author typed, the whole arrangement has quietly changed from "publishing" to "remote control".

Pluck CMS gets this subtly, importantly wrong.

## Where the trust breaks

Pluck does have a sanitiser. It is called `sanitizePageContent()`, and it is called when a post is saved by `blog_save_post()` in `data/modules/blog/functions.php`. So far, so reassuring.

The problem is the second argument:

```php
sanitizePageContent($content, false)

```

That `false` is the HTML flag. When it is `false`, the function does the bookkeeping you would want for safely writing the content into a PHP data file, escaping backslashes and single quotes, but it skips the one step that matters for the reader: `htmlspecialchars()`. Nothing turns `<` into `&lt;`. The HTML tags you typed stay exactly as HTML tags.

So the content lands on disk raw. Here is a confirmed stored payload from a test instance, sitting in `data/settings/modules/blog/posts/1.block8xss.php`:

```php
$post_content = '<img src=x onerror=alert("BLOG_B8")>';

```

That is not escaped text waiting to be displayed. That is a working script, parked and ready.

## Under the bonnet

The second half of the bug is on the output side, in `blog_theme_main()` within `data/modules/blog/blog.site.php`. When a visitor loads any page that embeds the blog module, the stored content is written to the response like this:

```php
echo $post['content'];

```

No encoding on the way in. No encoding on the way out. The `<img>` tag is parsed by the browser as a real image element, the bogus `src=x` fails to load, the `onerror` handler fires, and the attacker's JavaScript executes in the context of the site.

Reproducing it is short work:

1. Log in as admin.
2. Visit any page that shows the blog module.
3. The `onerror` handler fires. In testing, `alert("BLOG_B8")` rendered raw in the HTML and ran.

POST to `admin.php?module=blog&page=editpost` with:

```
cont1=testpost&cont2=General&cont3=<img src=x onerror=alert("XSS")>&save_exit=Save

```

The `cont3` parameter is the post body. The form handler for all of this is `blog_page_admin_editpost()` in `blog.admin.php`, which hands the content down into `blog_save_post()` and that fateful `false`.

This is the same class of mistake as CVE-2026-31205, which covered the page editor via `editpage.php`. Same root cause, different doorway. This one lives entirely inside the blog module's own content path, so patching the page editor does not close it.

## Why it matters

Stored XSS is the patient kind. The payload is written once and then served to everyone, no further interaction from the attacker required. Because the script runs with the privileges of the site in the visitor's browser, the practical outcomes include session token theft, credential phishing dressed up in the real site's chrome, defacement, and drive-by malware delivery.

There is a privilege cost to triggering it: you need an authenticated administrator account to author the post, which is why the CVSS base score sits at 4.8 rather than higher. But the *blast radius* is every reader of the blog, and the CVSS scope is marked changed for exactly that reason. One compromised or careless admin account becomes a problem for the entire audience.

## How to fix it

The fix is small and specific: stop telling the sanitiser to skip HTML encoding for content that will be echoed into a page.

- Call `sanitizePageContent()` with the HTML flag set to `true` for blog content, or apply `htmlspecialchars()` explicitly at the output point in `blog.site.php` before the `echo`.
- If the product genuinely needs to allow *some* markup in posts, switch to an allowlist-based HTML sanitiser rather than passing raw input through untouched. A boolean "encode / don't encode" switch is the wrong tool for "allow bold but not scripts".
- Encode on output as a rule, not just on input. Output encoding is the layer that actually protects the reader.

Until a patched release is available, operators should treat blog authorship as a sensitive capability and restrict it to trusted accounts.

## Closing

This one is a good reminder that a sanitiser you *called* is not the same as a sanitiser that *ran*. The function was right there in the code path, invoked on exactly the right data, and still let a live script through because of a single `false`. The Block8.ai Security Research Team flagged it during authorised research into Pluck CMS; the lesson travels well beyond this one module. When user content ends up in a page, encode it on the way out, every time, and never trust a flag to remember to do it for you.

![Block8.ai](https://plaintext.block8.ai/assets/images/logos/B8_Shield-Magenta.svg) 

AI-powered penetration testing

### Block8.ai Team

[Meet the team →](https://plaintext.block8.ai/team/)