
Unauthenticated arbitrary file upload -> RCE in WPLP Cookie Consent (gdpr-cookie-consent) <= 4.4.1 - technical write-up and PoC
Proof-of-concept and technical write-up for CVE-2026-75865, an unauthenticated arbitrary file upload leading to remote code execution in the WPLP Cookie Consent – Cookie Banner & Consent Management for GDPR, CCPA & Google Consent Mode plugin for WordPress (slug gdpr-cookie-consent), affecting all versions up to and including 4.4.1.
| Key facts |
|---|
| CVE | CVE-2026-75865 |
| Severity | Critical — CVSS 3.1 9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| Weaknesses | CWE-862 (Missing Authorization) + CWE-434 (Unrestricted Upload of File with Dangerous Type) |
| Affected versions | gdpr-cookie-consent ≤ 4.4.1 |
| Fixed in | 4.4.2 |
| CVE assigner | Wordfence |
| Published | 2026-09-01 |
| Discovered by | Supakiad S. (m3ez) |
The plugin's SaaS "connector" REST endpoints authenticate requests by delegating to the vendor's SaaS application, which validates a Bearer JWT — but the plugin only checks that the validation endpoint answers HTTP 200. There is no ownership binding between the JWT and the target site, so any valid token issued by the SaaS (e.g. from a free account) is accepted.
The saas_upload_logo() function then writes an attacker-supplied base64 blob into the public wp-content/uploads/ directory under an attacker-supplied filename, without any content or extension validation. The stored API token (master_key) that also guards the endpoint is not actually secret: the plugin's own /store-auth route overwrites it from request parameters after the same JWT check, so the full chain — from unauthenticated request to an executed payload in the web root — requires only a target URL and a free SaaS account.
The vulnerability was fixed in 4.4.2 by removing the upload endpoint entirely and reworking the connector trust model (JWT ownership binding, hash_equals(), HMAC request signing).
Full technical analysis: REPORT.md
| File | Description |
|---|---|
REPORT.md | Detailed technical write-up: root-cause analysis with line-referenced code, the 4.4.1 → 4.4.2 fix comparison, exploitation chain, detection & mitigation |
cve_2026_75865.py | PoC script — scans a target list, detects vulnerable sites, and runs the full chain (admin enumeration → master-key overwrite → payload upload → verification) with live output |
payload.php | Minimal benign payload used by default: prints a marker string and supports self-removal (?delete=1) |
git clone https://github.com/ghostpels/CVE-2026-75865.git
cd CVE-2026-75865
pip install requests # colorama is optional (colored logs)
# 1. Save a valid SaaS JWT (any account at app.wplegalpages.com) into jwt.txt
# 2. Create a target list and run only against systems you are authorized to test:
python cve_2026_75865.py -l targets.txt -o vuln.txt -t 40 --marker CVE-2026-75865-POC
Verified payload URLs are appended to vuln.txt live, one per line, as soon as each target is verified.
| Option | Default | Description |
|---|---|---|
-l, --list | (required) | Target list, one URL per line |
-o, --output | vuln.txt | Verified payload URLs, appended live |
-t, --threads | 40 | Concurrent workers |
--shell | payload.php | Payload file next to the script; uploaded name = its basename |
--filename | — | Custom uploaded filename (its extension is tried first, then --exts) |
--exts | php,phtml,php5,php74,php7,php3,php4,phar | Fallback extensions when the primary one is blocked |
--jwt-file | jwt.txt | SaaS JWT, first non-empty line |
--marker | — | String that must appear in the payload response to count as valid |
--timeout | 15 | Per-request timeout (seconds) |
-k, --insecure | off | Disable TLS verification |
-v, --verbose | off | Show connection error details |
This repository is published for educational and defensive security research only. Use the tool exclusively against systems you own or have explicit written permission to test. The author is not responsible for any misuse or damage caused by this material.