Lucene search
+L

Enumerate PostgreSQL Foreign Connections

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

Enumerate PostgreSQL postgres_fdw servers, user mappings, foreign tables, and dblink sessions.

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


class Module(Post, PostgreSQLClient):

	__info__ = {
		"name": "Enumerate PostgreSQL Foreign Connections",
		"description": (
			"List postgres_fdw servers, user mappings, foreign tables, "
			"dblink sessions, and connection strings in function source"
		),
		"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': []},
	},
	}

	search_source = OptBool(True, "Search function bodies for host/user/password strings", False)

	def run(self):
		try:
			print_info("=" * 80)
			self._enum_postgres_fdw()
			self._enum_foreign_tables()
			self._enum_dblink()
			if self.search_source:
				self._search_connection_strings()

			print_info("=" * 80)
			print_success("Foreign connection enumeration completed")
			return True
		except ProcedureError:
			raise
		except Exception as exc:
			raise ProcedureError(
				FailureType.Unknown, f"Foreign connection enumeration failed: {exc}"
			)

	def _enum_postgres_fdw(self):
		print_status("postgres_fdw servers")
		if not self.extension_installed("postgres_fdw"):
			print_info("  postgres_fdw not installed in current database")
			return

		try:
			servers = self.execute_query(
				"SELECT s.srvname, w.fdwname, s.srvoptions, s.srvowner::regrole::text "
				"FROM pg_foreign_server s "
				"JOIN pg_foreign_data_wrapper w ON w.oid = s.srvfdw "
				"ORDER BY s.srvname;"
			)
		except Exception as exc:
			print_warning(f"  Cannot read foreign servers: {exc}")
			return

		if not servers:
			print_info("  No foreign servers defined")
			return

		print_success(f"  Found {len(servers)} foreign server(s):")
		for srvname, fdwname, srvoptions, owner in servers:
			opts = self._format_options(srvoptions)
			print_info(f"    {srvname} (fdw={fdwname}, owner={owner})")
			if opts:
				print_info(f"      {opts}")

		try:
			mappings = self.execute_query(
				"SELECT COALESCE(r.rolname, 'PUBLIC') AS local_role, "
				"s.srvname, u.umoptions "
				"FROM pg_user_mapping u "
				"JOIN pg_foreign_server s ON s.oid = u.umserver "
				"LEFT JOIN pg_roles r ON r.oid = u.umuser "
				"ORDER BY s.srvname, local_role;"
			)
		except Exception as exc:
			print_warning(f"  Cannot read user mappings: {exc}")
			return

		if mappings:
			print_info("  User mappings:")
			for local_role, srvname, umoptions in mappings:
				opts = self._format_options(umoptions)
				print_info(f"    {local_role} -> {srvname}: {opts or '(no options)'}")

	def _enum_foreign_tables(self):
		print_info("-" * 80)
		print_status("Foreign tables")
		try:
			rows = self.execute_query(
				"SELECT n.nspname, c.relname, s.srvname, "
				"pg_get_userbyid(c.relowner) AS owner "
				"FROM pg_class c "
				"JOIN pg_namespace n ON n.oid = c.relnamespace "
				"JOIN pg_foreign_table ft ON ft.ftrelid = c.oid "
				"JOIN pg_foreign_server s ON s.oid = ft.ftserver "
				"ORDER BY n.nspname, c.relname "
				"LIMIT 50;"
			)
		except Exception as exc:
			print_warning(f"  Cannot list foreign tables: {exc}")
			return

		if not rows:
			print_info("  No foreign tables in current database")
			return

		print_success(f"  Found {len(rows)} foreign table(s):")
		for schema, table, srvname, owner in rows:
			print_info(f"    {schema}.{table} -> server {srvname} (owner={owner})")

	def _enum_dblink(self):
		print_info("-" * 80)
		print_status("dblink")
		if not self.extension_installed("dblink"):
			print_info("  dblink not installed in current database")
			return

		if not self.function_exists("dblink_get_connections"):
			print_warning("  dblink_get_connections() not available")
			return

		try:
			rows = self.execute_query("SELECT dblink_get_connections();", fetch_all=False)
			conns = rows[0] if rows else None
		except Exception as exc:
			print_warning(f"  dblink_get_connections failed: {exc}")
			return

		if not conns:
			print_info("  No active dblink connections")
			return

		print_success(f"  Active dblink connection(s): {conns}")

	def _search_connection_strings(self):
		print_info("-" * 80)
		print_status("Connection strings in function/trigger source")
		try:
			rows = self.execute_query(
				"SELECT n.nspname, p.proname, "
				"LEFT(p.prosrc, 300) AS src_preview "
				"FROM pg_proc p "
				"JOIN pg_namespace n ON n.oid = p.pronamespace "
				"WHERE p.prosrc IS NOT NULL "
				"AND ("
				"  p.prosrc ILIKE '%dblink%' "
				"  OR p.prosrc ILIKE '%host=%' "
				"  OR p.prosrc ILIKE '%password=%' "
				"  OR p.prosrc ILIKE '%postgres_fdw%' "
				"  OR p.prosrc ILIKE '%dbname=%' "
				") "
				"AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\' "
				"ORDER BY n.nspname, p.proname "
				"LIMIT 30;"
			)
		except Exception as exc:
			print_warning(f"  Source search failed: {exc}")
			return

		if not rows:
			print_info("  No matching function source found")
			return

		print_warning(f"  Found {len(rows)} function(s) with connection hints:")
		for schema, name, preview in rows:
			snippet = (preview or "").replace("\n", " ").strip()
			if len(snippet) > 160:
				snippet = snippet[:157] + "..."
			print_info(f"    {schema}.{name}(): {snippet}")

	@staticmethod
	def _format_options(options) -> str:
		if not options:
			return ""
		if isinstance(options, (list, tuple)):
			return ", ".join(str(o) for o in options)
		return str(options)

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
4