Add Postgres TLS support and peer cert tracing via CertificateTrace (Phase 3)#21608
Add Postgres TLS support and peer cert tracing via CertificateTrace (Phase 3)#21608Pushpenderrathore wants to merge 15 commits into
Conversation
Introduces Msf::Exploit::Remote::SslPeerCertTrace with an SSLPeerCertTrace enum datastore option (off/metadata/full). HttpClient includes the mixin and calls ssl_peer_cert_trace after each send_recv; the helper reads conn.peer_cert, deduplicates per host:port, and formats the server certificate through the existing CertificateTracePresenter.
Removes the standalone SSLPeerCertTrace mixin and SSLPeerCertTrace datastore option. Peer SSL certificate tracing now lives in CertificateTrace as #certificate_peer_cert_trace, reusing the existing CertificateTrace enum and CertificateTraceColors option so all cert output (issued, CSR, peer) is controlled by a single operator-facing option. Deduplication now uses the database when active (ssl.peer_cert_seen note per host:port, update: :unique) so the same server certificate is not reprinted across module runs. Falls back to in-memory dedup when no DB is connected. HttpClient is updated to include CertificateTrace instead of SslPeerCertTrace and calls certificate_peer_cert_trace after send_recv. The ssl_peer_cert_trace mixin file and its spec are removed; the 20 new peer cert examples are added to certificate_trace_spec.rb (46 examples, 0 failures).
Includes CertificateTrace in Exploit::Remote::LDAP so LDAP over TLS connections surface the server certificate through the same CertificateTrace option and CertificateTraceColors that HTTP uses. No new operator-facing option is introduced. ldap_open is updated for both paths: - keep_open: false (Rex::Proto::LDAP::Client.open) -- wraps the caller block and reads peer_cert from the @open_connection socket before yielding to the caller. - keep_open: true (Rex::Proto::LDAP::Client._open) -- reads peer_cert from ldap.socket after the connection is established and returned. A private _ldap_trace_peer_cert helper centralises the socket guard and the certificate_peer_cert_trace call so both paths share the same nil-safe check. Plain LDAP connections (no TLS, no peer_cert) are silently ignored. Dedup and DB recording are handled by certificate_peer_cert_trace in the CertificateTrace mixin. Spec: 14 examples (7 new for peer cert tracing), 0 failures.
Includes CertificateTrace in Exploit::Remote::RDP so the server TLS certificate is surfaced when an operator sets CertificateTrace to metadata or full. The hook is in swap_sock_plain_to_ssl, immediately after ssl.connect succeeds -- the OpenSSL socket has a valid peer_cert at that point and before it is wrapped into the Rex SslTcp layer. No new operator-facing option is introduced; RDP re-uses the same CertificateTrace enum as HTTP and LDAP. A nil peer_cert (e.g. when the server sends no certificate) is a silent no-op. Spec: 3 new examples covering TLS cert printed, off mode silent, and nil peer_cert silent.
Includes CertificateTrace in Exploit::Remote::Postgres so the server TLS certificate is surfaced when connecting via SSL (datastore SSL=true). The SSL option was already registered by the included Exploit::Remote::Tcp mixin but was never threaded through to Connection.new. This commit passes it as the sixth positional argument, activating the starttls path in postgres-pr. After a successful login the leaf certificate is read from postgres_conn.conn.peer_cert and forwarded to certificate_peer_cert_trace. A nil peer_cert (plain TCP connection with SSL=false) is a silent no-op. Callers can override with opts[:ssl] to force TLS regardless of the datastore, matching the existing pattern for other opts keys in postgres_login. Spec: 7 new examples covering TLS cert printed, off mode silent, ssl arg threading, plain connection no-op, opts[:ssl] override, and failed login no-op.
|
Thanks for your pull request! As part of our landing process, we manually verify that all modules work as expected. We've added the |
When CertificateTrace=full and the server sends a chain during the TLS handshake, the intermediate and root CA certs are now printed after the leaf cert. Each chain cert uses a "Chain N/M" position header in place of the standard [CertificateTrace] separator so operators can visually distinguish chain depth. certificate_peer_cert_trace gains an optional chain: keyword argument (array of OpenSSL::X509::Certificate). chain[0] is the peer cert already printed; chain[1..] are the chain certs. In metadata mode the chain parameter is silently ignored so verbosity stays minimal. Protocol hooks updated to pass peer_cert_chain: - HTTP (HttpClient): c.conn.peer_cert_chain - LDAP (_ldap_trace_peer_cert): socket.peer_cert_chain via respond_to? guard - RDP (swap_sock_plain_to_ssl): ssl.peer_cert_chain (OpenSSL::SSL::SSLSocket) - Postgres (postgres_login): pg_socket.peer_cert_chain via respond_to? guard Spec: 5 new chain examples in certificate_trace_spec; LDAP and RDP specs updated to stub peer_cert_chain on their socket doubles. 75 examples, 0 failures across all Phase 1-4 specs.
Lab test: Postgres TLS cert tracingTested against a local PostgreSQL 14 instance (SSL enabled, self-signed cert) using metadata mode: full mode (extended cert fields + chain): Notes:
|
Plain (non-SSL) connections return a raw Socket which does not have peer_cert, causing a NoMethodError. Match the pattern used in the LDAP and HTTP hooks.
LoginScanner::LDAP includes Exploit::Remote::LDAP so it calls _ldap_trace_peer_cert via ldap_open. LoginScanner does not have datastore, rhost, or rport. Guard with certificate_trace_enabled? early so we return before evaluating rhost/rport arguments when tracing is not available (e.g. in scanner context).
LoginScanner::LDAP includes Exploit::Remote::LDAP so it calls _ldap_trace_peer_cert via ldap_open. LoginScanner does not have datastore, so calling rhost (which calls datastore['RHOST']) raises NoMethodError, caught as UNABLE_TO_CONNECT in the scanner. Guard with certificate_trace_enabled? at the top so we return before evaluating rhost/rport when tracing is unavailable.
certificate_trace already tested the invalid-cert no-raise case; add the same coverage for certificate_peer_cert_trace.
Resolve conflicts from the merged CSR trace work (rapid7#21580): - certificate_trace.rb: keep both certificate_peer_cert_trace (with chain support) and certificate_csr_trace as sibling methods - certificate_trace_spec.rb: keep both describe blocks as siblings Fix stale constant: the chain-printing path referenced CertificateTracePresenter::SEPARATOR, which rapid7#21580 removed in favor of the trace_separator(label) helper. Replace the leading x.509 separator line of each chain cert's full output via a first-line substitution so the Chain N/M header still renders. ldap/rdp/postgres and their specs merged cleanly.
|
Merged latest master and resolved the conflicts. One fix worth flagging for review: The chain-printing path in Fix: instead of substituting on the deleted constant, I replace the leading chain_output = chain_output.sub(/\A.*\n/, "#{chain_header}\n")This keeps the Verification: full spec suite passes (81 examples, 0 failures), including the |
Addresses review feedback that a server's TLS peer certificate printed under the same '[CertificateTrace] x.509' header as an issued/client certificate, making the two indistinguishable. Add an optional label: keyword to CertificateTracePresenter#to_s_metadata and #to_s_full (default 'x.509', backward compatible). certificate_peer_cert_trace now renders peer certs under a 'Peer Cert' header. Issued-cert and CSR output are unchanged.
With to_s_full now accepting a label: keyword, the chain-printing path passes label: "Chain N/M" directly to the presenter instead of building a header string and substituting it into to_s_full's output. This removes the manual first-line sub, drops the hardcoded dash width (headers now pad to the presenter's separator width for any chain length), and no longer depends on any presenter separator internals.
|
Addressed the peer-cert labeling feedback, and used the same mechanism to simplify the chain rendering. Added a Verified live in All specs green (certificate_trace 59, ldap 31, rdp 10, postgres 14). |
Summary
Extends
CertificateTracepeer cert tracing to PostgreSQL over TLS, and fixes a long-standing gap where the Postgres mixin accepted anSSLdatastore option (registered via the includedExploit::Remote::Tcp) but never passed it through to the underlying connection.Changes:
Exploit::Remote::Postgresnow includesCertificateTracepostgres_loginextractssslfromopts[:ssl]ordatastore['SSL']and passes it as the 6th argument toConnection.new, activating thestarttlspath inpostgres-prwhen SSL is requestedcertificate_peer_cert_traceis called withpostgres_conn.conn.peer_cert-- guarded byrespond_to?(:peer_cert)so plain TCP connections are a silent no-opopts[:ssl]: true/falseindependent of the datastoreNo new options introduced. The
SSLbool was already registered byExploit::Remote::Tcp; this PR just wires it through.Test plan
Rebased onto latest master; the conflicts with the merged CSR trace work (#21580) were resolved.
Chain header rendering: chain members are printed by passing
label: "Chain N/M"toCertificateTracePresenter#to_s_full. This relies on the samelabel:keyword described below (defaultx.509), so there is no dependency on presenter separator internals and the header pads to the presenter's separator width for any chain length. (An earlier revision referencedCertificateTracePresenter::SEPARATOR, a constant #21580 removed; that path is gone.)Peer cert labeling: peer certs now print under a
Peer Certheader (alabel:keyword was added toCertificateTracePresenter#to_s_metadata/#to_s_full, defaultx.509), so a server's TLS certificate is no longer visually indistinguishable from an issued/client certificate. Issued-cert (x.509) and CSR output are unchanged.Unit specs (this branch, post-merge):
bundle exec rspec spec/lib/msf/core/exploit/remote/certificate_trace_spec.rb- 59 examples, 0 failures (includes 5cert chain (full mode)examples)bundle exec rspec spec/lib/msf/core/exploit/remote/ldap_spec.rb- 31 examples, 0 failuresbundle exec rspec spec/lib/msf/core/exploit/remote/rdp_spec.rb- 10 examples, 0 failuresbundle exec rspec spec/lib/msf/core/exploit/remote/postgres_spec.rb- 14 examples, 0 failuresLive test - Postgres peer cert trace over TLS (
auxiliary/admin/postgres/postgres_sqlvs a local PostgreSQL 14 withssl=on,SSL true,CertificateTrace=full). The SQL query succeeds and the cert traces:Live test - full cert chain rendering. Postgres was configured to present a CA-signed server cert (leaf + issuing CA). In
fullmode the intermediate is printed under aChain N/Mheader (leaf underPeer Cert), confirming the chain rendering at runtime:SSL false, no peer cert) produce no output.Notes