Skip to content

Commit 4480ea7

Browse files
author
Brent Cook
committed
Land rapid7#7827, Cisco Firepower Management Console LoginScanner
2 parents a4dd1fc + 171cc7d commit 4480ea7

File tree

4 files changed

+316
-0
lines changed

4 files changed

+316
-0
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
This module allows you to authenticate to Cisco Firepower Management console. The found credentials
2+
could also be used in Cisco Firepower's SSH service, which would potentially give you remote code
3+
execution.
4+
5+
## Vulnerable Application
6+
7+
The vulnerable software can be downloaded from Cisco as long as you are a member. Specifically,
8+
this module was testing on version 6.0.1 during development.
9+
10+
11+
For Cisco members, get the virtual appliance 6.0.1-2013 here:
12+
13+
https://software.cisco.com/download/release.html?mdfid=286259687&softwareid=286271056&release=6.0.1&flowid=54052
14+
15+
16+
## Verification Steps
17+
18+
1. Make sure Cisco Firepower Management console's HTTPS service is running
19+
2. Start ```msfconsole```
20+
3. ```use auxiliary/scanner/http/cisco_firepower_login.rb
21+
4. ```set RHOSTS [IP]```
22+
5. Set credentials
23+
6. ```run```
24+
7. You should see that the module is attempting to log in.
25+
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
require 'metasploit/framework/login_scanner/http'
2+
require 'digest'
3+
4+
module Metasploit
5+
module Framework
6+
module LoginScanner
7+
8+
class CiscoFirepower < HTTP
9+
10+
DEFAULT_PORT = 443
11+
PRIVATE_TYPES = [ :password ]
12+
LOGIN_STATUS = Metasploit::Model::Login::Status # Shorter name
13+
14+
def check_setup
15+
res = send_request({
16+
'method' => 'GET',
17+
'uri' => normalize_uri("#{uri}login.cgi")
18+
})
19+
20+
if res && res.code == 200 && res.body.include?('/img/favicon.png?v=6.0.1-1213')
21+
return true
22+
end
23+
24+
false
25+
end
26+
27+
def do_login(cred)
28+
console_user = cred.public
29+
console_pass = cred.private
30+
31+
res = send_request({
32+
'method' => 'POST',
33+
'uri' => normalize_uri("#{uri}login.cgi"),
34+
'vars_post' => {
35+
'username' => console_user,
36+
'password' => console_pass,
37+
'target' => ''
38+
}
39+
})
40+
41+
unless res
42+
return {status: LOGIN_STATUS::UNABLE_TO_CONNECT, proof: 'Connection timed out for login.cig'}
43+
end
44+
45+
if res.code == 302 && res.get_cookies.include?('CGISESSID')
46+
return {status: LOGIN_STATUS::SUCCESSFUL, proof: res.body}
47+
end
48+
49+
{status: LOGIN_STATUS::INCORRECT, proof: res.body}
50+
end
51+
52+
# Attempts to login to Cisco. This is called first.
53+
#
54+
# @param credential [Metasploit::Framework::Credential] The credential object
55+
# @return [Result] A Result object indicating success or failure
56+
def attempt_login(credential)
57+
result_opts = {
58+
credential: credential,
59+
status: Metasploit::Model::Login::Status::INCORRECT,
60+
proof: nil,
61+
host: host,
62+
port: port,
63+
protocol: 'tcp'
64+
}
65+
66+
begin
67+
result_opts.merge!(do_login(credential))
68+
rescue ::Rex::ConnectionError => e
69+
# Something went wrong during login. 'e' knows what's up.
70+
result_opts.merge!(status: LOGIN_STATUS::UNABLE_TO_CONNECT, proof: e.message)
71+
end
72+
73+
Result.new(result_opts)
74+
end
75+
76+
end
77+
end
78+
end
79+
end
80+
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
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+
require 'metasploit/framework/login_scanner/cisco_firepower'
8+
require 'metasploit/framework/credential_collection'
9+
10+
class MetasploitModule < Msf::Auxiliary
11+
12+
include Msf::Exploit::Remote::HttpClient
13+
include Msf::Auxiliary::AuthBrute
14+
include Msf::Auxiliary::Report
15+
include Msf::Auxiliary::Scanner
16+
17+
def initialize(info={})
18+
super(update_info(info,
19+
'Name' => 'Cisco Firepower Management Console 6.0 Login',
20+
'Description' => %q{
21+
This module attempts to authenticate to a Cisco Firepower Management console via HTTPS.
22+
The credentials are also used for SSH, which could allow remote code execution.
23+
},
24+
'Author' => [ 'sinn3r' ],
25+
'License' => MSF_LICENSE,
26+
'DefaultOptions' =>
27+
{
28+
'RPORT' => 443,
29+
'SSL' => true,
30+
'SSLVersion' => 'Auto'
31+
}
32+
))
33+
34+
register_options(
35+
[
36+
OptString.new('TARGETURI', [true, 'The base path to Cisco Firepower Management console', '/']),
37+
OptBool.new('TRYDEFAULT', [false, 'Try the default credential admin:Admin123', false])
38+
], self.class)
39+
end
40+
41+
42+
def scanner(ip)
43+
@scanner ||= lambda {
44+
cred_collection = Metasploit::Framework::CredentialCollection.new(
45+
blank_passwords: datastore['BLANK_PASSWORDS'],
46+
pass_file: datastore['PASS_FILE'],
47+
password: datastore['PASSWORD'],
48+
user_file: datastore['USER_FILE'],
49+
userpass_file: datastore['USERPASS_FILE'],
50+
username: datastore['USERNAME'],
51+
user_as_pass: datastore['USER_AS_PASS']
52+
)
53+
54+
if datastore['TRYDEFAULT']
55+
print_status("Default credential admin:Admin123 added to the credential queue for testing.")
56+
cred_collection.add_public('admin')
57+
cred_collection.add_private('Admin123')
58+
end
59+
60+
return Metasploit::Framework::LoginScanner::CiscoFirepower.new(
61+
configure_http_login_scanner(
62+
host: ip,
63+
port: datastore['RPORT'],
64+
cred_details: cred_collection,
65+
stop_on_success: datastore['STOP_ON_SUCCESS'],
66+
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
67+
connection_timeout: 5,
68+
http_username: datastore['HttpUsername'],
69+
http_password: datastore['HttpPassword'],
70+
uri: target_uri.path
71+
))
72+
}.call
73+
end
74+
75+
76+
def report_good_cred(ip, port, result)
77+
service_data = {
78+
address: ip,
79+
port: port,
80+
service_name: 'http',
81+
protocol: 'tcp',
82+
workspace_id: myworkspace_id
83+
}
84+
85+
credential_data = {
86+
module_fullname: self.fullname,
87+
origin_type: :service,
88+
private_data: result.credential.private,
89+
private_type: :password,
90+
username: result.credential.public,
91+
}.merge(service_data)
92+
93+
login_data = {
94+
core: create_credential(credential_data),
95+
last_attempted_at: DateTime.now,
96+
status: result.status,
97+
proof: result.proof
98+
}.merge(service_data)
99+
100+
create_credential_login(login_data)
101+
end
102+
103+
104+
def report_bad_cred(ip, rport, result)
105+
invalidate_login(
106+
address: ip,
107+
port: rport,
108+
protocol: 'tcp',
109+
public: result.credential.public,
110+
private: result.credential.private,
111+
realm_key: result.credential.realm_key,
112+
realm_value: result.credential.realm,
113+
status: result.status,
114+
proof: result.proof
115+
)
116+
end
117+
118+
def bruteforce(ip)
119+
scanner(ip).scan! do |result|
120+
case result.status
121+
when Metasploit::Model::Login::Status::SUCCESSFUL
122+
print_brute(:level => :good, :ip => ip, :msg => "Success: '#{result.credential}'")
123+
report_good_cred(ip, rport, result)
124+
when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
125+
vprint_brute(:level => :verror, :ip => ip, :msg => result.proof)
126+
report_bad_cred(ip, rport, result)
127+
when Metasploit::Model::Login::Status::INCORRECT
128+
vprint_brute(:level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'")
129+
report_bad_cred(ip, rport, result)
130+
end
131+
end
132+
end
133+
134+
def run_host(ip)
135+
unless scanner(ip).check_setup
136+
print_brute(:level => :error, :ip => ip, :msg => 'Target is not Cisco Firepower Management console.')
137+
return
138+
end
139+
140+
bruteforce(ip)
141+
end
142+
143+
end
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
require 'spec_helper'
2+
require 'metasploit/framework/login_scanner/cisco_firepower'
3+
4+
RSpec.describe Metasploit::Framework::LoginScanner::CiscoFirepower do
5+
6+
it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false
7+
it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket'
8+
9+
subject do
10+
described_class.new
11+
end
12+
13+
let(:successful_auth_response) do
14+
res = Rex::Proto::Http::Response.new(302, 'Found')
15+
res.headers['Location'] = '/'
16+
res.headers['Set-Cookie'] = 'CGISESSID=NEWSESSIONID;'
17+
res
18+
end
19+
20+
let(:fail_auth_response) do
21+
Rex::Proto::Http::Response.new(200, 'OK')
22+
end
23+
24+
describe '#attempt_login' do
25+
26+
context 'when the credential is valid' do
27+
let(:username) { 'user' }
28+
let(:password) { 'goddpass' }
29+
30+
before do
31+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:request_cgi).with(any_args)
32+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).with(any_args).and_return(successful_auth_response)
33+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:set_config).with(any_args)
34+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:close)
35+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect)
36+
end
37+
38+
it 'returns a Result object indicating a successful login' do
39+
cred = Metasploit::Framework::Credential.new(public: username, private: password)
40+
result = subject.attempt_login(cred)
41+
expect(result).to be_kind_of(Metasploit::Framework::LoginScanner::Result)
42+
expect(result.status).to eq(Metasploit::Model::Login::Status::SUCCESSFUL)
43+
end
44+
end
45+
46+
context 'when the credential is invalid' do
47+
let(:username) { 'admin' }
48+
let(:password) { 'badpass' }
49+
50+
before(:example) do
51+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:request_cgi).with(any_args)
52+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).with(any_args).and_return(fail_auth_response)
53+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:set_config).with(any_args)
54+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:close)
55+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect)
56+
end
57+
58+
it 'returns a Result object indicating a failed login' do
59+
cred = Metasploit::Framework::Credential.new(public: username, private: password)
60+
result = subject.attempt_login(cred)
61+
expect(result).to be_kind_of(Metasploit::Framework::LoginScanner::Result)
62+
expect(result.status).to eq(Metasploit::Model::Login::Status::INCORRECT)
63+
end
64+
end
65+
end
66+
67+
68+
end

0 commit comments

Comments
 (0)