Skip to content

Add HTTP TLS peer cert tracing via CertificateTrace (Phase 1)#21599

Open
Pushpenderrathore wants to merge 5 commits into
rapid7:masterfrom
Pushpenderrathore:feature/ssl-peer-cert-trace
Open

Add HTTP TLS peer cert tracing via CertificateTrace (Phase 1)#21599
Pushpenderrathore wants to merge 5 commits into
rapid7:masterfrom
Pushpenderrathore:feature/ssl-peer-cert-trace

Conversation

@Pushpenderrathore

@Pushpenderrathore Pushpenderrathore commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Extends CertificateTrace (the existing mixin for issued cert and CSR tracing) to also surface the server's TLS peer certificate when an HTTP module connects over HTTPS. No new operator-facing option is introduced -- operators use the same CertificateTrace enum (off/metadata/full) they already know.

Key design points:

  • New public method #certificate_peer_cert_trace(cert, host, port) added to Msf::Exploit::Remote::CertificateTrace
  • HttpClient calls it after send_recv when a raw peer cert is available
  • Dedup per session: DB-backed (ssl.peer_cert_seen note, update: :unique) with in-memory @_peer_cert_seen fallback when no DB
  • Plain HTTP connections (no peer_cert) are silently ignored
  • Removed the previous standalone SslPeerCertTrace mixin and its SSLPeerCertTrace datastore option

Test plan

Rebased onto latest master; the conflicts with the merged CSR trace work (#21580) were resolved (both certificate_peer_cert_trace and certificate_csr_trace kept as sibling methods). All checks below re-run after the merge.

Peer cert labeling: peer certs now print under a Peer Cert header (a label: keyword was added to CertificateTracePresenter#to_s_metadata/#to_s_full, default x.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 - 54 examples, 0 failures

Live test - HTTP peer cert trace against a real HTTPS server (auxiliary/scanner/http/http_version vs example.com:443, CertificateTrace=full):

[CertificateTrace] Peer Cert ----------------------------
  Subject    : /CN=example.com
  Issuer     : /C=US/O=SSL Corporation/CN=Cloudflare TLS Issuing ECC CA 3
  Not Before : 2026-05-31 21:39:12 UTC
  Not After  : 2026-08-29 21:41:26 UTC
  SHA-256    : beab14cf39678fda0ef1606eedb818c2298ba2cc7a00886e7dc2d2410f24cd35
  Serial     : 35428337808578903465180920265426569102
  Version    : v3
  Public Key : EC-256
  Identity   : example.com (Subject CN)
  • Dedup confirmed: one trace per distinct host:port (DB-backed ssl.peer_cert_seen note when a database is connected, in-memory fallback otherwise).
  • Plain HTTP connections (no peer cert) produce no output.

Phasing

This PR covers HTTP only (Phase 1). Follow-up PR covers LDAP over TLS and RDP (Phase 2), with Postgres and SMB over TLS pending TLS support in those mixins.

Depends on: #21198 (already merged)

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).
@Pushpenderrathore Pushpenderrathore changed the title Add SSLPeerCertTrace mixin for HTTP TLS peer certificate tracing Add HTTP TLS peer cert tracing via CertificateTrace (Phase 1) Jun 25, 2026
@Pushpenderrathore Pushpenderrathore marked this pull request as ready for review June 25, 2026 17:57
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 and
  certificate_csr_trace as sibling methods
- certificate_trace_spec.rb: keep both describe blocks as siblings

Presenter takes master's trace_separator version; peer cert method
uses it transparently via the presenter public API.
@Pushpenderrathore

Copy link
Copy Markdown
Contributor Author

Rebased/merged latest master and resolved the conflicts.

The conflicts were both-add collisions with the CSR trace work (#21580) that landed on master after this branch was cut. Both PRs inserted a new method into certificate_trace.rb and a new describe block into the spec at the same anchor, so git flagged them even though the additions are independent.

Resolution:

  • certificate_trace.rb: kept both certificate_peer_cert_trace and certificate_csr_trace as sibling methods.
  • certificate_trace_spec.rb: kept both describe blocks as siblings.
  • The presenter took master's trace_separator version; the peer cert method uses it transparently via the presenter API (no stale SEPARATOR reference).

Spec passes locally: 52 examples, 0 failures. Branch is now mergeable.

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.
@Pushpenderrathore

Pushpenderrathore commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the peer-cert labeling feedback: a server's TLS peer certificate was printing under the same [CertificateTrace] x.509 header as an issued/client cert, making the two indistinguishable.

Added an optional label: keyword to CertificateTracePresenter#to_s_metadata/#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. Specs updated to lock in both the new Peer Cert label and the unchanged x.509 issued-cert label.

Verified live with http_version vs example.com:443 in full mode:

[CertificateTrace] Peer Cert ----------------------------
  Subject    : /CN=example.com
  Issuer     : /C=US/O=SSL Corporation/CN=Cloudflare TLS Issuing ECC CA 3
  Not Before : 2026-05-31 21:39:12 UTC
  Not After  : 2026-08-29 21:41:26 UTC
  SHA-256    : beab14cf39678fda0ef1606eedb818c2298ba2cc7a00886e7dc2d2410f24cd35
  Serial     : 35428337808578903465180920265426569102
  Version    : v3
  Public Key : EC-256
  Identity   : example.com (Subject CN)
  SAN        : DNS:example.com, DNS:*.example.com
  EKU        : TLS Web Server Authentication
  Key Usage  : Digital Signature
  Extensions :
    basicConstraints : CA:FALSE
    authorityKeyIdentifier : 83:03:FD:E7:F6:F5:4A:4D:15:41:F4:ED:22:16:D3:32:0A:3E:CA:66
    authorityInfoAccess : CA Issuers - URI:http://i.cf-i.ssl.com/Cloudflare-TLS-I-E3.cer
OCSP - URI:http://o.cf-i.ssl.com
    certificatePolicies : Policy: 2.23.140.1.2.1
Policy: 1.3.6.1.4.1.38064.1.3.1.1
    crlDistributionPoints : Full Name:
  URI:http://c.cf-i.ssl.com/ae801ed1c55bb579d79208b0d772acfb8cc3a208.crl

    1.3.6.1.4.1.44363.44 : 05:00
    ct_precert_scts : 04:81:F2:00:F0:00:76:00:94:4E:43:87:FA:EC:C1:EF:81:F3:19:24:26:A8:18:65:01:C7:D3:5F:38:02:01:3F:72:67:7D:55:37:2E:19:D8:00:00:01:9E:80:03:31:89:00:00:04:03:00:47:30:45:02:21:00:81:BF:42:4C:D2:65:DA:8A:24:3D:79:FB:80:AE:49:37:3B:6B:7F:45:0D:FD:AB:B3:B7:A0:AB:58:0A:F3:94:85:02:20:76:7D:AE:B7:9A:E7:F8:05:B3:06:A6:7D:9B:55:4E:94:C2:E9:4F:F8:F2:D8:CF:76:22:CE:A2:B4:CC:BD:AD:1F:00:76:00:C8:A3:C4:7F:C7:B3:AD:B9:35:6B:01:3F:6A:7A:12:6D:E3:3A:4E:43:A5:C6:46:F9:97:AD:39:75:99:1D:CF:9A:00:00:01:9E:80:03:31:A0:00:00:04:03:00:47:30:45:02:21:00:A8:73:CA:F4:2D:58:A0:6B:3E:A9:BC:6F:2C:BF:4F:AE:06:AC:DF:F8:B3:CD:3F:1C:18:C6:D8:24:B9:57:4D:33:02:20:01:B2:BF:A9:13:49:21:69:63:21:5A:77:D0:F1:56:8E:63:3E:4A:7A:13:F3:44:A1:68:F2:4C:19:D6:ED:34:B3

Only the header line differs from an issued or client cert (Peer Cert vs x.509); every field comes from the same to_s_full path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

2 participants