Land #7827, Cisco Firepower Management Console LoginScanner
commit
4480ea7877
|
@ -0,0 +1,25 @@
|
|||
This module allows you to authenticate to Cisco Firepower Management console. The found credentials
|
||||
could also be used in Cisco Firepower's SSH service, which would potentially give you remote code
|
||||
execution.
|
||||
|
||||
## Vulnerable Application
|
||||
|
||||
The vulnerable software can be downloaded from Cisco as long as you are a member. Specifically,
|
||||
this module was testing on version 6.0.1 during development.
|
||||
|
||||
|
||||
For Cisco members, get the virtual appliance 6.0.1-2013 here:
|
||||
|
||||
https://software.cisco.com/download/release.html?mdfid=286259687&softwareid=286271056&release=6.0.1&flowid=54052
|
||||
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Make sure Cisco Firepower Management console's HTTPS service is running
|
||||
2. Start ```msfconsole```
|
||||
3. ```use auxiliary/scanner/http/cisco_firepower_login.rb
|
||||
4. ```set RHOSTS [IP]```
|
||||
5. Set credentials
|
||||
6. ```run```
|
||||
7. You should see that the module is attempting to log in.
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
require 'metasploit/framework/login_scanner/http'
|
||||
require 'digest'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module LoginScanner
|
||||
|
||||
class CiscoFirepower < HTTP
|
||||
|
||||
DEFAULT_PORT = 443
|
||||
PRIVATE_TYPES = [ :password ]
|
||||
LOGIN_STATUS = Metasploit::Model::Login::Status # Shorter name
|
||||
|
||||
def check_setup
|
||||
res = send_request({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri("#{uri}login.cgi")
|
||||
})
|
||||
|
||||
if res && res.code == 200 && res.body.include?('/img/favicon.png?v=6.0.1-1213')
|
||||
return true
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def do_login(cred)
|
||||
console_user = cred.public
|
||||
console_pass = cred.private
|
||||
|
||||
res = send_request({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri("#{uri}login.cgi"),
|
||||
'vars_post' => {
|
||||
'username' => console_user,
|
||||
'password' => console_pass,
|
||||
'target' => ''
|
||||
}
|
||||
})
|
||||
|
||||
unless res
|
||||
return {status: LOGIN_STATUS::UNABLE_TO_CONNECT, proof: 'Connection timed out for login.cig'}
|
||||
end
|
||||
|
||||
if res.code == 302 && res.get_cookies.include?('CGISESSID')
|
||||
return {status: LOGIN_STATUS::SUCCESSFUL, proof: res.body}
|
||||
end
|
||||
|
||||
{status: LOGIN_STATUS::INCORRECT, proof: res.body}
|
||||
end
|
||||
|
||||
# Attempts to login to Cisco. This is called first.
|
||||
#
|
||||
# @param credential [Metasploit::Framework::Credential] The credential object
|
||||
# @return [Result] A Result object indicating success or failure
|
||||
def attempt_login(credential)
|
||||
result_opts = {
|
||||
credential: credential,
|
||||
status: Metasploit::Model::Login::Status::INCORRECT,
|
||||
proof: nil,
|
||||
host: host,
|
||||
port: port,
|
||||
protocol: 'tcp'
|
||||
}
|
||||
|
||||
begin
|
||||
result_opts.merge!(do_login(credential))
|
||||
rescue ::Rex::ConnectionError => e
|
||||
# Something went wrong during login. 'e' knows what's up.
|
||||
result_opts.merge!(status: LOGIN_STATUS::UNABLE_TO_CONNECT, proof: e.message)
|
||||
end
|
||||
|
||||
Result.new(result_opts)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'metasploit/framework/login_scanner/cisco_firepower'
|
||||
require 'metasploit/framework/credential_collection'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Auxiliary::AuthBrute
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::Scanner
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => 'Cisco Firepower Management Console 6.0 Login',
|
||||
'Description' => %q{
|
||||
This module attempts to authenticate to a Cisco Firepower Management console via HTTPS.
|
||||
The credentials are also used for SSH, which could allow remote code execution.
|
||||
},
|
||||
'Author' => [ 'sinn3r' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'RPORT' => 443,
|
||||
'SSL' => true,
|
||||
'SSLVersion' => 'Auto'
|
||||
}
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('TARGETURI', [true, 'The base path to Cisco Firepower Management console', '/']),
|
||||
OptBool.new('TRYDEFAULT', [false, 'Try the default credential admin:Admin123', false])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
|
||||
def scanner(ip)
|
||||
@scanner ||= lambda {
|
||||
cred_collection = Metasploit::Framework::CredentialCollection.new(
|
||||
blank_passwords: datastore['BLANK_PASSWORDS'],
|
||||
pass_file: datastore['PASS_FILE'],
|
||||
password: datastore['PASSWORD'],
|
||||
user_file: datastore['USER_FILE'],
|
||||
userpass_file: datastore['USERPASS_FILE'],
|
||||
username: datastore['USERNAME'],
|
||||
user_as_pass: datastore['USER_AS_PASS']
|
||||
)
|
||||
|
||||
if datastore['TRYDEFAULT']
|
||||
print_status("Default credential admin:Admin123 added to the credential queue for testing.")
|
||||
cred_collection.add_public('admin')
|
||||
cred_collection.add_private('Admin123')
|
||||
end
|
||||
|
||||
return Metasploit::Framework::LoginScanner::CiscoFirepower.new(
|
||||
configure_http_login_scanner(
|
||||
host: ip,
|
||||
port: datastore['RPORT'],
|
||||
cred_details: cred_collection,
|
||||
stop_on_success: datastore['STOP_ON_SUCCESS'],
|
||||
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
|
||||
connection_timeout: 5,
|
||||
http_username: datastore['HttpUsername'],
|
||||
http_password: datastore['HttpPassword'],
|
||||
uri: target_uri.path
|
||||
))
|
||||
}.call
|
||||
end
|
||||
|
||||
|
||||
def report_good_cred(ip, port, result)
|
||||
service_data = {
|
||||
address: ip,
|
||||
port: port,
|
||||
service_name: 'http',
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
|
||||
credential_data = {
|
||||
module_fullname: self.fullname,
|
||||
origin_type: :service,
|
||||
private_data: result.credential.private,
|
||||
private_type: :password,
|
||||
username: result.credential.public,
|
||||
}.merge(service_data)
|
||||
|
||||
login_data = {
|
||||
core: create_credential(credential_data),
|
||||
last_attempted_at: DateTime.now,
|
||||
status: result.status,
|
||||
proof: result.proof
|
||||
}.merge(service_data)
|
||||
|
||||
create_credential_login(login_data)
|
||||
end
|
||||
|
||||
|
||||
def report_bad_cred(ip, rport, result)
|
||||
invalidate_login(
|
||||
address: ip,
|
||||
port: rport,
|
||||
protocol: 'tcp',
|
||||
public: result.credential.public,
|
||||
private: result.credential.private,
|
||||
realm_key: result.credential.realm_key,
|
||||
realm_value: result.credential.realm,
|
||||
status: result.status,
|
||||
proof: result.proof
|
||||
)
|
||||
end
|
||||
|
||||
def bruteforce(ip)
|
||||
scanner(ip).scan! do |result|
|
||||
case result.status
|
||||
when Metasploit::Model::Login::Status::SUCCESSFUL
|
||||
print_brute(:level => :good, :ip => ip, :msg => "Success: '#{result.credential}'")
|
||||
report_good_cred(ip, rport, result)
|
||||
when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
|
||||
vprint_brute(:level => :verror, :ip => ip, :msg => result.proof)
|
||||
report_bad_cred(ip, rport, result)
|
||||
when Metasploit::Model::Login::Status::INCORRECT
|
||||
vprint_brute(:level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'")
|
||||
report_bad_cred(ip, rport, result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
unless scanner(ip).check_setup
|
||||
print_brute(:level => :error, :ip => ip, :msg => 'Target is not Cisco Firepower Management console.')
|
||||
return
|
||||
end
|
||||
|
||||
bruteforce(ip)
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,68 @@
|
|||
require 'spec_helper'
|
||||
require 'metasploit/framework/login_scanner/cisco_firepower'
|
||||
|
||||
RSpec.describe Metasploit::Framework::LoginScanner::CiscoFirepower do
|
||||
|
||||
it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false
|
||||
it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket'
|
||||
|
||||
subject do
|
||||
described_class.new
|
||||
end
|
||||
|
||||
let(:successful_auth_response) do
|
||||
res = Rex::Proto::Http::Response.new(302, 'Found')
|
||||
res.headers['Location'] = '/'
|
||||
res.headers['Set-Cookie'] = 'CGISESSID=NEWSESSIONID;'
|
||||
res
|
||||
end
|
||||
|
||||
let(:fail_auth_response) do
|
||||
Rex::Proto::Http::Response.new(200, 'OK')
|
||||
end
|
||||
|
||||
describe '#attempt_login' do
|
||||
|
||||
context 'when the credential is valid' do
|
||||
let(:username) { 'user' }
|
||||
let(:password) { 'goddpass' }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:request_cgi).with(any_args)
|
||||
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).with(any_args).and_return(successful_auth_response)
|
||||
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:set_config).with(any_args)
|
||||
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:close)
|
||||
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect)
|
||||
end
|
||||
|
||||
it 'returns a Result object indicating a successful login' do
|
||||
cred = Metasploit::Framework::Credential.new(public: username, private: password)
|
||||
result = subject.attempt_login(cred)
|
||||
expect(result).to be_kind_of(Metasploit::Framework::LoginScanner::Result)
|
||||
expect(result.status).to eq(Metasploit::Model::Login::Status::SUCCESSFUL)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the credential is invalid' do
|
||||
let(:username) { 'admin' }
|
||||
let(:password) { 'badpass' }
|
||||
|
||||
before(:example) do
|
||||
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:request_cgi).with(any_args)
|
||||
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).with(any_args).and_return(fail_auth_response)
|
||||
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:set_config).with(any_args)
|
||||
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:close)
|
||||
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect)
|
||||
end
|
||||
|
||||
it 'returns a Result object indicating a failed login' do
|
||||
cred = Metasploit::Framework::Credential.new(public: username, private: password)
|
||||
result = subject.attempt_login(cred)
|
||||
expect(result).to be_kind_of(Metasploit::Framework::LoginScanner::Result)
|
||||
expect(result.status).to eq(Metasploit::Model::Login::Status::INCORRECT)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
Loading…
Reference in New Issue