Module: Msf::Exploit::Remote::Kerberos::Client::Pkinit
- Included in:
- Msf::Exploit::Remote::Kerberos::Client
- Defined in:
- lib/msf/core/exploit/remote/kerberos/client/pkinit.rb
Overview
Methods for interacting with Kerberos’s PKINIT extension for obtaining a TGT from a certificate
www.rfc-editor.org/rfc/rfc4556 learn.microsoft.com/en-us/openspecs/windows_protocols/ms-pkca/d0cf1763-3541-4008-a75f-a577fa5e8c5b
Instance Method Summary collapse
-
#build_dh ⇒ OpenSSL::PKey::DH, string
Builds a Diffie Helman object with parameters set up.
-
#build_pa_pk_as_req(pfx, dh, dh_nonce, request_body, opts) ⇒ Rex::Proto::Kerberos::Model::PreAuthDataEntry
Build a PreAuth data entry structure for negotiating a shared DH key with the server.
-
#calculate_shared_key(pa_pk_as_rep, dh, dh_nonce, etype) ⇒ String
Given all the Diffie Hellman parameters and response from the server, calculate the shared key using the steps described in www.rfc-editor.org/rfc/rfc4556#section-3.2.3.1.
-
#extract_user_and_realm(certificate, username, realm) ⇒ Array<String>
Extracts the user and realm from a certificate, deferring to the provided values if they are not nil.
-
#k_truncate(data, etype) ⇒ String
Transform a key into a key of a certain size, using the k-truncate algorithm described in www.rfc-editor.org/rfc/rfc4556#section-3.2.3.1.
-
#sign_auth_pack(auth_pack, key, certificate) ⇒ Rex::Proto::Kerberos::Model::Pkinit::ContentInfo
Calculate the cryptographic signatures over the AuthPack, and create the appropriate ASN.1-encoded structure, per www.rfc-editor.org/rfc/rfc4556#section-3.2.1.
Instance Method Details
#build_dh ⇒ OpenSSL::PKey::DH, string
Builds a Diffie Helman object with parameters set up
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/msf/core/exploit/remote/kerberos/client/pkinit.rb', line 19 def build_dh # When using the Diffie-Hellman key agreement method, implementations MUST support Oakley 1024-bit Modular # Exponential (MODP) well-known group 2 RFC2412 # Kerberos spec: https://www.rfc-editor.org/rfc/rfc4556 # Value: https://www.rfc-editor.org/rfc/rfc2412#appendix-E.2 prime_modulus = 0 # built 256 bits at a time prime_modulus |= 0xffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74 << (256 * 3) prime_modulus |= 0x020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437 << (256 * 2) prime_modulus |= 0x4fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed << (256 * 1) prime_modulus |= 0xee386bfb5a899fa5ae9f24117c4b1fe649286651ece65381ffffffffffffffff dh = OpenSSL::PKey::DH.new( OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(prime_modulus), OpenSSL::ASN1::Integer(2) ]).to_der ) if OpenSSL::PKey.respond_to?(:generate_key) # OpenSSL v3.x path # see: # * https://github.com/rapid7/metasploit-framework/pull/17308 # * https://ruby-doc.org/stdlib-3.1.0/libdoc/openssl/rdoc/OpenSSL/PKey/DH.html#method-i-generate_key-21 dh = OpenSSL::PKey.generate_key(dh) else dh.generate_key! end dh_nonce = SecureRandom.random_bytes(32) [dh, dh_nonce] end |
#build_pa_pk_as_req(pfx, dh, dh_nonce, request_body, opts) ⇒ Rex::Proto::Kerberos::Model::PreAuthDataEntry
Build a PreAuth data entry structure for negotiating a shared DH key with the server
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 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 |
# File 'lib/msf/core/exploit/remote/kerberos/client/pkinit.rb', line 191 def build_pa_pk_as_req(pfx, dh, dh_nonce, request_body, opts) certificate = pfx.certificate now_time = Time.now.utc now_ctime = now_time.round ctime = opts.fetch(:ctime) { now_ctime } cusec = opts.fetch(:cusec) { now_time&.usec || 0 } nonce = opts.fetch(:nonce) { rand(1 << 31) } data = request_body.encode checksum = Digest::SHA1.digest(data) pub_key_encoded = RASN1::Types::Integer.new(value: dh.pub_key.to_i).to_der auth_pack = Rex::Proto::Kerberos::Model::Pkinit::AuthPack.new( pk_authenticator: { cusec: cusec, ctime: ctime, nonce: nonce, pa_checksum: checksum }, client_public_value: { algorithm: { algorithm: Rex::Proto::Kerberos::Model::OID::DiffieHellman, # Diffie-Hellman parameters: Rex::Proto::Kerberos::Model::Pkinit::DomainParameters.new( p: dh.p.to_i, g: dh.g.to_i, q: 0 ) }, subject_public_key: pub_key_encoded }, client_dh_nonce: RASN1::Types::OctetString.new(value: dh_nonce) ) auth_pack[:client_public_value][:subject_public_key].bit_length = pub_key_encoded.length * 8 signed_auth_pack = sign_auth_pack(auth_pack, pfx.key, certificate) pa_as_req = Rex::Proto::Kerberos::Model::PreAuthPkAsReq.new pa_as_req.signed_auth_pack = signed_auth_pack Rex::Proto::Kerberos::Model::PreAuthDataEntry.new(type: Rex::Proto::Kerberos::Model::PreAuthType::PA_PK_AS_REQ, value: pa_as_req.to_der) end |
#calculate_shared_key(pa_pk_as_rep, dh, dh_nonce, etype) ⇒ String
Given all the Diffie Hellman parameters and response from the server, calculate the shared key using the steps described in www.rfc-editor.org/rfc/rfc4556#section-3.2.3.1
172 173 174 175 176 177 178 179 180 181 |
# File 'lib/msf/core/exploit/remote/kerberos/client/pkinit.rb', line 172 def calculate_shared_key(pa_pk_as_rep, dh, dh_nonce, etype) dh_rep_info = pa_pk_as_rep.dh_rep_info signed_data = dh_rep_info.signed_data dh_key_info = signed_data[:encap_content_info].econtent server_public_key = RASN1::Types::Integer.parse(dh_key_info[:subject_public_key].value).value shared_key = dh.compute_key(server_public_key.to_bn) server_nonce = pa_pk_as_rep[:server_dh_nonce].value full_key = shared_key + dh_nonce + server_nonce k_truncate(full_key, etype) end |
#extract_user_and_realm(certificate, username, realm) ⇒ Array<String>
Extracts the user and realm from a certificate, deferring to the provided values if they are not nil.
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 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 |
# File 'lib/msf/core/exploit/remote/kerberos/client/pkinit.rb', line 58 def extract_user_and_realm(certificate, username, realm) raise ArgumentError, 'Must provide username if providing realm' if username.nil? && !realm.nil? raise ArgumentError, 'Must provide realm if providing username' if realm.nil? && !username.nil? results = [] asn_san_seq = [] # MS's SAN extension isn't handled nicely by OpenSSL, so we need to read it ourselves # https://manas.tech/blog/2013/01/29/extracting-subject-alternative-name-from-microsoft-authentication-client-certificates/ certificate.extensions.select { |ext| ext.oid == 'subjectAltName' }.each do |san_extension| begin asn_san = OpenSSL::ASN1.decode(san_extension) asn_san_value = asn_san.value[1]&.value if asn_san_value.nil? raise ArgumentError, 'Invalid certificate provided: unable to decode SAN' end asn_san_seq = OpenSSL::ASN1.decode(asn_san_value) rescue OpenSSL::ASN1::ASN1Error raise ArgumentError, 'Invalid certificate provided: unable to decode SAN' end asn_san_seq.each do |san_entry| if san_entry.tag == 0 # x509.OtherName key = san_entry.value[0]&.value next if key != 'msUPN' # Principal Name principal = san_entry.value[1].value[0].value parts = principal.split('@') if parts.length == 1 user = principal domain = '' else user = parts[0..-2].join('@') domain = parts[-1] end elsif san_entry.tag == 2 # dNSName parts = san_entry.value.split('.') if parts.length == 1 user = san_entry domain = '' else user = parts[0] + '$' domain = parts[1..].join('.') end else next end results.append([user, domain]) end end unless realm.nil? # and also username, since it's both or neither unless results.map { |x| x.map(&:downcase) }.include?([username.downcase, realm.downcase]) # If we've been provided an override but can't find them in a SAN, give a warning print_warning("Warning: Provided principal and realm (#{username}@#{realm}) do not match entries in certificate:") results.each do |cert_username, cert_realm| print_warning(" * #{cert_username}@#{cert_realm}") end end # But hey, they've overridden it, so off we go return [username, realm] end # No override was provided, so hopefully we only extracted one value from the certificate if results.length == 1 return results[0] else raise ArgumentError, "Failed to retrieve Principal from certificate (contained #{results.length} SAN entries). Provide an override user and domain." end end |
#k_truncate(data, etype) ⇒ String
Transform a key into a key of a certain size, using the k-truncate algorithm described in www.rfc-editor.org/rfc/rfc4556#section-3.2.3.1
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/msf/core/exploit/remote/kerberos/client/pkinit.rb', line 138 def k_truncate(data, etype) if etype == Rex::Proto::Kerberos::Crypto::Encryption::AES256 keysize = 32 elsif etype == Rex::Proto::Kerberos::Crypto::Encryption::AES128 keysize = 16 else # This is unsupported per the spec raise Rex::Proto::Kerberos::Model::Error::KerberosEncryptionNotSupported.new("Unsupported DH Key exchange encryption type #{etype}", encryption_type: etype) end result = '' x = 0 while result.length < keysize digest = Digest::SHA1.digest(x.chr + data) if result.length + digest.length > keysize result += digest[0..(keysize - result.length - 1)] # Just take the first few bytes until we reach the desired length return result end result += digest x += 1 end result end |
#sign_auth_pack(auth_pack, key, certificate) ⇒ Rex::Proto::Kerberos::Model::Pkinit::ContentInfo
Calculate the cryptographic signatures over the AuthPack, and create the appropriate ASN.1-encoded structure, per www.rfc-editor.org/rfc/rfc4556#section-3.2.1
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
# File 'lib/msf/core/exploit/remote/kerberos/client/pkinit.rb', line 241 def sign_auth_pack(auth_pack, key, certificate) signer_info = Rex::Proto::Kerberos::Model::Pkinit::SignerInfo.new( version: 1, sid: { issuer: certificate.issuer, serial_number: certificate.serial.to_i }, digest_algorithm: { algorithm: Rex::Proto::Kerberos::Model::OID::SHA1 }, signed_attrs: [ { attribute_type: Rex::Proto::Kerberos::Model::OID::ContentType, attribute_values: [RASN1::Types::Any.new(value: RASN1::Types::ObjectId.new(value: Rex::Proto::Kerberos::Model::OID::PkinitAuthData))] }, { attribute_type: Rex::Proto::Kerberos::Model::OID::MessageDigest, attribute_values: [RASN1::Types::Any.new(value: RASN1::Types::OctetString.new(value: Digest::SHA1.digest(auth_pack.to_der)))] } ], signature_algorithm: { algorithm: Rex::Proto::Kerberos::Model::OID::RSAWithSHA1 } ) data = RASN1::Types::Set.new(value: signer_info[:signed_attrs].value).to_der signature = key.sign(OpenSSL::Digest.new('SHA1'), data) signer_info[:signature] = signature signed_data = Rex::Proto::Kerberos::Model::Pkinit::SignedData.new( version: 3, digest_algorithms: [ { algorithm: Rex::Proto::Kerberos::Model::OID::SHA1 } ], encap_content_info: { econtent_type: Rex::Proto::Kerberos::Model::OID::PkinitAuthData, econtent: auth_pack.to_der }, certificates: [{ openssl_certificate: certificate }], signer_infos: [signer_info] ) Rex::Proto::Kerberos::Model::Pkinit::ContentInfo.new( content_type: Rex::Proto::Kerberos::Model::OID::SignedData, signed_data: signed_data ) end |