Changes from code review
This commit is contained in:
parent
0b7f079d25
commit
2a699b89fa
|
@ -10,6 +10,8 @@ module Msf::Exploit::Remote::DCERPC::KerberosAuthentication
|
|||
@kerberos_authenticator = kerberos_authenticator
|
||||
end
|
||||
|
||||
# Initialize the auth provider using Kerberos
|
||||
# @return Serialized message for initializing the auth provider
|
||||
def auth_provider_init
|
||||
kerberos_result = @kerberos_authenticator.authenticate
|
||||
@application_key = @session_key = kerberos_result[:session_key]
|
||||
|
@ -17,6 +19,9 @@ module Msf::Exploit::Remote::DCERPC::KerberosAuthentication
|
|||
kerberos_result[:security_blob]
|
||||
end
|
||||
|
||||
# Encrypt the value in dcerpc_req, and add a valid signature to the request.
|
||||
# This function modifies the request object in-place, and does not return anything.
|
||||
# @param dcerpc_req [Request] The Request object to be encrypted and signed in-place
|
||||
def auth_provider_encrypt_and_sign(dcerpc_req)
|
||||
auth_pad_length = get_auth_padding_length(dcerpc_req.stub.to_binary_s.length)
|
||||
plain_stub = dcerpc_req.stub.to_binary_s + "\x00" * auth_pad_length
|
||||
|
@ -24,12 +29,20 @@ module Msf::Exploit::Remote::DCERPC::KerberosAuthentication
|
|||
|
||||
encrypted_stub = emessage[header_length..-1]
|
||||
signature = emessage[0,header_length]
|
||||
dcerpc_req.sec_trailer.auth_pad_length = auth_pad_length
|
||||
set_encrypted_packet(dcerpc_req, encrypted_stub, auth_pad_length)
|
||||
set_signature_on_packet(dcerpc_req, signature)
|
||||
end
|
||||
|
||||
# Decrypt the value in dcerpc_response, and validate its signature.
|
||||
# This function modifies the request object in-place, and returns whether the signature was valid.
|
||||
# @param dcerpc_response [Response] The Response packet to decrypt and verify in-place
|
||||
# @raise ArgumentError If the auth type is not SPNEGO (which ultimately wraps Kerberos)
|
||||
# @return [Boolean] Is the packet's signature valid?
|
||||
def auth_provider_decrypt_and_verify(dcerpc_response)
|
||||
auth_type = dcerpc_response.sec_trailer.auth_type
|
||||
unless [RubySMB::Dcerpc::RPC_C_AUTHN_GSS_NEGOTIATE].include?(auth_type)
|
||||
raise ArgumentError, "Unsupported Auth Type: #{dcerpc_response.sec_trailer.auth_type}"
|
||||
end
|
||||
encrypted_stub = get_response_full_stub(dcerpc_response)
|
||||
signature = dcerpc_response.auth_value
|
||||
data = signature + encrypted_stub
|
||||
|
@ -40,6 +53,8 @@ module Msf::Exploit::Remote::DCERPC::KerberosAuthentication
|
|||
return false
|
||||
end
|
||||
set_decrypted_packet(dcerpc_response, result)
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def build_ap_rep(session_key, sequence_number)
|
||||
|
@ -67,41 +82,47 @@ module Msf::Exploit::Remote::DCERPC::KerberosAuthentication
|
|||
end
|
||||
|
||||
def auth_provider_complete_handshake(response, options)
|
||||
begin
|
||||
@kerberos_authenticator.validate_response!(response.auth_value, accept_incomplete: true)
|
||||
gss_api = OpenSSL::ASN1.decode(response.auth_value)
|
||||
security_blob = ::RubySMB::Gss.asn1dig(gss_api, 0, 2, 0)&.value
|
||||
ap_rep = Rex::Proto::Kerberos::Model::ApRep.decode(security_blob)
|
||||
ap_rep_enc_part = ap_rep.decrypt_enc_part(@session_key.value)
|
||||
server_sequence_number = ap_rep_enc_part.sequence_number
|
||||
# Now complete the handshake - see [MS-KILE] 3.4.5.1 - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/190ab8de-dc42-49cf-bf1b-ea5705b7a087
|
||||
response_ap_rep = build_ap_rep(@session_key, server_sequence_number)
|
||||
rescue ::Rex::Proto::Kerberos::Model::Error::KerberosDecodingError,
|
||||
::Rex::Proto::Kerberos::Model::Error::KerberosError,
|
||||
OpenSSL::ASN1::ASN1Error => e
|
||||
raise RubySMB::Dcerpc::Error::BindError, e.message # raise the more context-specific BindError
|
||||
end
|
||||
server_sequence_number = ap_rep_enc_part.sequence_number
|
||||
# Now complete the handshake - see [MS-KILE] 3.4.5.1 - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/190ab8de-dc42-49cf-bf1b-ea5705b7a087
|
||||
response_ap_rep = build_ap_rep(@session_key, server_sequence_number)
|
||||
|
||||
wrapped_ap_rep = OpenSSL::ASN1::ASN1Data.new([
|
||||
OpenSSL::ASN1::Sequence.new([
|
||||
OpenSSL::ASN1::ASN1Data.new([
|
||||
OpenSSL::ASN1::OctetString(response_ap_rep.encode)
|
||||
], 2, :CONTEXT_SPECIFIC)
|
||||
])
|
||||
], 1, :CONTEXT_SPECIFIC).to_der
|
||||
wrapped_ap_rep = OpenSSL::ASN1::ASN1Data.new([
|
||||
OpenSSL::ASN1::Sequence.new([
|
||||
OpenSSL::ASN1::ASN1Data.new([
|
||||
OpenSSL::ASN1::OctetString(response_ap_rep.encode)
|
||||
], 2, :CONTEXT_SPECIFIC)
|
||||
])
|
||||
], 1, :CONTEXT_SPECIFIC).to_der
|
||||
|
||||
alter_ctx = RubySMB::Dcerpc::AlterContext.new(options)
|
||||
alter_ctx.pdu_header.call_id = @call_id
|
||||
alter_ctx = RubySMB::Dcerpc::AlterContext.new(options)
|
||||
alter_ctx.pdu_header.call_id = @call_id
|
||||
|
||||
add_auth_verifier(alter_ctx, wrapped_ap_rep)
|
||||
add_auth_verifier(alter_ctx, wrapped_ap_rep)
|
||||
|
||||
send_packet(alter_ctx)
|
||||
send_packet(alter_ctx)
|
||||
|
||||
begin
|
||||
dcerpc_response = recv_struct(RubySMB::Dcerpc::AlterContextResp)
|
||||
rescue RubySMB::Dcerpc::Error::InvalidPacket
|
||||
raise RubySMB::Dcerpc::Error::BindError # raise the more context-specific BindError
|
||||
end
|
||||
begin
|
||||
dcerpc_response = recv_struct(RubySMB::Dcerpc::AlterContextResp)
|
||||
rescue RubySMB::Dcerpc::Error::InvalidPacket
|
||||
raise RubySMB::Dcerpc::Error::BindError, e.message # raise the more context-specific BindError
|
||||
end
|
||||
|
||||
self.krb_encryptor = @kerberos_authenticator.get_message_encryptor(ap_rep_enc_part.subkey,
|
||||
@client_sequence_number,
|
||||
server_sequence_number)
|
||||
# Set the session key value on the parent class - needed for decrypting attribute values in e.g. DRSR
|
||||
@session_key = ap_rep_enc_part.subkey.value
|
||||
self.krb_encryptor = @kerberos_authenticator.get_message_encryptor(ap_rep_enc_part.subkey,
|
||||
@client_sequence_number,
|
||||
server_sequence_number)
|
||||
# Set the session key value on the parent class - needed for decrypting attribute values in e.g. DRSR
|
||||
@session_key = ap_rep_enc_part.subkey.value
|
||||
end
|
||||
|
||||
def get_auth_padding_length(plaintext_len)
|
||||
|
|
|
@ -294,11 +294,16 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
|
|||
|
||||
# @param security_blob [String] SPNEGO GSS Blob
|
||||
# @param accept_incomplete [Boolean] Whether an Incomplete value is an acceptable response
|
||||
# @raise [Rex::Proto::Kerberos::Model::Error::KerberosDecodingError] if the response was not successful
|
||||
# @raise [Rex::Proto::Kerberos::Model::Error::KerberosError] if the response was not successful
|
||||
# @raise [Rex::Proto::Kerberos::Model::Error::KerberosDecodingError] if the response was invalid per the Kerberos/GSS protocol
|
||||
def validate_response!(security_blob, accept_incomplete: false)
|
||||
gss_api = OpenSSL::ASN1.decode(security_blob)
|
||||
neg_result = ::RubySMB::Gss.asn1dig(gss_api, 0, 0, 0)&.value.to_i
|
||||
supported_neg = ::RubySMB::Gss.asn1dig(gss_api, 0, 1, 0)&.value
|
||||
begin
|
||||
gss_api = OpenSSL::ASN1.decode(security_blob)
|
||||
neg_result = ::RubySMB::Gss.asn1dig(gss_api, 0, 0, 0)&.value.to_i
|
||||
supported_neg = ::RubySMB::Gss.asn1dig(gss_api, 0, 1, 0)&.value
|
||||
rescue OpenSSL::ASN1::ASN1Error
|
||||
raise ::Rex::Proto::Kerberos::Model::Error::KerberosDecodingError.new('Invalid GSS Response')
|
||||
end
|
||||
|
||||
is_success = (neg_result == NEG_TOKEN_ACCEPT_COMPLETED || (accept_incomplete && neg_result == NEG_TOKEN_ACCEPT_INCOMPLETE)) &&
|
||||
supported_neg == ::Rex::Proto::Gss::OID_MICROSOFT_KERBEROS_5.value
|
||||
|
|
|
@ -13,9 +13,13 @@ module Msf::Exploit::Remote::SMB::Client::KerberosAuthentication
|
|||
def authenticate
|
||||
raise ::RubySMB::Error::AuthenticationFailure, "Missing negotiation security buffer" if negotiation_security_buffer.nil?
|
||||
|
||||
gss_api = OpenSSL::ASN1.decode(negotiation_security_buffer)
|
||||
mech_types = RubySMB::Gss.asn1dig(gss_api, 1, 0, 0, 0)&.value || []
|
||||
has_kerberos_gss_mech_type = mech_types&.any? { |mech_type| mech_type.value == ::Rex::Proto::Gss::OID_MICROSOFT_KERBEROS_5.value }
|
||||
begin
|
||||
gss_api = OpenSSL::ASN1.decode(negotiation_security_buffer)
|
||||
mech_types = RubySMB::Gss.asn1dig(gss_api, 1, 0, 0, 0)&.value || []
|
||||
has_kerberos_gss_mech_type = mech_types&.any? { |mech_type| mech_type.value == ::Rex::Proto::Gss::OID_MICROSOFT_KERBEROS_5.value }
|
||||
rescue OpenSSL::ASN1::ASN1Error
|
||||
raise ::Rex::Proto::Kerberos::Model::Error::KerberosDecodingError.new('Invalid GSS Response')
|
||||
end
|
||||
|
||||
error = "Unable to negotiate kerberos with the remote host. Expected oid #{::Rex::Proto::Gss::OID_MICROSOFT_KERBEROS_5.value} in #{mech_types.map(&:value).inspect}"
|
||||
raise ::RubySMB::Error::AuthenticationFailure, error unless has_kerberos_gss_mech_type
|
||||
|
|
|
@ -28,6 +28,16 @@ RSpec.describe Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when the response is invalid ASN1' do
|
||||
let(:response) do
|
||||
'abcd'
|
||||
end
|
||||
|
||||
it 'raises a Kerberos decoding exception' do
|
||||
expect { subject.validate_response!(response) }.to raise_error(::Rex::Proto::Kerberos::Model::Error::KerberosDecodingError, /Invalid GSS/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the response is accept-incomplete and contains a kerberos error' do
|
||||
let(:response) do
|
||||
"\xa1\x81\x89\x30\x81\x86\xa0\x03\x0a\x01\x01\xa1\x0b\x06\x09\x2a" \
|
||||
|
|
Loading…
Reference in New Issue