#!/usr/bin/env python3 """ RedRays Scanner for Vulnerability CVE-2025-31324 ----------------------------------------------- A professional security tool to detect and mitigate critical SAP CVE-2025-31324 vulnerability. Features: - Detects vulnerability in Visual Composer component (SAP Security Note 3594142) - Scans for known malicious webshells - Follows security best practices CVSS Score: 10.0 (Critical) """ import argparse import logging import sys from typing import List, Tuple from dataclasses import dataclass import requests from urllib3.exceptions import InsecureRequestWarning # Configure logging logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S" ) logger = logging.getLogger("redray-scanner") # Suppress only the specific InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) # Constants KNOWN_WEBSHELLS = ["cache.jsp", "helper.jsp", "nzwcnktc.jsp"] BANNER = r""" ____ _ ____ | _ \ ___ __| | _ \ __ _ _ _ ___ | |_) / _ \/ _` | |_) / _` | | | / __| | _ < __/ (_| | _ < (_| | |_| \__ \ |_| \_\___|\__,_|_| \_\__,_|\__, |___/ |___/ CVE-2025-31324 Scanner v1.1.0 """ @dataclass class ScanTarget: """Data class representing a scan target.""" hostname: str port: int use_ssl: bool @property def base_url(self) -> str: """Get the base URL for the target.""" protocol = "https" if self.use_ssl else "http" return f"{protocol}://{self.hostname}:{self.port}" def check_vulnerability(target: ScanTarget) -> bool: """ Check if the SAP system is vulnerable to CVE-2025-31324. Args: target: ScanTarget object with connection details Returns: bool: True if vulnerable, False otherwise """ url = f"{target.base_url}/developmentserver/metadatauploader" vulnerable = False try: response = requests.head(url, timeout=10, verify=False) if response.status_code == 200 and 'Set-Cookie' not in response.headers: logger.critical(f"SAP System at {url} appears to be vulnerable to CVE-2025-31324") vulnerable = True elif response.status_code == 404: logger.info(f"Visual Composer at {url} appears to not be installed or unavailable") else: logger.info(f"SAP system at {url} does not appear to be vulnerable to CVE-2025-31324") except requests.exceptions.RequestException as e: logger.error(f"Connection error at {url}: {e}") return vulnerable def detect_webshells(target: ScanTarget) -> List[str]: """ Test for known webshells in the SAP system. Args: target: ScanTarget object with connection details Returns: List[str]: List of detected webshell URLs """ detected_webshells = [] for webshell_filename in KNOWN_WEBSHELLS: url = f"{target.base_url}/irj/{webshell_filename}" try: response = requests.get(url, timeout=10, verify=False) if response.status_code == 200: logger.critical(f"Known webshell found at: {url}") detected_webshells.append(url) except requests.exceptions.RequestException as e: logger.error(f"Error connecting to {url} for webshell testing: {e}") if not detected_webshells: logger.info("No known webshells found") return detected_webshells def scan_system(target: ScanTarget) -> Tuple[bool, List[str]]: """ Complete scan of the specified SAP system. Args: target: ScanTarget object with connection details Returns: Tuple[bool, List[str]]: (is_vulnerable, detected_webshells) """ logger.info(f"Scanning SAP system at {target.base_url}") is_vulnerable = check_vulnerability(target) detected_webshells = detect_webshells(target) return is_vulnerable, detected_webshells def generate_report(target: ScanTarget, is_vulnerable: bool, detected_webshells: List[str]) -> str: """ Generate a text report of scan findings. Args: target: ScanTarget object with connection details is_vulnerable: Whether the system is vulnerable detected_webshells: List of detected webshell URLs Returns: str: Formatted report """ report = [ "=" * 60, f"SECURITY SCAN REPORT: {target.hostname}:{target.port}", "=" * 60, "", "VULNERABILITY ASSESSMENT:", f" CVE-2025-31324: {'VULNERABLE' if is_vulnerable else 'NOT VULNERABLE'}", "", "WEBSHELL DETECTION:", ] if detected_webshells: for webshell in detected_webshells: report.append(f" - {webshell}") else: report.append(" No known webshells detected") report.extend([ "", "RECOMMENDATIONS:", " 1. Apply SAP Security Note 3594142 immediately if vulnerable", " 2. Investigate any detected webshells", " 3. Consider a full system integrity check", "", "=" * 60 ]) return "\n".join(report) def main(): """Main function to parse arguments and run vulnerability checks.""" print(BANNER) parser = argparse.ArgumentParser( description=( "RedRays Scanner for Vulnerability CVE-2025-31324 (SAP Security " "Note 3594142) - CVSS 10 (Critical)\n\n" "This tool checks for the presence of the vulnerability and known " "webshells in the SAP system." ), epilog=( "DISCLAIMER: This tool is provided by RedRays as a contribution to " "the security and incident response community to aid in response to " "active exploitation of CVE-2025-31324. This tool is offered as-is " "with no warranty or liability." ), formatter_class=argparse.RawDescriptionHelpFormatter ) parser.add_argument( "hostname", help="Hostname or IP address of the SAP system" ) parser.add_argument( "port", type=int, help="Port number of the SAP system (e.g., 50000)" ) parser.add_argument( "--ssl", action="store_true", help="Use SSL/TLS for the connection" ) parser.add_argument( "--verbose", "-v", action="store_true", help="Enable verbose output" ) parser.add_argument( "--output", "-o", help="Save report to specified file" ) args = parser.parse_args() # Configure logging level if args.verbose: logger.setLevel(logging.DEBUG) # Create target object target = ScanTarget( hostname=args.hostname, port=args.port, use_ssl=args.ssl ) # Run scan is_vulnerable, detected_webshells = scan_system(target) # Generate and display report report = generate_report(target, is_vulnerable, detected_webshells) print("\n" + report) # Save report if requested if args.output: try: with open(args.output, 'w') as f: f.write(report) logger.info(f"Report saved to {args.output}") except IOError as e: logger.error(f"Failed to save report: {e}") # Set exit code based on findings if is_vulnerable or detected_webshells: sys.exit(1) else: sys.exit(0) if __name__ == "__main__": main()