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
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 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.
34 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 34 attr_accessor :kerberos_client |
#kerberos_client ⇒ Object
Returns the value of attribute kerberos_client.
34 35 36 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 34 def kerberos_client @kerberos_client end |
Instance Method Details
#cleanup ⇒ Object
Performs cleanup as necessary, disconnecting the Kerberos client if it’s still established.
173 174 175 176 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 173 def cleanup super disconnect end |
#connect(opts = {}) ⇒ Rex::Proto::Kerberos::Client
Creates a kerberos connection
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 134 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
163 164 165 166 167 168 169 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 163 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)
492 493 494 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 492 def framework_module self end |
#initialize(info = {}) ⇒ Object
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 36 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.
80 81 82 83 84 85 86 87 88 89 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 80 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.
94 95 96 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 94 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.
102 103 104 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 102 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.
110 111 112 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 110 def kerberos_time_local(base_time = Time.now) base_time + kerberos_clock_skew end |
#kerberos_trace_subscriber ⇒ Object (protected)
496 497 498 499 500 501 502 503 504 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 496 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
117 118 119 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 117 def peer "#{rhost}:#{rport}" end |
#proxies ⇒ String?
Returns the configured proxy list
124 125 126 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 124 def proxies datastore['Proxies'] end |
#rhost ⇒ String
Returns the target host
59 60 61 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 59 def rhost datastore['RHOST'] end |
#rport ⇒ Integer
Returns the remote port
66 67 68 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 66 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.
213 214 215 216 217 218 219 220 221 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 213 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
184 185 186 187 188 189 190 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 184 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
198 199 200 201 202 203 204 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 198 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
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 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 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 302 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
233 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 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 233 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 = 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
73 74 75 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 73 def timeout datastore['Timeout'] end |