postgres

package
v0.543.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 5, 2026 License: AGPL-3.0 Imports: 4 Imported by: 0

Documentation

Overview

Package postgres decodes PostgreSQL frontend / backend protocol v3 messages per the PostgreSQL documentation (Part VIII: "Frontend/Backend Protocol"). Runs on TCP/5432 default; alternate ports are common in container deployments but the wire format is identical.

Operationally, PostgreSQL is the **second-largest open-source database pentest target after MySQL**, deployed everywhere from cloud-managed Postgres (RDS / Aurora / Cloud SQL / Crunchy / Supabase / Neon / Timescale Cloud) to bare-metal installations to containerized side-cars. The wire format leaks:

  • **Cleartext username + database via StartupMessage** — the first message after TCP connect (no type byte) is a StartupMessage containing a sequence of null-terminated `key\0value\0` pairs. The `user` and `database` keys are sent in cleartext (UTF-8) — the canonical PostgreSQL credential disclosure on TCP/5432 without TLS. Common keys: `user`, `database`, `application_name` (often identifies the client: `psql`, `pgAdmin 4`, `DBeaver`, `pgcli`, `node-postgres`, `sqlmap`), `client_encoding`, `options`, `replication` (`true` indicates a streaming replication connection — the canonical "I want to copy all your data" connection).

  • **Authentication method enumeration via AuthenticationRequest** — the server's response to StartupMessage is a single `R` message carrying a 4-byte sub-type that tells the client which auth method to use. The sub-type values reveal the server's authentication policy:

  • `0` AuthenticationOk — authenticated already (typical for `trust` auth method — **NO AUTHENTICATION REQUIRED**, the canonical Postgres misconfiguration).

  • `2` KerberosV5 — legacy.

  • `3` CleartextPassword — **MITM-capturable!** Server wants the password in cleartext over the wire (typically used when SSL is required at a different layer, or for `password` auth method).

  • `5` MD5Password — **offline-crackable via hashcat mode 12**; the server sends a 4-byte salt, the client responds with `md5(md5(password||username)||salt)`.

  • `7` GSS — GSSAPI (Kerberos) negotiate.

  • `8` GSSContinue — GSSAPI continuation.

  • `9` SSPI — Windows SSPI negotiate.

  • `10` SASL — SASL negotiation, typically SCRAM-SHA-256 (modern Postgres default since v10).

  • `11` SASLContinue / `12` SASLFinal — SCRAM exchange continuation.

  • **Brute-force feedback via ErrorResponse SQLSTATE** — failed authentication returns an `E` (ErrorResponse) message with a series of field-tag-prefixed null- terminated strings. The `C` (SQLSTATE) field carries a 5-character code; key codes for pentest feedback:

  • `28P01` `invalid_password` — canonical wrong-password response; password-spray tools consume this directly.

  • `28000` `invalid_authorization_specification` — auth mechanism mismatch (no matching `pg_hba.conf` entry).

  • `3D000` `invalid_catalog_name` — database doesn't exist (database enumeration feedback).

  • `42501` `insufficient_privilege` — authenticated but not authorized.

  • `42P01` `undefined_table` — table doesn't exist (post-auth enumeration feedback).

  • `53300` `too_many_connections` — DoS detection signal.

  • `57P03` `cannot_connect_now` — server in shutdown / recovery.

  • **PostgreSQL version disclosure via ParameterStatus** — after AuthenticationOk, the server emits a series of `S` (ParameterStatus) messages carrying GUC parameters: `server_version` (canonical version-fingerprint for CVE selection — `15.4`, `16.1`, `17.2`), `server_encoding`, `client_encoding`, `application_name`, `is_superuser`, `session_authorization`, `DateStyle`, `IntervalStyle`, `TimeZone`, `integer_datetimes`, `standard_conforming _strings`. The decoder surfaces all observed ParameterStatus key/value pairs.

  • **SSL / GSS pre-handshake** — the SSLRequest magic (length=8 + payload=0x04D2162F = 80877103) and GSSENCRequest magic (length=8 + payload=0x04D21630 = 80877104) are sent BEFORE StartupMessage to probe whether the server supports TLS / GSSAPI encryption. The server replies with a single byte: `S` (accept) / `N` (decline); on `S` the client upgrades to TLS via the standard TLS handshake. The decoder surfaces `is_ssl_request` / `is_gss_request` / `is_cancel_request` boolean classifications.

Wrap-vs-native judgement

Native. The PostgreSQL frontend/backend protocol is
publicly documented (PostgreSQL Part VIII). The message
format is a simple type+length+body frame (with a
startup-message exception). StartupMessage parameters
are null-terminated key/value pairs.
AuthenticationRequest / ErrorResponse / ParameterStatus
bodies are simple TLV walks. Bind / Parse parameter
marshalling, RowDescription type-OID parsing, extended
query protocol multi-message flow, COPY streaming, TLS
handshake, and SASL inner-mechanism decode are out of
scope.

What this package covers

  • **Type+Length+Body frame parsing**: Type (1 byte) + Length (4 BE, includes self) + Body. Startup messages (StartupMessage / SSLRequest / GSSENCRequest / CancelRequest) have NO type byte and are discriminated by Length + ProtocolVersion magic.

  • **3-entry pre-startup magic name table**: `0x04D2162F` SSLRequest / `0x04D21630` GSSENCRequest / `0x04D21631` CancelRequest.

  • **15-entry frontend message type name table**: `B` Bind / `C` Close / `d` CopyData / `c` CopyDone / `f` CopyFail / `D` Describe / `E` Execute / `F` FunctionCall / `H` Flush / `P` Parse / `p` PasswordMessage / `Q` Query / `S` Sync / `X` Terminate.

  • **24-entry backend message type name table**: `R` Authentication / `K` BackendKeyData / `2` BindComplete / `3` CloseComplete / `C` CommandComplete / `G` CopyInResponse / `H` CopyOutResponse / `W` CopyBothResponse / `D` DataRow / `I` EmptyQueryResponse / `E` ErrorResponse / `V` FunctionCallResponse / `v` NegotiateProtocolVersion / `n` NoData / `N` NoticeResponse / `A` NotificationResponse / `t` ParameterDescription / `S` ParameterStatus / `1` ParseComplete / `s` PortalSuspended / `Z` ReadyForQuery / `T` RowDescription.

  • **StartupMessage body walker**: ProtocolVersion (4 BE) + sequence of null-terminated key/value strings terminated by an extra null byte. Surfaces all observed key/value pairs as `startup_params` map plus the canonical `user` / `database` / `application_name` / `client_encoding` keys as top-level fields.

  • **AuthenticationRequest body walker**: first 4 BE bytes are the sub-type. Surfaces `auth_subtype` + `auth_subtype_name`.

  • **11-entry AuthenticationRequest sub-type name table**: 0 `AuthenticationOk` (trust auth — no password!) / 2 `KerberosV5` / 3 `CleartextPassword` (MITM-capturable!) / 5 `MD5Password` (offline-crackable hashcat mode 12) / 7 `GSS` / 8 `GSSContinue` / 9 `SSPI` / 10 `SASL` (SCRAM- SHA-256 — modern hardened) / 11 `SASLContinue` / 12 `SASLFinal`.

  • **ErrorResponse / NoticeResponse body walker**: TLV- style (1-byte field tag + null-terminated value), terminated by 0-byte tag. Surfaces all observed field tags as `error_fields` map.

  • **18-entry ErrorResponse field-tag name table** (per PostgreSQL documentation §52.8): `S` Severity (localized) / `V` Severity (non-localized) / `C` SQLSTATE code / `M` Message / `D` Detail / `H` Hint / `P` Position (character offset) / `p` InternalPosition / `q` InternalQuery / `W` Where / `s` Schema / `t` Table / `c` Column / `d` DataType / `n` Constraint / `F` File / `L` Line / `R` Routine.

  • **8+ entry canonical SQLSTATE name table**: `28P01` `invalid_password` (canonical brute-force feedback!) / `28000` `invalid_authorization_specification` / `3D000` `invalid_catalog_name` (database enumeration feedback) / `42501` `insufficient_privilege` / `42P01` `undefined_table` (post-auth enumeration feedback) / `53300` `too_many_connections` (DoS signal) / `57P03` `cannot_connect_now` (server shutdown/recovery) / `00000` `successful_completion`.

  • **ParameterStatus body walker**: two null-terminated strings (parameter name + value). Surfaces all observed parameters; canonical pentest-interesting: `server_version` (CVE-selection fingerprint), `is_superuser`, `session_authorization`.

What this package does NOT cover (deliberately out of scope)

  • **Bind / Parse parameter marshalling** — Bind carries N parameter values with per-parameter format codes (text/binary) and length-prefixed binary blobs. Each binary parameter is typed via the prior Parse message's type OID list. Out of scope here.
  • **RowDescription type-OID parsing** — RowDescription enumerates result columns with type OID + type modifier
  • format code; mapping type OIDs to PostgreSQL type names (1700+ system catalogue OIDs) is out of scope.
  • **DataRow body parsing** — DataRow carries N column values; each is either NULL (length=-1) or length-prefixed binary. Out of scope.
  • **Extended query protocol multi-message flow** — Parse / Bind / Describe / Execute / Sync form a multi- message exchange; the decoder reports each individually but does not track exchange state.
  • **COPY streaming** — CopyInResponse + CopyData + CopyDone / CopyFail carry raw COPY data (typically CSV or PostgreSQL binary format); out of scope.
  • **TLS / GSSAPI encryption handshake** — after SSLRequest + `S` reply, the connection upgrades to TLS; subsequent PostgreSQL messages ride inside the TLS record layer. Handle TLS strip first.
  • **SASL inner-mechanism decode** — for SASL auth_subtype 10 / 11 / 12, the SCRAM-SHA-256 client-first / server- first / client-final / server-final messages are base64-blob payloads in the auth body; per-step SCRAM decode is out of scope.
  • **NOTIFY / LISTEN payload semantics** — NotificationResponse (`A`) carries a channel name + a payload string; the channel namespace is application- defined.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Result

type Result struct {
	TotalBytes int `json:"total_bytes"`

	// Pre-startup magic classification
	IsSSLRequest    bool `json:"is_ssl_request"`
	IsGSSRequest    bool `json:"is_gss_request"`
	IsCancelRequest bool `json:"is_cancel_request"`

	MessageType     string `json:"message_type,omitempty"`
	MessageTypeName string `json:"message_type_name,omitempty"`
	Direction       string `json:"direction,omitempty"`
	Length          int    `json:"length"`

	// StartupMessage
	ProtocolVersionMajor int               `json:"protocol_version_major,omitempty"`
	ProtocolVersionMinor int               `json:"protocol_version_minor,omitempty"`
	StartupParams        map[string]string `json:"startup_params,omitempty"`
	User                 string            `json:"user,omitempty"`
	Database             string            `json:"database,omitempty"`
	ApplicationName      string            `json:"application_name,omitempty"`
	ClientEncoding       string            `json:"client_encoding,omitempty"`

	// AuthenticationRequest
	AuthSubtype     int    `json:"auth_subtype,omitempty"`
	AuthSubtypeName string `json:"auth_subtype_name,omitempty"`

	// ErrorResponse / NoticeResponse
	ErrorFields   map[string]string `json:"error_fields,omitempty"`
	SQLState      string            `json:"sqlstate,omitempty"`
	SQLStateName  string            `json:"sqlstate_name,omitempty"`
	ErrorSeverity string            `json:"error_severity,omitempty"`
	ErrorMessage  string            `json:"error_message,omitempty"`

	// ParameterStatus
	ParameterName  string `json:"parameter_name,omitempty"`
	ParameterValue string `json:"parameter_value,omitempty"`
}

Result is the structured decode of a PostgreSQL message.

func Decode

func Decode(hexStr string) (*Result, error)

Decode parses a PostgreSQL message from a hex string.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL