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.
-
#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.
33 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 33 attr_accessor :kerberos_client |
#kerberos_client ⇒ Object
Returns the value of attribute kerberos_client.
33 34 35 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 33 def kerberos_client @kerberos_client end |
Instance Method Details
#cleanup ⇒ Object
Performs cleanup as necessary, disconnecting the Kerberos client if it’s still established.
164 165 166 167 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 164 def cleanup super disconnect end |
#connect(opts = {}) ⇒ Rex::Proto::Kerberos::Client
Creates a kerberos connection
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 132 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
154 155 156 157 158 159 160 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 154 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)
483 484 485 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 483 def framework_module self end |
#initialize(info = {}) ⇒ Object
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 35 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 ) ( [ OptString.new('KrbClockSkew', [true, 'Adjust Kerberos client clock by this offset (e.g. 90s, -5m, 1h)', '0s'], regex: Msf::Exploit::Remote::Kerberos::ClockSkew::CLOCK_SKEW_REGEX) ], self.class ) end |
#kerberos_clock_skew ⇒ Float
Returns the configured Kerberos clock skew in seconds.
78 79 80 81 82 83 84 85 86 87 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 78 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.
92 93 94 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 92 def kerberos_clock_skew=(value) @kerberos_clock_skew = Msf::Exploit::Remote::Kerberos::ClockSkew.parse(value) end |
#kerberos_time(base_time = Time.now.utc) ⇒ Time
Returns the current time adjusted for Kerberos clock skew in UTC.
100 101 102 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 100 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.
108 109 110 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 108 def kerberos_time_local(base_time = Time.now) base_time + kerberos_clock_skew end |
#peer ⇒ String
Returns the kdc peer
115 116 117 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 115 def peer "#{rhost}:#{rport}" end |
#proxies ⇒ String?
Returns the configured proxy list
122 123 124 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 122 def proxies datastore['Proxies'] end |
#rhost ⇒ String
Returns the target host
57 58 59 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 57 def rhost datastore['RHOST'] end |
#rport ⇒ Integer
Returns the remote port
64 65 66 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 64 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.
204 205 206 207 208 209 210 211 212 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 204 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
175 176 177 178 179 180 181 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 175 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
189 190 191 192 193 194 195 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 189 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
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 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 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 293 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
224 225 226 227 228 229 230 231 232 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 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 224 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
71 72 73 |
# File 'lib/msf/core/exploit/remote/kerberos/client.rb', line 71 def timeout datastore['Timeout'] end |