Skip to content

Commit 9ef0f7b

Browse files
authored
Merge pull request rapid7#20019 from adfoster-r7/improve-support-for-finding-available-http-login-scanners
Improve support for finding available HTTP login scanners
2 parents e6ab820 + bef322e commit 9ef0f7b

File tree

7 files changed

+105
-77
lines changed

7 files changed

+105
-77
lines changed

lib/metasploit/framework/login_scanner.rb

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,24 @@ def self.classes_for_service(service)
3434
end
3535
end
3636

37+
# Gather a list of LoginScanner classes that can potentially be
38+
# used against an HTTP service
39+
#
40+
# @return [Array<LoginScanner::Base>] A collection of LoginScanner
41+
# classes that will probably give useful results when run
42+
# against an HTTP service
43+
def self.all_http_classes
44+
require_login_scanners
45+
46+
http_base_class = Metasploit::Framework::LoginScanner::HTTP
47+
Metasploit::Framework::LoginScanner.constants.sort.filter_map do |sym|
48+
const = Metasploit::Framework::LoginScanner.const_get(sym)
49+
next unless const.kind_of?(Class) && const.ancestors.include?(http_base_class) && const != http_base_class
50+
51+
const
52+
end
53+
end
54+
3755
def self.all_service_names
3856
require_login_scanners
3957

@@ -49,9 +67,9 @@ def self.all_service_names
4967

5068
service_names
5169
end
52-
70+
5371
private
54-
72+
5573
def self.require_login_scanners
5674
unless @required
5775
# Make sure we've required all the scanner classes

lib/metasploit/framework/login_scanner/ivanti_login.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ module LoginScanner
99
class Ivanti < HTTP
1010

1111
DEFAULT_SSL_PORT = 443
12-
LIKELY_PORTS = [443]
13-
LIKELY_SERVICE_NAMES = [
12+
LIKELY_PORTS = self.superclass::LIKELY_PORTS + [443]
13+
LIKELY_SERVICE_NAMES = self.superclass::LIKELY_SERVICE_NAMES + [
1414
'Ivanti Connect Secure'
1515
]
1616
PRIVATE_TYPES = [:password]

lib/metasploit/framework/login_scanner/ldap.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class LDAP
1313

1414
LIKELY_PORTS = [ 389, 636 ]
1515
LIKELY_SERVICE_NAMES = [ 'ldap', 'ldaps', 'ldapssl' ]
16+
PRIVATE_TYPES = [:password, :ntlm_hash]
1617

1718
attr_accessor :opts, :realm_key
1819
# @!attribute use_client_as_proof

lib/metasploit/framework/login_scanner/nessus.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class Nessus < HTTP
99

1010
DEFAULT_PORT = 8834
1111
PRIVATE_TYPES = [ :password ]
12-
LIKELY_SERVICE_NAMES = [ 'nessus' ]
12+
LIKELY_SERVICE_NAMES = self.superclass::LIKELY_SERVICE_NAMES + [ 'nessus' ]
1313
LOGIN_STATUS = Metasploit::Model::Login::Status # Shorter name
1414

1515

lib/metasploit/framework/login_scanner/sonicwall.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ module LoginScanner
88
# - Admin Login
99
class SonicWall < HTTP
1010

11-
DEFAULT_SSL_PORT = [443, 4433]
12-
LIKELY_PORTS = [443, 4433]
13-
LIKELY_SERVICE_NAMES = [
11+
LIKELY_PORTS = self.superclass::LIKELY_PORTS + [4433]
12+
LIKELY_SERVICE_NAMES = self.superclass::LIKELY_SERVICE_NAMES + [
1413
'SonicWall Network Security'
1514
]
1615
PRIVATE_TYPES = [:password]

lib/metasploit/framework/login_scanner/teamcity.rb

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,11 @@ def encrypt_data(text, public_key)
121121
include Crypto
122122

123123
DEFAULT_PORT = 8111
124-
LIKELY_PORTS = [8111]
125-
LIKELY_SERVICE_NAMES = [
124+
LIKELY_PORTS = self.superclass::LIKELY_PORTS + [8111]
125+
LIKELY_SERVICE_NAMES = self.superclass::LIKELY_SERVICE_NAMES + [
126126
# Comes from nmap 7.95 on MacOS
127127
'skynetflow',
128-
'teamcity',
129-
'http',
130-
'https'
128+
'teamcity'
131129
]
132130
PRIVATE_TYPES = [:password]
133131
REALM_KEY = nil

spec/lib/metasploit/framework/login_scanner_spec.rb

Lines changed: 76 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -6,50 +6,68 @@
66

77
RSpec.describe Metasploit::Framework::LoginScanner do
88

9-
subject { described_class.classes_for_service(service) }
10-
let(:port) { nil }
11-
let(:name) { nil }
12-
13-
let(:service) do
14-
s = double('service')
15-
allow(s).to receive(:port) { port }
16-
allow(s).to receive(:name) { name }
17-
s
18-
end
9+
describe '.classes_for_service' do
10+
subject { described_class.classes_for_service(service) }
11+
let(:port) { nil }
12+
let(:name) { nil }
1913

20-
context "with name 'smb'" do
21-
let(:name) { 'smb' }
14+
let(:service) do
15+
instance_double(Mdm::Service, port: port, name: name)
16+
end
2217

23-
it { is_expected.to include Metasploit::Framework::LoginScanner::SMB }
24-
it { is_expected.not_to include Metasploit::Framework::LoginScanner::HTTP }
25-
end
18+
context "with name 'smb'" do
19+
let(:name) { 'smb' }
2620

21+
it { is_expected.to include Metasploit::Framework::LoginScanner::SMB }
22+
it { is_expected.not_to include Metasploit::Framework::LoginScanner::HTTP }
23+
end
2724

28-
context "with port 445" do
29-
let(:port) { 445 }
25+
context "with port 445" do
26+
let(:port) { 445 }
3027

31-
it { is_expected.to include Metasploit::Framework::LoginScanner::SMB }
32-
it { is_expected.not_to include Metasploit::Framework::LoginScanner::HTTP }
33-
it { is_expected.not_to include Metasploit::Framework::LoginScanner::VNC }
34-
end
28+
it { is_expected.to include Metasploit::Framework::LoginScanner::SMB }
29+
it { is_expected.not_to include Metasploit::Framework::LoginScanner::HTTP }
30+
it { is_expected.not_to include Metasploit::Framework::LoginScanner::VNC }
31+
end
3532

33+
context "with name 'http'" do
34+
let(:name) { 'http' }
3635

37-
context "with name 'http'" do
38-
let(:name) { 'http' }
36+
it { is_expected.to include Metasploit::Framework::LoginScanner::HTTP }
37+
it { is_expected.not_to include Metasploit::Framework::LoginScanner::SMB }
38+
it { is_expected.not_to include Metasploit::Framework::LoginScanner::VNC }
39+
end
3940

40-
it { is_expected.to include Metasploit::Framework::LoginScanner::HTTP }
41-
it { is_expected.not_to include Metasploit::Framework::LoginScanner::SMB }
42-
it { is_expected.not_to include Metasploit::Framework::LoginScanner::VNC }
41+
[ 80, 8080, 8000, 443 ].each do |foo|
42+
context "with port #{foo}" do
43+
let(:port) { foo }
44+
45+
it { is_expected.to include Metasploit::Framework::LoginScanner::HTTP }
46+
it { is_expected.to include Metasploit::Framework::LoginScanner::Axis2 }
47+
it { is_expected.to include Metasploit::Framework::LoginScanner::Tomcat }
48+
it { is_expected.not_to include Metasploit::Framework::LoginScanner::SMB }
49+
end
50+
end
4351
end
4452

45-
[ 80, 8080, 8000, 443 ].each do |foo|
46-
context "with port #{foo}" do
47-
let(:port) { foo }
53+
describe '.all_http_classes' do
54+
let(:http_classes) { described_class.all_http_classes }
4855

49-
it { is_expected.to include Metasploit::Framework::LoginScanner::HTTP }
50-
it { is_expected.to include Metasploit::Framework::LoginScanner::Axis2 }
51-
it { is_expected.to include Metasploit::Framework::LoginScanner::Tomcat }
52-
it { is_expected.not_to include Metasploit::Framework::LoginScanner::SMB }
56+
it 'returns a populated array' do
57+
expect(http_classes).to be_a Array
58+
expect(http_classes).to_not be_empty
59+
end
60+
61+
it 'includes HTTP classes' do
62+
expect(http_classes).to include Metasploit::Framework::LoginScanner::TeamCity
63+
expect(http_classes).to include Metasploit::Framework::LoginScanner::Ivanti
64+
end
65+
66+
it 'does not include non-HTTP classes' do
67+
# Base HTTP scanner should not be present
68+
expect(http_classes).to_not include Metasploit::Framework::LoginScanner::HTTP
69+
expect(http_classes).to_not include Metasploit::Framework::LoginScanner::SMB
70+
expect(http_classes).to_not include Metasploit::Framework::LoginScanner::VNC
5371
end
5472
end
5573

@@ -69,39 +87,33 @@
6987
expect(service_names).to include 'https'
7088
expect(service_names).to include 'smb'
7189
end
72-
end
73-
74-
describe '.classes_for_service' do
75-
described_class.all_service_names.each do |service_name|
76-
context "with service #{service_name}" do
77-
let(:name) { service_name }
78-
let(:login_scanners) { described_class.classes_for_service(service) }
79-
80-
it 'returns at least one class' do
81-
expect(login_scanners).to_not be_empty
82-
end
83-
8490

85-
MockService = Struct.new(:name, :port)
86-
87-
described_class.classes_for_service(MockService.new(name: service_name)).each do |login_scanner|
88-
context "when the login scanner is #{login_scanner.name}" do
89-
it 'is a LoginScanner' do
90-
expect(login_scanner).to include Metasploit::Framework::LoginScanner::Base
91-
end
92-
93-
it 'can be initialized with a single argument' do
94-
expect {
95-
# here we emulate how Pro will initialize the class by passing a single configuration hash argument
96-
login_scanner.new({
97-
bruteforce_speed: 5,
98-
host: '192.0.2.1',
99-
port: 1234,
100-
stop_on_success: true
101-
})
102-
}.to_not raise_error
103-
end
91+
it 'returns a list of valid services' do
92+
all_scanners = service_names.flat_map do |service_name|
93+
service = instance_double Mdm::Service, name: service_name, port: nil
94+
classes = described_class.classes_for_service(service)
95+
expect(classes).to_not be_empty
96+
classes
97+
end.uniq
98+
expect(all_scanners).to_not be_empty
99+
100+
all_scanners.each do |scanner|
101+
# Emulate how Pro will initialize the class by passing a single configuration hash argument
102+
options = {
103+
bruteforce_speed: 5,
104+
host: '192.0.2.1',
105+
port: 1234,
106+
stop_on_success: true
107+
}
108+
aggregate_failures "#{scanner} is a valid scanner" do
109+
expect(scanner.const_defined?(:PRIVATE_TYPES)).to be(true), "for #{scanner}"
110+
expect(scanner.const_defined?(:LIKELY_SERVICE_NAMES)).to be(true), "for #{scanner}"
111+
expect(scanner.const_defined?(:LIKELY_PORTS)).to be(true), "for #{scanner}"
112+
if scanner.ancestors.include?(Metasploit::Framework::LoginScanner::HTTP) && scanner != Metasploit::Framework::LoginScanner::WinRM
113+
expect(scanner::LIKELY_SERVICE_NAMES).to include('http', 'https'), "for #{scanner}"
114+
expect(scanner::LIKELY_PORTS).to include(80, 443, 8000, 8080), "for #{scanner}"
104115
end
116+
expect { scanner.new(options) }.to_not raise_error, "for #{scanner}"
105117
end
106118
end
107119
end

0 commit comments

Comments
 (0)