Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-70376 — Advisory and Python PoC for Pluck CMS CSRF: fail-open Referer check plus double-extension upload enables webshell deployment and remote code execution. | Kitploit
Tools/GitHubGitHub/ilhomjonr/cve-2026-70376
Vulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityPenetration TestingLearning & Education
GitHubilhomjonr/cve-2026-70376

CVE-2026-70376

Advisory and Python PoC for Pluck CMS CSRF: fail-open Referer check plus double-extension upload enables webshell deployment and remote code execution.

View Repository
142 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-70376 — Pluck CMS Site-wide CSRF → RCE

Fail-open Referer check + no CSRF tokens + double-extension upload

A single admin page-visit deletes content, injects pages, and drops a webshell

CVE CVSS 3.1 CWE CWE

Product Status Researcher

At a glance · Summary · Root cause · Attack chain · Exploit · Remediation · Timeline


📋 At a glance

CVE IDCVE-2026-70376
Tracking IDPT-2026-68036
Productpluck-cms/pluck — Pluck CMS (flat-file PHP)
Affected4.7.x through 4.7.21-dev / current master
WeaknessCWE-352 (CSRF) · CWE-434 (unrestricted upload, amplifier)
CVSS v3.18.0 — High · AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:H
VectorNetwork · no privileges · one admin page-visit (UI:R)
ImpactContent destruction/DoS, stored-content injection, RCE on Apache/mod_php
Introducedcommit f79f916 (Dec 2019) — vulnerable logic present since
ResearcherIlhomjon Rustamov (@IlhomjonR)

🔎 Summary

The Pluck admin panel has no per-request CSRF tokens anywhere in the codebase. Every state-changing admin action is gated by a single function, requestedByTheSameDomain(), whose only defense is a Referer-host comparison — and that check fails open: when a request arrives with no Referer header at all, the function returns true and the action is allowed.

Because an attacker page fully controls whether a Referer is sent (<meta name="referrer" content="no-referrer">), any logged-in administrator who visits a malicious page can be forced to perform privileged actions cross-site. Several destructive actions run on GET, and Pluck sets no SameSite attribute on PHPSESSID (browsers apply SameSite=Lax, which still rides along on top-level GET navigations), so they are reachable cross-site under default browser settings.

A secondary upload-filter weakness lets the same CSRF plant a shell.php.jpg double-extension file — turning the CSRF into a drive-by RCE on Apache/mod_php hosts.


🧬 Root cause

1. Fail-open Referer check — data/inc/functions.admin.php

function requestedByTheSameDomain() {
    if (isset($_SERVER['HTTP_HOST'])) { $myDomain = $_SERVER['HTTP_HOST']; }
    elseif (isset($_SERVER['SCRIPT_URI'])) { $myDomain = $_SERVER['SCRIPT_URI']; }
    else { $myDomain = NULL; }

    if (isset($_SERVER['HTTP_REFERER'])) { $requestsSource = $_SERVER['HTTP_REFERER']; }
    else { $requestsSource = NULL; }

    $referelDomain = parse_url($requestsSource, PHP_URL_HOST);

    if ($myDomain != NULL && $requestsSource != NULL &&
        (strcmp(trim($myDomain), trim($referelDomain)) === 0)) {
        return true;                 // Referer host == our host  -> allow
    } elseif ($myDomain == NULL || $requestsSource == NULL) {
        show_error("Be carefull with clicking links, ...", 1);
        return true;                 // Referer ABSENT -> FAIL OPEN -> allow  <==
    } else {
        return false;                // Referer host mismatch -> block
    }
}

A cross-site request with a foreign Referer is correctly rejected (the else branch), which creates a false sense of protection — but the attacker simply suppresses the Referer, hits the fail-open branch, and the request is allowed. There is no token layer behind this check.

The gate is applied once in admin.php and trusted for the whole action switch:

$isCSRF = requestedByTheSameDomain();
if (isset($_GET['action']) && $isCSRF) {
    switch ($_GET['action']) {
        case 'deletefile':  include_once('data/inc/deletefile.php');  break;
        case 'deleteimage': include_once('data/inc/deleteimage.php'); break;
        case 'deletepage':  include_once('data/inc/deletepage.php');  break;
        case 'module_delete': /* ... */
        case 'images':      include_once('data/inc/images.php');      break; // upload
        // ...
    }
}

2. No SameSite on the session cookie (amplifier)

Pluck never calls session_set_cookie_params(), so PHPSESSID inherits the empty default → browsers apply SameSite=Lax, which still rides along on top-level GET navigations. GET-exposed actions (deletefile, deleteimage, deletepage, module_delete, theme_delete, logout) are therefore forgeable with a single page visit.

3. Double-extension upload — data/inc/images.php (RCE amplifier)

if (in_array($_FILES['imagefile']['type'],                       // client-controlled MIME
    array('image/pjpeg','image/jpeg','image/png','image/gif'))) {
    $imagewhitelist = array('jfif', '.png', '.jpg', '.gif', 'jpeg');
    if (!in_array(strtolower(substr($_FILES['imagefile']['name'], -4)), $imagewhitelist)) {
        show_error($lang['general']['upload_failed'], 1);         // only checks LAST 4 chars
    } else {
        copy($_FILES['imagefile']['tmp_name'], 'images/'.latinOnlyInput($_FILES['imagefile']['name']));
        // ...
    }
}

Both checks are trivially bypassed:

  • the MIME type comes from the client ($_FILES[...]['type']) → set image/jpeg;
  • only the last 4 characters of the filename are validated → shell.php.jpg ends in .jpg and passes.

The file is written to images/shell.php.jpg; on an Apache/mod_php host with multi-extension handling it executes as PHP.


⛓️ Attack chain

One authenticated admin page-visit — no click.

flowchart LR
    A[Admin logged into Pluck] --> B[Opens attacker page]
    B --> C["meta referrer=no-referrer<br/>suppresses Referer"]
    C --> D[Top-level nav / auto-form to admin.php]
    D --> E["Lax PHPSESSID cookie rides along<br/>Referer absent"]
    E --> F["requestedByTheSameDomain() -> FAIL OPEN -> true"]
    F --> G1[deletefile / deletepage -> destruction / DoS]
    F --> G2[editpage -> stored-content injection]
    F --> G3["images upload -> shell.php.jpg -> RCE"]

Forgeable actions include:

ActionMethodImpact
admin.php?action=deletefile&var1=<f>GETDelete arbitrary uploaded file
admin.php?action=deletepage&...GETDelete site pages (DoS)
admin.php?action=module_delete&...GETRemove modules
admin.php?action=logoutGETLog the admin out
admin.php?action=editpagePOSTInject stored page content
admin.php?action=images (upload)POSTPlant shell.php.jpg → RCE

💥 Exploit

A working PoC toolkit lives in exploit/:

  • pluck_csrf_rce.py — prove the fail-open logic, CSRF-upload a webshell and run commands, CSRF-delete files, or generate a lure.
  • csrf_poc.html — the standalone drive-by page delivered to a victim admin.
pip install requests
Download Tool