From 49c8c8a40df922f24d0f87ab8e70bad38a9a13f6 Mon Sep 17 00:00:00 2001 From: Ashley Donaldson Date: Fri, 20 Dec 2024 12:17:16 +1100 Subject: [PATCH] Refactor CMS data structures used in pkinit functionality --- .../exploit/remote/kerberos/client/pkinit.rb | 10 +- lib/msf/core/exploit/remote/ms_icpr.rb | 10 +- lib/rex/proto/crypto_asn1/cms.rb | 45 +++++- lib/rex/proto/crypto_asn1/o_i_ds.rb | 3 + lib/rex/proto/crypto_asn1/x509.rb | 8 + lib/rex/proto/kerberos/model/pkinit.rb | 145 +----------------- .../kerberos/model/pre_auth_pk_as_rep.rb | 2 +- .../kerberos/model/pre_auth_pk_as_req.rb | 2 +- .../msf/core/exploit/remote/ms_icpr_spec.rb | 4 +- .../auxiliary/admin/dcerpc/icpr_cert_spec.rb | 4 +- 10 files changed, 72 insertions(+), 161 deletions(-) diff --git a/lib/msf/core/exploit/remote/kerberos/client/pkinit.rb b/lib/msf/core/exploit/remote/kerberos/client/pkinit.rb index 58ddfcfa1372..fd9176fe465e 100644 --- a/lib/msf/core/exploit/remote/kerberos/client/pkinit.rb +++ b/lib/msf/core/exploit/remote/kerberos/client/pkinit.rb @@ -238,9 +238,9 @@ def build_pa_pk_as_req(pfx, dh, dh_nonce, request_body, opts) # @param auth_pack [Rex::Proto::Kerberos::Model::Pkinit::AuthPack] The AuthPack to sign # @param key [OpenSSL::PKey] The private key to digitally sign the data # @param dh [OpenSSL::X509::Certificate] The certificate associated with the private key - # @return [Rex::Proto::Kerberos::Model::Pkinit::ContentInfo] The signed AuthPack + # @return [Rex::Proto::CryptoAsn1::Cms::ContentInfo] The signed AuthPack def sign_auth_pack(auth_pack, key, certificate) - signer_info = Rex::Proto::Kerberos::Model::Pkinit::SignerInfo.new( + signer_info = Rex::Proto::CryptoAsn1::Cms::SignerInfo.new( version: 1, sid: { issuer: certificate.issuer, @@ -268,7 +268,7 @@ def sign_auth_pack(auth_pack, key, certificate) signer_info[:signature] = signature - signed_data = Rex::Proto::Kerberos::Model::Pkinit::SignedData.new( + signed_data = Rex::Proto::CryptoAsn1::Cms::SignedData.new( version: 3, digest_algorithms: [ { @@ -283,9 +283,9 @@ def sign_auth_pack(auth_pack, key, certificate) signer_infos: [signer_info] ) - Rex::Proto::Kerberos::Model::Pkinit::ContentInfo.new( + Rex::Proto::CryptoAsn1::Cms::ContentInfo.new( content_type: Rex::Proto::Kerberos::Model::OID::SignedData, - signed_data: signed_data + data: signed_data ) end end diff --git a/lib/msf/core/exploit/remote/ms_icpr.rb b/lib/msf/core/exploit/remote/ms_icpr.rb index 925baa796dd0..ba2d404dd896 100644 --- a/lib/msf/core/exploit/remote/ms_icpr.rb +++ b/lib/msf/core/exploit/remote/ms_icpr.rb @@ -299,7 +299,7 @@ def build_csr(cn:, private_key:, dns: nil, msext_sid: nil, msext_upn: nil, algor # @param [OpenSSL::X509::Certificate] cert The public key to use for signing the request. # @param [OpenSSL::PKey::RSA] key The private key to use for signing the request. # @param [String] algorithm The digest algorithm to use. - # @return [Rex::Proto::Kerberos::Model::Pkinit::ContentInfo] The signed request content. + # @return [Rex::Proto::CryptoAsn1::Cms::ContentInfo] The signed request content. def build_on_behalf_of(csr:, on_behalf_of:, cert:, key:, algorithm: 'SHA256') # algorithm needs to be one that OpenSSL supports, but we also need the OID constants defined digest = OpenSSL::Digest.new(algorithm) @@ -309,7 +309,7 @@ def build_on_behalf_of(csr:, on_behalf_of:, cert:, key:, algorithm: 'SHA256') digest_oid = Rex::Proto::Kerberos::Model::OID.const_get(digest.name) - signer_info = Rex::Proto::Kerberos::Model::Pkinit::SignerInfo.new( + signer_info = Rex::Proto::CryptoAsn1::Cms::SignerInfo.new( version: 1, sid: { issuer: cert.issuer, @@ -342,7 +342,7 @@ def build_on_behalf_of(csr:, on_behalf_of:, cert:, key:, algorithm: 'SHA256') signer_info[:signature] = signature - signed_data = Rex::Proto::Kerberos::Model::Pkinit::SignedData.new( + signed_data = Rex::Proto::CryptoAsn1::Cms::SignedData.new( version: 3, digest_algorithms: [ { @@ -357,9 +357,9 @@ def build_on_behalf_of(csr:, on_behalf_of:, cert:, key:, algorithm: 'SHA256') signer_infos: [signer_info] ) - Rex::Proto::Kerberos::Model::Pkinit::ContentInfo.new( + Rex::Proto::CryptoAsn1::Cms::ContentInfo.new( content_type: Rex::Proto::Kerberos::Model::OID::SignedData, - signed_data: signed_data + data: signed_data ) end diff --git a/lib/rex/proto/crypto_asn1/cms.rb b/lib/rex/proto/crypto_asn1/cms.rb index 5c69b5b6441f..f1a1674a4422 100755 --- a/lib/rex/proto/crypto_asn1/cms.rb +++ b/lib/rex/proto/crypto_asn1/cms.rb @@ -236,10 +236,47 @@ class EnvelopedData < RASN1::Model ] end + class SignerInfo < RASN1::Model + sequence :signer_info, + content: [integer(:version), + model(:sid, IssuerAndSerialNumber), + model(:digest_algorithm, AlgorithmIdentifier), + set_of(:signed_attrs, Attribute, implicit: 0, optional: true), + model(:signature_algorithm, AlgorithmIdentifier), + octet_string(:signature), + ] + end + + class EncapsulatedContentInfo < RASN1::Model + sequence :encapsulated_content_info, + content: [objectid(:econtent_type), + octet_string(:econtent, explicit: 0, constructed: true, optional: true) + ] + + def econtent + if self[:econtent_type].value == Rex::Proto::CryptoAsn1::OIDs::OID_DIFFIE_HELLMAN_KEYDATA.value + Rex::Proto::Kerberos::Model::Pkinit::KdcDhKeyInfo.parse(self[:econtent].value) + elsif self[:econtent_type].value == Rex::Proto::Kerberos::Model::OID::PkinitAuthData + Rex::Proto::Kerberos::Model::Pkinit::AuthPack.parse(self[:econtent].value) + end + end + end + + class SignedData < RASN1::Model + sequence :signed_data, + explicit: 0, constructed: true, + content: [integer(:version), + set_of(:digest_algorithms, AlgorithmIdentifier), + model(:encap_content_info, EncapsulatedContentInfo), + set_of(:certificates, Certificate, implicit: 0, optional: true), + # CRLs - not implemented + set_of(:signer_infos, SignerInfo) + ] + end + class ContentInfo < RASN1::Model sequence :content_info, content: [model(:content_type, ContentType), - # In our case, expected to be EnvelopedData any(:data) ] @@ -248,5 +285,11 @@ def enveloped_data EnvelopedData.parse(self[:data].value) end end + + def signed_data + if self[:content_type].value == Rex::Proto::CryptoAsn1::OIDs::OID_CMS_SIGNED_DATA.value + SignedData.parse(self[:data].value) + end + end end end \ No newline at end of file diff --git a/lib/rex/proto/crypto_asn1/o_i_ds.rb b/lib/rex/proto/crypto_asn1/o_i_ds.rb index 1a1941626533..264d3f08ee70 100644 --- a/lib/rex/proto/crypto_asn1/o_i_ds.rb +++ b/lib/rex/proto/crypto_asn1/o_i_ds.rb @@ -61,8 +61,11 @@ class OIDs OID_PKIX_KP_TIMESTAMP_SIGNING = ObjectId.new('1.3.6.1.5.5.7.3.8', name: 'OID_PKIX_KP_TIMESTAMP_SIGNING', label: 'Time Stamping') OID_ROOT_LIST_SIGNER = ObjectId.new('1.3.6.1.4.1.311.10.3.9', name: 'OID_ROOT_LIST_SIGNER', label: 'Root List Signer') OID_WHQL_CRYPTO = ObjectId.new('1.3.6.1.4.1.311.10.3.5', name: 'OID_WHQL_CRYPTO', label: 'Windows Hardware Driver Verification') + OID_DIFFIE_HELLMAN_KEYDATA = ObjectId.new('1.3.6.1.5.2.3.2', name: 'OID_DIFFIE_HELLMAN_KEYDATA', label: 'Diffie Hellman Key Data') + OID_CMS_ENVELOPED_DATA = ObjectId.new('1.2.840.113549.1.7.3', name: 'OID_CMS_ENVELOPED_DATA', label: 'PKCS#7 CMS Enveloped Data') + OID_CMS_SIGNED_DATA = ObjectId.new('1.2.840.113549.1.7.2', name: 'OID_CMS_SIGNED_DATA', label: 'CMS Signed Data') OID_DES_EDE3_CBC = ObjectId.new('1.2.840.113549.3.7', name: 'OID_DES_EDE_CBC', label: 'Triple DES encryption in CBC mode') OID_AES256_CBC = ObjectId.new('2.16.840.1.101.3.4.1.42', name: 'OID_AES256_CBC', label: 'AES256 in CBC mode') diff --git a/lib/rex/proto/crypto_asn1/x509.rb b/lib/rex/proto/crypto_asn1/x509.rb index c1dcf889c1ca..a4ffdd28c7a3 100644 --- a/lib/rex/proto/crypto_asn1/x509.rb +++ b/lib/rex/proto/crypto_asn1/x509.rb @@ -101,6 +101,14 @@ class PrivateDomainName < RASN1::Model ] end + class SubjectPublicKeyInfo < RASN1::Model + sequence :subject_public_key_info, + explicit: 1, constructed: true, optional: true, + content: [model(:algorithm, Rex::Proto::CryptoAsn1::Cms::AlgorithmIdentifier), + bit_string(:subject_public_key) + ] + end + class BuiltinDomainDefinedAttribute < RASN1::Model sequence :BuiltinDomainDefinedAttribute, content: [ printable_string(:type), diff --git a/lib/rex/proto/kerberos/model/pkinit.rb b/lib/rex/proto/kerberos/model/pkinit.rb index 1ab6fcca351d..819e136ef07a 100644 --- a/lib/rex/proto/kerberos/model/pkinit.rb +++ b/lib/rex/proto/kerberos/model/pkinit.rb @@ -8,78 +8,6 @@ module Model # Contains the models for PKINIT-related ASN1 structures # These use the RASN1 library to define the types module Pkinit - class AlgorithmIdentifier < RASN1::Model - sequence :algorithm_identifier, - content: [objectid(:algorithm), - any(:parameters, optional: true) - ] - end - - class Attribute < RASN1::Model - sequence :attribute, - content: [objectid(:attribute_type), - set_of(:attribute_values, RASN1::Types::Any) - ] - end - - class AttributeTypeAndValue < RASN1::Model - sequence :attribute_type_and_value, - content: [objectid(:attribute_type), - any(:attribute_value) - ] - end - - class Certificate - # Rather than specifying the entire structure of a certificate, we pass this off - # to OpenSSL, effectively providing an interface between RASN and OpenSSL. - - attr_accessor :options - - def initialize(options={}) - self.options = options - end - - def to_der - self.options[:openssl_certificate]&.to_der || '' - end - - # RASN1 Glue method - Say if DER can be built (not default value, not optional without value, has a value) - # @return [Boolean] - # @since 0.12 - def can_build? - !to_der.empty? - end - - # RASN1 Glue method - def primitive? - false - end - - # RASN1 Glue method - def value - options[:openssl_certificate] - end - - def parse!(str, ber: false) - self.options[:openssl_certificate] = OpenSSL::X509::Certificate.new(str) - to_der.length - end - end - - class ContentInfo < RASN1::Model - sequence :content_info, - content: [objectid(:content_type), - # In our case, expected to be SignedData - any(:signed_data) - ] - - def signed_data - if self[:content_type].value == '1.2.840.113549.1.7.2' - SignedData.parse(self[:signed_data].value) - end - end - end - class DomainParameters < RASN1::Model sequence :domain_parameters, content: [integer(:p), @@ -90,46 +18,6 @@ class DomainParameters < RASN1::Model ] end - class EncapsulatedContentInfo < RASN1::Model - sequence :encapsulated_content_info, - content: [objectid(:econtent_type), - octet_string(:econtent, explicit: 0, constructed: true, optional: true) - ] - - def econtent - if self[:econtent_type].value == '1.3.6.1.5.2.3.2' - KdcDhKeyInfo.parse(self[:econtent].value) - elsif self[:econtent_type].value == '1.3.6.1.5.2.3.1' - AuthPack.parse(self[:econtent].value) - end - end - end - - class Name - # Rather than specifying the entire structure of a name, we pass this off - # to OpenSSL, effectively providing an interface between RASN and OpenSSL. - attr_accessor :value - - def initialize(options={}) - end - - def parse!(str, ber: false) - self.value = OpenSSL::X509::Name.new(str) - to_der.length - end - - def to_der - self.value.to_der - end - end - - class IssuerAndSerialNumber < RASN1::Model - sequence :signer_identifier, - content: [model(:issuer, Name), - integer(:serial_number) - ] - end - class KdcDhKeyInfo < RASN1::Model sequence :kdc_dh_key_info, content: [bit_string(:subject_public_key, explicit: 0, constructed: true), @@ -148,41 +36,10 @@ class PkAuthenticator < RASN1::Model ] end - class SignerInfo < RASN1::Model - sequence :signer_info, - content: [integer(:version), - model(:sid, IssuerAndSerialNumber), - model(:digest_algorithm, AlgorithmIdentifier), - set_of(:signed_attrs, Attribute, implicit: 0, optional: true), - model(:signature_algorithm, AlgorithmIdentifier), - octet_string(:signature), - ] - end - - class SignedData < RASN1::Model - sequence :signed_data, - explicit: 0, constructed: true, - content: [integer(:version), - set_of(:digest_algorithms, AlgorithmIdentifier), - model(:encap_content_info, EncapsulatedContentInfo), - set_of(:certificates, Certificate, implicit: 0, optional: true), - # CRLs - not implemented - set_of(:signer_infos, SignerInfo) - ] - end - - class SubjectPublicKeyInfo < RASN1::Model - sequence :subject_public_key_info, - explicit: 1, constructed: true, optional: true, - content: [model(:algorithm, AlgorithmIdentifier), - bit_string(:subject_public_key) - ] - end - class AuthPack < RASN1::Model sequence :auth_pack, content: [model(:pk_authenticator, PkAuthenticator), - model(:client_public_value, SubjectPublicKeyInfo), + model(:client_public_value, Rex::Proto::CryptoAsn1::X509::SubjectPublicKeyInfo), octet_string(:client_dh_nonce, implicit: 3, constructed: true, optional: true) ] end diff --git a/lib/rex/proto/kerberos/model/pre_auth_pk_as_rep.rb b/lib/rex/proto/kerberos/model/pre_auth_pk_as_rep.rb index a34ece3572bb..aaa940377a58 100644 --- a/lib/rex/proto/kerberos/model/pre_auth_pk_as_rep.rb +++ b/lib/rex/proto/kerberos/model/pre_auth_pk_as_rep.rb @@ -14,7 +14,7 @@ class PreAuthPkAsRep < RASN1::Model ] def dh_rep_info - Rex::Proto::Kerberos::Model::Pkinit::ContentInfo.parse(self[:dh_rep_info].value) + Rex::Proto::CryptoAsn1::Cms::ContentInfo.parse(self[:dh_rep_info].value) end def self.decode(data) diff --git a/lib/rex/proto/kerberos/model/pre_auth_pk_as_req.rb b/lib/rex/proto/kerberos/model/pre_auth_pk_as_req.rb index 53781cd0d0c6..093ad269ad1b 100644 --- a/lib/rex/proto/kerberos/model/pre_auth_pk_as_req.rb +++ b/lib/rex/proto/kerberos/model/pre_auth_pk_as_req.rb @@ -15,7 +15,7 @@ class PreAuthPkAsReq < RASN1::Model def parse!(der, ber: false) res = super(der, ber: ber) - self.signed_auth_pack = Rex::Proto::Kerberos::Model::Pkinit::ContentInfo.parse(self[:signed_auth_pack].value) + self.signed_auth_pack = Rex::Proto::CryptoAsn1::Cms::ContentInfo.parse(self[:signed_auth_pack].value) res end diff --git a/spec/lib/msf/core/exploit/remote/ms_icpr_spec.rb b/spec/lib/msf/core/exploit/remote/ms_icpr_spec.rb index 1fe9316ff647..7a316f6e5c3a 100644 --- a/spec/lib/msf/core/exploit/remote/ms_icpr_spec.rb +++ b/spec/lib/msf/core/exploit/remote/ms_icpr_spec.rb @@ -108,7 +108,7 @@ end let(:content_info) do - Rex::Proto::Kerberos::Model::Pkinit::ContentInfo.parse( + Rex::Proto::CryptoAsn1::Cms::ContentInfo.parse( "\x30\x82\x0b\x71\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x07\x02\xa0\x82\x0b" \ "\x62\x30\x82\x0b\x5e\x02\x01\x03\x31\x0d\x30\x0b\x06\x09\x60\x86\x48\x01" \ "\x65\x03\x04\x02\x01\x30\x82\x02\x6c\x06\x07\x2b\x06\x01\x05\x02\x03\x01" \ @@ -328,7 +328,7 @@ end it 'return a ContentInfo object' do - expect(result).to be_a(Rex::Proto::Kerberos::Model::Pkinit::ContentInfo) + expect(result).to be_a(Rex::Proto::CryptoAsn1::Cms::ContentInfo) end it 'should respond to #to_der' do diff --git a/spec/modules/auxiliary/admin/dcerpc/icpr_cert_spec.rb b/spec/modules/auxiliary/admin/dcerpc/icpr_cert_spec.rb index 5ab10f9b5875..337fa680d8a4 100644 --- a/spec/modules/auxiliary/admin/dcerpc/icpr_cert_spec.rb +++ b/spec/modules/auxiliary/admin/dcerpc/icpr_cert_spec.rb @@ -109,7 +109,7 @@ end let(:content_info) do - Rex::Proto::Kerberos::Model::Pkinit::ContentInfo.parse( + Rex::Proto::CryptoAsn1::Cms::ContentInfo.parse( "\x30\x82\x0b\x71\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x07\x02\xa0\x82\x0b" \ "\x62\x30\x82\x0b\x5e\x02\x01\x03\x31\x0d\x30\x0b\x06\x09\x60\x86\x48\x01" \ "\x65\x03\x04\x02\x01\x30\x82\x02\x6c\x06\x07\x2b\x06\x01\x05\x02\x03\x01" \ @@ -330,7 +330,7 @@ end it 'return a ContentInfo object' do - expect(result).to be_a(Rex::Proto::Kerberos::Model::Pkinit::ContentInfo) + expect(result).to be_a(Rex::Proto::CryptoAsn1::Cms::ContentInfo) end it 'should respond to #to_der' do