@@ -15,12 +15,40 @@ class Redis
15
15
include Metasploit ::Framework ::LoginScanner ::RexSocket
16
16
include Metasploit ::Framework ::Tcp ::Client
17
17
18
+ # Required to be able to invoke the scan! method from the included Base module.
19
+ # We do not use inheritance, so overwriting a method and relying on super does
20
+ # not work in this case.
21
+ alias parent_scan! scan!
22
+
18
23
DEFAULT_PORT = 6379
19
24
LIKELY_PORTS = [ DEFAULT_PORT ]
20
25
LIKELY_SERVICE_NAMES = [ 'redis' ]
21
26
PRIVATE_TYPES = [ :password ]
22
27
REALM_KEY = nil
23
28
29
+ # Attempt to login with every {Credential credential} in
30
+ # {#cred_details}, by calling {#attempt_login} once for each.
31
+ #
32
+ # If a successful login is found for a user, no more attempts
33
+ # will be made for that user. If the scanner detects that no
34
+ # authentication is required, no further attempts will be made
35
+ # at all.
36
+ #
37
+ # @yieldparam result [Result] The {Result} object for each attempt
38
+ # @yieldreturn [void]
39
+ # @return [void]
40
+ def scan! ( &block )
41
+ first_credential = to_enum ( :each_credential ) . first
42
+ result = attempt_login ( first_credential )
43
+ result . freeze
44
+
45
+ if result . status == Metasploit ::Model ::Login ::Status ::NO_AUTH_REQUIRED
46
+ yield result if block_given?
47
+ else
48
+ parent_scan! ( &block )
49
+ end
50
+ end
51
+
24
52
# This method can create redis command which can be read by redis server
25
53
def redis_proto ( command_parts )
26
54
return if command_parts . blank?
@@ -45,17 +73,25 @@ def attempt_login(credential)
45
73
service_name : 'redis'
46
74
}
47
75
48
- disconnect if self . sock
76
+ disconnect if sock
49
77
50
78
begin
51
79
connect
52
80
select ( [ sock ] , nil , nil , 0.4 )
53
81
54
- command = redis_proto ( [ 'AUTH' , credential . private . to_s ] )
82
+ # Skip this call if we're dealing with an older redis version.
83
+ response = authenticate ( credential . public . to_s , credential . private . to_s ) unless @older_redis
55
84
56
- sock . put ( command )
85
+ # If we're dealing with an older redis version or the previous call failed,
86
+ # try the backwards compatibility call instead.
87
+ # We also set the @older_redis to true if we haven't as we might be entering this
88
+ # block from the match response.
89
+ if @older_redis || ( response && response . match ( ::Rex ::Proto ::Redis ::Base ::Constants ::WRONG_ARGUMENTS_FOR_AUTH ) )
90
+ @older_redis ||= true
91
+ response = authenticate_pre_v6 ( credential . private . to_s )
92
+ end
57
93
58
- result_options [ :proof ] = sock . get_once
94
+ result_options [ :proof ] = response
59
95
result_options [ :status ] = validate_login ( result_options [ :proof ] )
60
96
rescue Rex ::ConnectionError , EOFError , Timeout ::Error , Errno ::EPIPE => e
61
97
result_options . merge! (
@@ -64,13 +100,36 @@ def attempt_login(credential)
64
100
)
65
101
end
66
102
67
- disconnect if self . sock
103
+ disconnect if sock
68
104
69
105
::Metasploit ::Framework ::LoginScanner ::Result . new ( result_options )
70
106
end
71
107
72
108
private
73
109
110
+ # Authenticates against Redis using the provided credentials arguments.
111
+ # Takes either a password, or a username and password combination.
112
+ #
113
+ # @param [String] username The username to authenticate with, defaults to 'default'
114
+ # @param [String] password The password to authenticate with.
115
+ # @return [String] The response from Redis for the AUTH command.
116
+ def authenticate ( username , password )
117
+ command = redis_proto ( [ 'AUTH' , username . blank? ? 'default' : username , password ] )
118
+ sock . put ( command )
119
+ sock . get_once
120
+ end
121
+
122
+ # Authenticates against Redis using the provided password.
123
+ # This method is for older Redis instances of backwards compatibility.
124
+ #
125
+ # @param [String] password The password to authenticate with.
126
+ # @return [String] The response from Redis for the AUTH command.
127
+ def authenticate_pre_v6 ( password )
128
+ command = redis_proto ( [ 'AUTH' , password ] )
129
+ sock . put ( command )
130
+ sock . get_once
131
+ end
132
+
74
133
# Validates the login data received from Redis and returns the correct Login status
75
134
# based upon the contents Redis sent back:
76
135
#
0 commit comments