Module: Msf::Exploit::Remote::Kerberos::Client
- Includes:
- CertificateTrace, 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
Overview
Kerberos client helpers shared across mixins.
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
-
#kerberos_clock_skew ⇒ Float
Returns the configured Kerberos clock skew in seconds.
-
#kerberos_clock_skew=(value) ⇒ Object
Sets the Kerberos clock skew.
-
#kerberos_time(base_time = Time.now.utc) ⇒ Time
Returns the current time adjusted for Kerberos clock skew in UTC.
-
#kerberos_time_local(base_time = Time.now) ⇒ Time
Returns the current time adjusted for Kerberos clock skew in the local timezone.
- #kerberos_trace_subscriber ⇒ Object protected
-
#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 CertificateTrace
#certificate_trace, #certificate_trace_enabled?
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, #format_tgs_rep_to_john_hash
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.
35 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 35 attr_accessor :kerberos_client |
#kerberos_client ⇒ Object
Returns the value of attribute kerberos_client.
35 36 37 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 35 def kerberos_client @kerberos_client end |
Instance Method Details
#cleanup ⇒ Object
Performs cleanup as necessary, disconnecting the Kerberos client if it’s still established.
174 175 176 177 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 174 def cleanup super disconnect end |
#connect(opts = {}) ⇒ Rex::Proto::Kerberos::Client
Creates a kerberos connection
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 135 def connect(opts = {}) has_session = defined?(session) && session remote_host = has_session ? session.client.peerhost : rhost # Can't use session.client.rport as a fallback here with an LDAP session as that's port 389. We need port 88. remote_port = has_session ? 88 : rport subscriber = opts.key?(:subscriber) ? opts[:subscriber] : kerberos_trace_subscriber kerb_client = Rex::Proto::Kerberos::Client.new( host: opts[:rhost] || remote_host, port: (opts[:rport] || remote_port).to_i, proxies: opts[:proxies] || proxies, timeout: (opts[:timeout] || timeout).to_i, context: { 'Msf' => framework, 'MsfExploit' => framework_module }, protocol: 'tcp', subscriber: subscriber ) disconnect if kerberos_client self.kerberos_client = kerb_client kerb_client end |
#disconnect(kerb_client = kerberos_client) ⇒ Object
Disconnects the Kerberos client
164 165 166 167 168 169 170 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 164 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)
500 501 502 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 500 def framework_module self end |
#initialize(info = {}) ⇒ Object
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 37 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 ) ( [ OptTimedelta.new('KrbClockSkew', [true, 'Adjust Kerberos client clock by this offset (e.g. 90s, -5m, 1h)', '0s']), OptBool.new('KerberosTicketTrace', [false, 'Show AS/TGS/AP Kerberos requests and responses', false]), OptString.new('KerberosTicketTraceColors', [false, 'Kerberos request and response colors for KerberosTicketTrace (unset to disable)', 'red/blu']) ], self.class ) end |
#kerberos_clock_skew ⇒ Float
Returns the configured Kerberos clock skew in seconds.
81 82 83 84 85 86 87 88 89 90 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 81 def kerberos_clock_skew return @kerberos_clock_skew if instance_variable_defined?(:@kerberos_clock_skew) && !@kerberos_clock_skew.nil? if respond_to?(:datastore) && datastore self.kerberos_clock_skew = datastore['KrbClockSkew'] else self.kerberos_clock_skew = 0 end @kerberos_clock_skew end |
#kerberos_clock_skew=(value) ⇒ Object
Sets the Kerberos clock skew.
95 96 97 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 95 def kerberos_clock_skew=(value) @kerberos_clock_skew = Msf::OptTimedelta.parse(value) end |
#kerberos_time(base_time = Time.now.utc) ⇒ Time
Returns the current time adjusted for Kerberos clock skew in UTC.
103 104 105 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 103 def kerberos_time(base_time = Time.now.utc) (base_time + kerberos_clock_skew).utc end |
#kerberos_time_local(base_time = Time.now) ⇒ Time
Returns the current time adjusted for Kerberos clock skew in the local timezone.
111 112 113 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 111 def kerberos_time_local(base_time = Time.now) base_time + kerberos_clock_skew end |
#kerberos_trace_subscriber ⇒ Object (protected)
504 505 506 507 508 509 510 511 512 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 504 def kerberos_trace_subscriber logger = framework_module if logger.respond_to?(:print_line) && logger.respond_to?(:datastore) Rex::Proto::Kerberos::KerberosLoggerSubscriber.new(logger: logger) else Rex::Proto::Kerberos::KerberosSubscriber.new end end |
#peer ⇒ String
Returns the kdc peer
118 119 120 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 118 def peer "#{rhost}:#{rport}" end |
#proxies ⇒ String?
Returns the configured proxy list
125 126 127 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 125 def proxies datastore['Proxies'] end |
#rhost ⇒ String
Returns the target host
60 61 62 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 60 def rhost datastore['RHOST'] end |
#rport ⇒ Integer
Returns the remote port
67 68 69 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 67 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.
214 215 216 217 218 219 220 221 222 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 214 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
185 186 187 188 189 190 191 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 185 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
199 200 201 202 203 204 205 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 199 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
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 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 310 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 = kerberos_time expiry_time = now + 1.day offered_etypes = [:offered_etypes] || Rex::Proto::Kerberos::Crypto::Encryption::DefaultOfferedEtypes if !password && key && offered_etypes.length != 1 raise ArgumentError, '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 if password.nil? && key.nil? raise ::Rex::Proto::Kerberos::Model::Error::KerberosError.new(res: initial_as_res) end # 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 unless [:offered_etypes]&.length == 1 raise ArgumentError, 'Encryption key provided without one offered encryption type' end 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
234 235 236 237 238 239 240 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 290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 234 def send_request_tgt_pkinit( = {}) pfx = [:pfx] # Trace the client certificate being submitted for PKINIT authentication. # Surfaces cert details at the moment the certificate is presented to the KDC. if pfx.respond_to?(:certificate) certificate_trace(pfx.certificate) end 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 = kerberos_time 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 { |data_entry| data_entry.type == Rex::Proto::Kerberos::Model::PreAuthType::PA_PK_AS_REP } # Should never happen from a spec-compliant server raise ::Rex::Proto::Kerberos::Model::Error::KerberosError, 'No PKINIT PreAuth data received' if entry.nil? 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, 'Unexpected response type (expected AS_REP or KRB_ERROR)' end end |
#timeout ⇒ Integer
Returns the TCP timeout
74 75 76 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 74 def timeout datastore['Timeout'] end |