Changes from code review

This commit is contained in:
Ashley Donaldson 2023-10-30 12:51:55 +11:00
parent 0b7f079d25
commit 2a699b89fa
No known key found for this signature in database
GPG Key ID: D4BCDC8C892F7477
4 changed files with 72 additions and 32 deletions

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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" \