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.