Skip to content

Commit 9343a35

Browse files
authored
Land rapid7#19283, MS-9445 Fix Redis Service Reporting
2 parents 580e7ff + 51176e7 commit 9343a35

File tree

2 files changed

+45
-26
lines changed

2 files changed

+45
-26
lines changed

lib/metasploit/framework/login_scanner/redis.rb

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,26 @@ module LoginScanner
99
# This is the LoginScanner class for dealing with REDIS.
1010
# It is responsible for taking a single target, and a list of credentials
1111
# and attempting them. It then saves the results.
12-
1312
class Redis
1413
include Metasploit::Framework::LoginScanner::Base
1514
include Metasploit::Framework::LoginScanner::RexSocket
1615
include Metasploit::Framework::Tcp::Client
1716

18-
DEFAULT_PORT = 6379
19-
LIKELY_PORTS = [ DEFAULT_PORT ]
20-
LIKELY_SERVICE_NAMES = [ 'redis' ]
21-
PRIVATE_TYPES = [ :password ]
22-
REALM_KEY = nil
17+
DEFAULT_PORT = 6379
18+
LIKELY_PORTS = [ DEFAULT_PORT ]
19+
LIKELY_SERVICE_NAMES = [ 'redis' ]
20+
PRIVATE_TYPES = [ :password ]
21+
REALM_KEY = nil
22+
OLD_PASSWORD_NOT_SET = /but no password is set/i
23+
PASSWORD_NOT_SET = /without any password configured/i
24+
WRONG_PASSWORD_SET = /^-WRONGPASS/i
25+
INVALID_PASSWORD_SET = /^-ERR invalid password/i
26+
OK = /^\+OK/
2327

2428
# This method can create redis command which can be read by redis server
2529
def redis_proto(command_parts)
2630
return if command_parts.blank?
31+
2732
command = "*#{command_parts.length}\r\n"
2833
command_parts.each do |p|
2934
command << "$#{p.length}\r\n#{p}\r\n"
@@ -50,40 +55,50 @@ def attempt_login(credential)
5055
connect
5156
select([sock], nil, nil, 0.4)
5257

53-
command = redis_proto(['AUTH', "#{credential.private}"])
54-
sock.put(command)
55-
result_options[:proof] = sock.get_once
58+
command = redis_proto(['AUTH', credential.private.to_s])
5659

57-
# No password - ( -ERR Client sent AUTH, but no password is set\r\n )
58-
# Invalid password - ( -ERR invalid password\r\n )
59-
# Valid password - (+OK\r\n)
60-
61-
if result_options[:proof] && result_options[:proof] =~ /but no password is set/i
62-
result_options[:status] = Metasploit::Model::Login::Status::NO_AUTH_REQUIRED
63-
elsif result_options[:proof] && result_options[:proof] =~ /^-ERR invalid password/i
64-
result_options[:status] = Metasploit::Model::Login::Status::INCORRECT
65-
elsif result_options[:proof] && result_options[:proof][/^\+OK/]
66-
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
67-
end
60+
sock.put(command)
6861

62+
result_options[:proof] = sock.get_once
63+
result_options[:status] = validate_login(result_options[:proof])
6964
rescue Rex::ConnectionError, EOFError, Timeout::Error, Errno::EPIPE => e
7065
result_options.merge!(
7166
proof: e,
7267
status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
7368
)
7469
end
70+
7571
disconnect if self.sock
72+
7673
::Metasploit::Framework::LoginScanner::Result.new(result_options)
7774
end
7875

7976
private
8077

78+
# Validates the login data received from Redis and returns the correct Login status
79+
# based upon the contents Redis sent back:
80+
#
81+
# No password - ( -ERR Client sent AUTH, but no password is set\r\n )
82+
# Invalid password - ( -ERR invalid password\r\n )
83+
# Valid password - (+OK\r\n)
84+
def validate_login(data)
85+
return if data.nil?
86+
87+
return Metasploit::Model::Login::Status::NO_AUTH_REQUIRED if data =~ OLD_PASSWORD_NOT_SET
88+
return Metasploit::Model::Login::Status::NO_AUTH_REQUIRED if data =~ PASSWORD_NOT_SET
89+
return Metasploit::Model::Login::Status::INCORRECT if (data =~ INVALID_PASSWORD_SET) == 0
90+
return Metasploit::Model::Login::Status::INCORRECT if (data =~ WRONG_PASSWORD_SET) == 0
91+
return Metasploit::Model::Login::Status::SUCCESSFUL if (data =~ OK) == 0
92+
93+
nil
94+
end
95+
8196
# (see Base#set_sane_defaults)
8297
def set_sane_defaults
83-
self.connection_timeout ||= 30
84-
self.port ||= DEFAULT_PORT
85-
self.max_send_size ||= 0
86-
self.send_delay ||= 0
98+
self.connection_timeout ||= 30
99+
self.port ||= DEFAULT_PORT
100+
self.max_send_size ||= 0
101+
self.send_delay ||= 0
87102
end
88103
end
89104
end

lib/msf/core/db_manager/vuln.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ def has_vuln?(name)
8484
# opts can contain
8585
# +:info+:: a human readable description of the vuln, free-form text
8686
# +:refs+:: an array of Ref objects or string names of references
87-
# +:details:: a hash with :key pointed to a find criteria hash and the rest containing VulnDetail fields
87+
# +:details+:: a hash with :key pointed to a find criteria hash and the rest containing VulnDetail fields
88+
# +:sname+:: the name of the service this vulnerability relates to, used to associate it or create it.
8889
#
8990
def report_vuln(opts)
9091
return if not active
@@ -150,6 +151,7 @@ def report_vuln(opts)
150151
case opts[:proto].to_s.downcase # Catch incorrect usages, as in report_note
151152
when 'tcp','udp'
152153
proto = opts[:proto]
154+
sname = opts[:sname]
153155
when 'dns','snmp','dhcp'
154156
proto = 'udp'
155157
sname = opts[:proto]
@@ -158,7 +160,9 @@ def report_vuln(opts)
158160
sname = opts[:proto]
159161
end
160162

161-
service = host.services.where(port: opts[:port].to_i, proto: proto).first_or_create
163+
services = host.services.where(port: opts[:port].to_i, proto: proto)
164+
services = services.where(name: sname) if sname.present?
165+
service = services.first_or_create
162166
end
163167

164168
# Try to find an existing vulnerability with the same service & references

0 commit comments

Comments
 (0)