Module: Msf::Exploit::Remote::CertificateTrace

Included in:
HttpClient, Kerberos::Client, LDAP, MsIcpr, Postgres, RDP
Defined in:
lib/msf/core/exploit/remote/certificate_trace.rb

Overview

Shared helpers for tracing X.509 certificates encountered during a module run (for example the client certificate presented for PKINIT, or a certificate issued via AD CS / MS-ICPR). Registers the CertificateTrace and CertificateTraceColors advanced options and dispatches formatted, optionally colorized output through Msf::Trace::CertificateTracePresenter.

Include this mixin in any module or mixin that wants certificate tracing, then call #certificate_trace with the certificate of interest.

Instance Method Summary collapse

Instance Method Details

#certificate_csr_trace(csr, attributes = {}) ⇒ void

This method returns an undefined value.

Dispatches a certificate signing request (CSR) trace at the configured verbosity level, mirroring #certificate_trace for the request side of an enrollment (e.g. AD CS / MS-ICPR). Intended to be called with the CSR and enrollment attributes just before the request is submitted to the CA.

Parameters:

  • csr (OpenSSL::X509::Request, String, #to_der)
  • attributes (Hash) (defaults to: {})

    enrollment request attributes (e.g. 'CertificateTemplate', 'SAN') as returned by CertRequest#create_csr



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/msf/core/exploit/remote/certificate_trace.rb', line 154

def certificate_csr_trace(csr, attributes = {})
  return unless certificate_trace_enabled?

  mode = datastore['CertificateTrace']
  presenter = Msf::Trace::CertificateTracePresenter.new

  output = case mode
           when 'metadata'
             presenter.(csr)
           when 'full'
             presenter.to_s_csr_full(csr, attributes)
           else
             vprint_warning("Unknown CertificateTrace mode: #{mode}")
             nil
           end
  return unless output

  print_line(certificate_trace_colorize(output))
end

#certificate_peer_cert_trace(cert, host, port, chain: nil) ⇒ void

This method returns an undefined value.

Surfaces the server TLS peer certificate after a TLS handshake. Uses CertificateTrace and CertificateTraceColors so peer cert output is consistent with issued-certificate and CSR output. Deduplicates per host:port using the database when active, falling back to in-memory dedup within a single module run when the database is not connected.

Parameters:

  • cert (OpenSSL::X509::Certificate, String)

    peer certificate or DER bytes

  • host (String)

    remote hostname or IP

  • port (Integer)

    remote port

  • chain (Array<OpenSSL::X509::Certificate>, nil) (defaults to: nil)

    the certificates the server presented (leaf first); intermediates/roots are printed in full mode



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/msf/core/exploit/remote/certificate_trace.rb', line 77

def certificate_peer_cert_trace(cert, host, port, chain: nil)
  return unless certificate_trace_enabled?
  return if cert.nil?

  @_peer_cert_seen ||= {}
  key = "#{host}:#{port}"
  return if @_peer_cert_seen[key]

  db_active = respond_to?(:framework) && framework&.db&.active
  if db_active
    # Dedup across runs using a note keyed by "host:port" in its own data.
    # Match on the note's serialized data column (already loaded with the
    # row) rather than walking the host/service associations, which would
    # issue a separate query per note (N+1).
    already_noted = framework.db.notes(ntype: 'ssl.peer_cert_seen').any? do |n|
      n.data.is_a?(Hash) && n.data['endpoint'] == key
    end
    if already_noted
      @_peer_cert_seen[key] = true
      return
    end
  end

  mode = datastore['CertificateTrace']
  presenter = Msf::Trace::CertificateTracePresenter.new(cert)
  # Label peer certs distinctly from issued (x.509) and CSR output so a
  # server's TLS cert is not confused with a client/issued certificate.
  output = case mode
           when 'metadata'
             presenter.(label: 'Peer Cert')
           when 'full'
             presenter.to_s_full(label: 'Peer Cert')
           else
             vprint_warning("Unknown CertificateTrace mode: #{mode}")
             nil
           end
  return unless output

  # Only record the endpoint as seen once we have actually produced output,
  # so an unknown mode or a render failure does not permanently suppress
  # future tracing for this host:port.
  @_peer_cert_seen[key] = true
  if db_active
    framework.db.report_note(
      host: host, port: port, proto: 'tcp',
      ntype: 'ssl.peer_cert_seen',
      data: { 'endpoint' => key },
      update: :unique
    )
  end

  print_line(certificate_trace_colorize(output))

  return unless mode == 'full'

  # When chain is provided, print each intermediate / root CA cert that the
  # server included in the handshake. chain[0] is the leaf (already printed),
  # so we start from chain[1].
  chain_certs = Array(chain).drop(1)
  chain_certs.each_with_index do |chain_cert, idx|
    chain_presenter = Msf::Trace::CertificateTracePresenter.new(chain_cert)
    chain_output = chain_presenter.to_s_full(label: "Chain #{idx + 1}/#{chain_certs.length}")
    next unless chain_output

    print_line(certificate_trace_colorize(chain_output))
  end
end

#certificate_trace(cert) ⇒ void

This method returns an undefined value.

Dispatches a certificate trace at the configured verbosity level. Builds a presenter, routes to the appropriate to_s_* method, applies the configured color, then prints via the module instance.

Color convention mirrors HttpTraceColors: the second color in the “req/resp” pair is used for certificate output since a cert is always a received (response-side) artifact.

Parameters:

  • cert (OpenSSL::X509::Certificate, OpenSSL::PKCS12, String)


45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/msf/core/exploit/remote/certificate_trace.rb', line 45

def certificate_trace(cert)
  return unless certificate_trace_enabled?

  mode = datastore['CertificateTrace']
  presenter = Msf::Trace::CertificateTracePresenter.new(cert)

  output = case mode
           when 'metadata'
             presenter.
           when 'full'
             presenter.to_s_full
           else
             vprint_warning("Unknown CertificateTrace mode: #{mode}")
             nil
           end
  return unless output

  print_line(certificate_trace_colorize(output))
end

#certificate_trace_enabled?Boolean

Returns true if CertificateTracePresenter is loaded and tracing is enabled.

Returns:

  • (Boolean)


27
28
29
30
31
32
33
# File 'lib/msf/core/exploit/remote/certificate_trace.rb', line 27

def certificate_trace_enabled?
  return false unless defined?(Msf::Trace::CertificateTracePresenter)
  return false unless respond_to?(:datastore) && datastore

  mode = datastore['CertificateTrace']
  mode && mode != 'off'
end

#initialize(info = {}) ⇒ Object



13
14
15
16
17
18
19
20
21
22
# File 'lib/msf/core/exploit/remote/certificate_trace.rb', line 13

def initialize(info = {})
  super

  register_advanced_options(
    [
      OptEnum.new('CertificateTrace', [false, 'Certificate trace verbosity level', 'off', ['off', 'metadata', 'full']]),
      OptString.new('CertificateTraceColors', [false, 'Certificate trace color (e.g. red/blu, unset to disable)', 'red/blu'])
    ], Msf::Exploit::Remote::CertificateTrace
  )
end