Module: Msf::Exploit::Remote::Kerberos::Client
- Includes:
- ApRequest, AsRequest, AsResponse, Base, Pac, Pkinit, TgsRequest, TgsResponse
- Defined in:
- lib/msf/core/exploit/remote/kerberos/client.rb,
lib/msf/core/exploit/remote/kerberos/client/pac.rb,
lib/msf/core/exploit/remote/kerberos/client/base.rb,
lib/msf/core/exploit/remote/kerberos/client/pkinit.rb,
lib/msf/core/exploit/remote/kerberos/client/ap_request.rb,
lib/msf/core/exploit/remote/kerberos/client/as_request.rb,
lib/msf/core/exploit/remote/kerberos/client/as_response.rb,
lib/msf/core/exploit/remote/kerberos/client/tgs_request.rb,
lib/msf/core/exploit/remote/kerberos/client/tgs_response.rb
Defined Under Namespace
Modules: ApRequest, AsRequest, AsResponse, Base, Pac, Pkinit, TgsRequest, TgsResponse
Constant Summary collapse
- TOK_ID_KRB_AP_REQ =
"\x01\x00"
- TOK_ID_KRB_AP_REP =
"\x02\x00"
- TOK_ID_KRB_ERROR =
"\x03\x00"
- NEG_TOKEN_ACCEPT_COMPLETED =
0
- NEG_TOKEN_ACCEPT_INCOMPLETE =
1
- NEG_TOKEN_REJECT =
2
- NEG_TOKEN_REQUEST_MIC =
3
Constants included from ApRequest
ApRequest::AP_MUTUAL_REQUIRED, ApRequest::AP_USE_SESSION_KEY
Instance Attribute Summary collapse
-
#client ⇒ Rex::Proto::Kerberos::Client
The kerberos client.
-
#kerberos_client ⇒ Object
Returns the value of attribute kerberos_client.
Instance Method Summary collapse
-
#cleanup ⇒ Object
Performs cleanup as necessary, disconnecting the Kerberos client if it’s still established.
-
#connect(opts = {}) ⇒ Rex::Proto::Kerberos::Client
Creates a kerberos connection.
-
#disconnect(kerb_client = kerberos_client) ⇒ Object
Disconnects the Kerberos client.
- #framework_module ⇒ Object protected
- #initialize(info = {}) ⇒ Object
-
#peer ⇒ String
Returns the kdc peer.
-
#proxies ⇒ String?
Returns the configured proxy list.
-
#rhost ⇒ String
Returns the target host.
-
#rport ⇒ Integer
Returns the remote port.
-
#select_cipher(client_etypes, server_etypeinfos_entries) ⇒ Rex::Proto::Kerberos::Model::EtypeInfo
Select a cipher that both the server and client support, preferencing ours in order.
-
#send_request_as(opts = {}) ⇒ Rex::Proto::Kerberos::Model::KdcResponse
Sends a kerberos AS request and reads the response.
-
#send_request_tgs(opts = {}) ⇒ Rex::Proto::Kerberos::Model::KdcResponse
Sends a kerberos TGS request and reads the response.
-
#send_request_tgt(options = {}) ⇒ Msf::Exploit::Remote::Kerberos::Model::TgtResponse
Sends the required kerberos AS requests for a kerberos Ticket Granting Ticket.
-
#send_request_tgt_pkinit(options = {}) ⇒ Msf::Exploit::Remote::Kerberos::Model::TgtResponse
Send a TGT request using PKINIT (certificate) authentication.
-
#timeout ⇒ Integer
Returns the TCP timeout.
Methods included from Pkinit
#build_dh, #build_pa_pk_as_req, #calculate_shared_key, #extract_user_and_realm, #k_truncate, #sign_auth_pack
Methods included from Pac
#build_empty_auth_data, #build_pa_pac_request, #build_pac, #build_pac_authorization_data
Methods included from TgsResponse
#decrypt_kdc_tgs_rep_enc_part, #extract_kerb_creds
Methods included from TgsRequest
#build_ap_req, #build_authenticator, #build_enc_auth_data, #build_pa_for_user, #build_subkey, #build_tgs_body_checksum, #build_tgs_request, #build_tgs_request_body
Methods included from AsResponse
#decrypt_kdc_as_rep_enc_part, #extract_logon_time, #extract_session_key, #format_as_rep_to_john_hash
Methods included from AsRequest
#build_as_pa_time_stamp, #build_as_request, #build_as_request_body
Methods included from ApRequest
#build_service_ap_request, #encode_gss_kerberos_ap_request, #encode_gss_spnego_ap_request
Methods included from Base
#build_client_name, #build_server_name
Instance Attribute Details
#client ⇒ Rex::Proto::Kerberos::Client
Returns The kerberos client.
30 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 30 attr_accessor :kerberos_client |
#kerberos_client ⇒ Object
Returns the value of attribute kerberos_client.
30 31 32 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 30 def kerberos_client @kerberos_client end |
Instance Method Details
#cleanup ⇒ Object
Performs cleanup as necessary, disconnecting the Kerberos client if it’s still established.
118 119 120 121 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 118 def cleanup super disconnect end |
#connect(opts = {}) ⇒ Rex::Proto::Kerberos::Client
Creates a kerberos connection
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 85 def connect(opts={}) kerb_client = Rex::Proto::Kerberos::Client.new( host: opts[:rhost] || rhost, port: (opts[:rport] || rport).to_i, proxies: opts[:proxies] || proxies, timeout: (opts[:timeout] || timeout).to_i, context: { 'Msf' => framework, 'MsfExploit' => framework_module, }, protocol: 'tcp' ) disconnect if kerberos_client self.kerberos_client = kerb_client kerb_client end |
#disconnect(kerb_client = kerberos_client) ⇒ Object
Disconnects the Kerberos client
108 109 110 111 112 113 114 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 108 def disconnect(kerb_client = kerberos_client) kerb_client.close if kerb_client if kerb_client == kerberos_client self.kerberos_client = nil end end |
#framework_module ⇒ Object (protected)
433 434 435 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 433 def framework_module self end |
#initialize(info = {}) ⇒ Object
32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 32 def initialize(info = {}) super ( [ Opt::RHOST, Opt::RPORT(88), OptInt.new('Timeout', [true, 'The TCP timeout to establish Kerberos connection and read data', 10]) ], self.class ) end |
#peer ⇒ String
Returns the kdc peer
68 69 70 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 68 def peer "#{rhost}:#{rport}" end |
#proxies ⇒ String?
Returns the configured proxy list
75 76 77 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 75 def proxies datastore['Proxies'] end |
#rhost ⇒ String
Returns the target host
47 48 49 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 47 def rhost datastore['RHOST'] end |
#rport ⇒ Integer
Returns the remote port
54 55 56 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 54 def rport datastore['RPORT'] end |
#select_cipher(client_etypes, server_etypeinfos_entries) ⇒ Rex::Proto::Kerberos::Model::EtypeInfo
Select a cipher that both the server and client support, preferencing ours in order. This may just be the default behaviour on Windows, but let’s be sure about it.
158 159 160 161 162 163 164 165 166 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 158 def select_cipher(client_etypes, server_etypeinfos_entries) client_etypes.each do |client_etype| server_etypeinfos_entries.each do |server_etypeinfo2_entry| if server_etypeinfo2_entry.etype == client_etype return server_etypeinfo2_entry end end end end |
#send_request_as(opts = {}) ⇒ Rex::Proto::Kerberos::Model::KdcResponse
Sends a kerberos AS request and reads the response
129 130 131 132 133 134 135 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 129 def send_request_as(opts = {}) connect(opts) req = opts.fetch(:req) { build_as_request(opts) } res = kerberos_client.send_recv(req) disconnect res end |
#send_request_tgs(opts = {}) ⇒ Rex::Proto::Kerberos::Model::KdcResponse
Sends a kerberos TGS request and reads the response
143 144 145 146 147 148 149 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 143 def send_request_tgs(opts = {}) connect(opts) req = opts.fetch(:req) { build_tgs_request(opts) } res = kerberos_client.send_recv(req) disconnect res end |
#send_request_tgt(options = {}) ⇒ Msf::Exploit::Remote::Kerberos::Model::TgtResponse
Sends the required kerberos AS requests for a kerberos Ticket Granting Ticket
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 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 246 def send_request_tgt( = {}) realm = [:realm] server_name = [:server_name] || "krbtgt/#{realm}" client_name = [:client_name] client_name = client_name.dup.force_encoding('utf-8') if client_name password = [:password] password = password.dup.force_encoding('utf-8') if password key = [:key] request_pac = .fetch(:request_pac, true) = .fetch(:options) { 0x50800000 } # Forwardable, Proxiable, Renewable # First stage: Send an initial AS-REQ request, used to exchange supported encryption methods. # The server may respond with a ticket granting ticket (TGT) immediately, # or the client may require preauthentication, and a second AS-REQ is required now = Time.now.utc expiry_time = now + 1.day offered_etypes = [:offered_etypes] || Rex::Proto::Kerberos::Crypto::Encryption::DefaultOfferedEtypes if !password && key && offered_etypes.length != 1 raise ArgumentError.new('Exactly one etype must be specified in :offered_etypes when a key is is defined without a password') end initial_as_req = build_as_request( pa_data: [ build_pa_pac_request(pac_request_value: request_pac) ], body: build_as_request_body( client_name: client_name, server_name: server_name, realm: realm, etype: offered_etypes, # Specify nil to ensure the KDC uses the current time for the desired starttime of the requested ticket from: nil, till: expiry_time, rtime: expiry_time, options: ), ) req_opts = {req: initial_as_req} req_opts.update() initial_as_res = send_request_as(req_opts) # If we receive an AS_REP response immediately, no-preauthentication was required and we can return immediately if initial_as_res.msg_type == Rex::Proto::Kerberos::Model::AS_REP pa_data = initial_as_res.pa_data if password.nil? && key.nil? decrypted_part = nil krb_enc_key = nil else etype_entries = pa_data.find {|entry| entry.type == Rex::Proto::Kerberos::Model::PreAuthType::PA_ETYPE_INFO2} # Let's try to check the password server_ciphers = etype_entries.decoded_value # Should only have one etype etype_info = server_ciphers.etype_info2_entries[0] if password enc_key, salt = get_enc_key_from_password(password, etype_info) elsif key enc_key = key end begin decrypted_part = decrypt_kdc_as_rep_enc_part( initial_as_res, enc_key, ) krb_enc_key = { enctype: etype_info.etype, key: enc_key, salt: salt } rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError # It's as if it were an invalid password decrypted_part = nil krb_enc_key = nil end end return Msf::Exploit::Remote::Kerberos::Model::TgtResponse.new( as_rep: initial_as_res, preauth_required: false, decrypted_part: decrypted_part, krb_enc_key: krb_enc_key ) end # If we're just AS_REP Roasting, we can't go any further raise ::Rex::Proto::Kerberos::Model::Error::KerberosError.new(res: initial_as_res) if password.nil? && key.nil? # Verify error codes. Anything other than the server requiring an additional preauth request is considered a failure. if initial_as_res.msg_type == Rex::Proto::Kerberos::Model::KRB_ERROR && initial_as_res.error_code != Rex::Proto::Kerberos::Model::Error::ErrorCodes::KDC_ERR_PREAUTH_REQUIRED if initial_as_res.error_code == Rex::Proto::Kerberos::Model::Error::ErrorCodes::KDC_ERR_ETYPE_NOSUPP raise Rex::Proto::Kerberos::Model::Error::KerberosEncryptionNotSupported.new(encryption_type: offered_etypes) end raise ::Rex::Proto::Kerberos::Model::Error::KerberosError.new(res: initial_as_res) end # Second stage: Send an additional AS-REQ request with preauthentication provided # Note that Clock skew issues may be raised at this point pa_data = initial_as_res.e_data_as_pa_data etype_entries = pa_data.find {|entry| entry.type == Rex::Proto::Kerberos::Model::PreAuthType::PA_ETYPE_INFO2} # No etypes specified - how are we supposed to negotiate ciphers? raise Rex::Proto::Kerberos::Model::Error::KerberosEncryptionNotSupported.new(encryption_type: offered_etypes) unless etype_entries server_ciphers = etype_entries.decoded_value remaining_server_ciphers_to_attempt = server_ciphers.etype_info2_entries.select do |server_etypeinfo2_entry| offered_etypes.include?(server_etypeinfo2_entry.etype) end if remaining_server_ciphers_to_attempt.empty? raise Rex::Proto::Kerberos::Model::Error::KerberosEncryptionNotSupported.new(encryption_type: offered_etypes) end # Attempt to use the available ciphers; In some scenarios they can fail due to GPO configurations # So we need to iterate until a success - or there's no more ciphers available while remaining_server_ciphers_to_attempt.any? selected_etypeinfo = select_cipher(offered_etypes, remaining_server_ciphers_to_attempt) selected_etype = selected_etypeinfo.etype if password enc_key, salt = get_enc_key_from_password(password, selected_etypeinfo) elsif key raise ArgumentError.new('Encryption key provided without one offered encryption type') unless [:offered_etypes]&.length == 1 enc_key = key end preauth_as_req = build_as_request( pa_data: [ build_as_pa_time_stamp(key: enc_key, etype: selected_etype), build_pa_pac_request(pac_request_value: request_pac) ], body: build_as_request_body( client_name: client_name, server_name: server_name, realm: realm, key: enc_key, etype: remaining_server_ciphers_to_attempt.map(&:etype), # Specify nil to ensure the KDC uses the current time for the desired starttime of the requested ticket from: nil, till: expiry_time, rtime: expiry_time ) ) req_opts = {req: preauth_as_req} req_opts.update() preauth_as_res = send_request_as(req_opts) # If we've succeeded - break out of trying ciphers break if preauth_as_res.msg_type == Rex::Proto::Kerberos::Model::AS_REP # If we've hit a cipher not supported error, try the next cipher if there's more to try is_etype_not_supported_error = preauth_as_res.msg_type == Rex::Proto::Kerberos::Model::KRB_ERROR && preauth_as_res.error_code == Rex::Proto::Kerberos::Model::Error::ErrorCodes::KDC_ERR_ETYPE_NOSUPP if is_etype_not_supported_error remaining_server_ciphers_to_attempt -= [selected_etypeinfo] next if remaining_server_ciphers_to_attempt.any? end # Unexpected server response raise ::Rex::Proto::Kerberos::Model::Error::KerberosError.new(res: preauth_as_res) end Msf::Exploit::Remote::Kerberos::Model::TgtResponse.new( as_rep: preauth_as_res, preauth_required: true, krb_enc_key: { enctype: selected_etype, key: enc_key, salt: salt }, decrypted_part: decrypt_kdc_as_rep_enc_part( preauth_as_res, enc_key, ) ) end |
#send_request_tgt_pkinit(options = {}) ⇒ Msf::Exploit::Remote::Kerberos::Model::TgtResponse
Send a TGT request using PKINIT (certificate) authentication
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 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 233 234 235 236 237 238 239 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 178 def send_request_tgt_pkinit( = {}) pfx = [:pfx] request_pac = .fetch(:request_pac, true) realm = [:realm] server_name = [:server_name] || "krbtgt/#{realm}" client_name = [:client_name] client_name = client_name.dup.force_encoding('utf-8') if client_name = .fetch(:options) { 0x50800000 } # Forwardable, Proxiable, Renewable # The diffie hellman client parameters dh, dh_nonce = build_dh now = Time.now.utc expiry_time = now + 1.day offered_etypes = [:offered_etypes] || Rex::Proto::Kerberos::Crypto::Encryption::PkinitEtypes request_body = build_as_request_body( client_name: client_name, server_name: server_name, realm: realm, etype: offered_etypes, # Specify nil to ensure the KDC uses the current time for the desired starttime of the requested ticket from: nil, till: expiry_time, rtime: expiry_time, options: ) as_req = build_as_request( pa_data: [ build_pa_pac_request(pac_request_value: request_pac), build_pa_pk_as_req(pfx, dh, dh_nonce, request_body, ) ], body: request_body ) # Send the request [:req] = as_req as_res = send_request_as() if as_res.msg_type == Rex::Proto::Kerberos::Model::AS_REP entry = as_res.pa_data.find {|entry| entry.type == Rex::Proto::Kerberos::Model::PreAuthType::PA_PK_AS_REP} raise ::Rex::Proto::Kerberos::Model::Error::KerberosError.new('No PKINIT PreAuth data received') if entry.nil? # Should never happen from a spec-compliant server pa_pk_as_rep = entry.decoded_value key = calculate_shared_key(pa_pk_as_rep, dh, dh_nonce, as_res.enc_part.etype) return Msf::Exploit::Remote::Kerberos::Model::TgtResponse.new( as_rep: as_res, preauth_required: true, decrypted_part: decrypt_kdc_as_rep_enc_part(as_res, key), krb_enc_key: { enctype: as_res.enc_part.etype, key: key } ) elsif as_res.msg_type == Rex::Proto::Kerberos::Model::KRB_ERROR raise ::Rex::Proto::Kerberos::Model::Error::KerberosError.new(res: as_res) else # Should never happen, per the spec raise ::Rex::Proto::Kerberos::Model::Error::KerberosError.new('Unexpected response type (expected AS_REP or KRB_ERROR)') end end |
#timeout ⇒ Integer
Returns the TCP timeout
61 62 63 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 61 def timeout datastore['Timeout'] end |