CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/metasploit/framework/login_scanner/redis.rb
Views: 1904
1
require 'metasploit/framework/login_scanner/base'
2
require 'metasploit/framework/login_scanner/rex_socket'
3
require 'metasploit/framework/tcp/client'
4
require 'rex/proto/redis'
5
6
module Metasploit
7
module Framework
8
module LoginScanner
9
10
# This is the LoginScanner class for dealing with REDIS.
11
# It is responsible for taking a single target, and a list of credentials
12
# and attempting them. It then saves the results.
13
class Redis
14
include Metasploit::Framework::LoginScanner::Base
15
include Metasploit::Framework::LoginScanner::RexSocket
16
include Metasploit::Framework::Tcp::Client
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
23
DEFAULT_PORT = 6379
24
LIKELY_PORTS = [ DEFAULT_PORT ]
25
LIKELY_SERVICE_NAMES = [ 'redis' ]
26
PRIVATE_TYPES = [ :password ]
27
REALM_KEY = nil
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
52
# This method can create redis command which can be read by redis server
53
def redis_proto(command_parts)
54
return if command_parts.blank?
55
56
command = "*#{command_parts.length}\r\n"
57
command_parts.each do |p|
58
command << "$#{p.length}\r\n#{p}\r\n"
59
end
60
command
61
end
62
63
# This method attempts a single login with a single credential against the target
64
# @param credential [Credential] The credential object to attempt to login with
65
# @return [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object
66
def attempt_login(credential)
67
result_options = {
68
credential: credential,
69
status: Metasploit::Model::Login::Status::INCORRECT,
70
host: host,
71
port: port,
72
protocol: 'tcp',
73
service_name: 'redis'
74
}
75
76
disconnect if sock
77
78
begin
79
connect
80
select([sock], nil, nil, 0.4)
81
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
84
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
93
94
result_options[:proof] = response
95
result_options[:status] = validate_login(result_options[:proof])
96
rescue Rex::ConnectionError, EOFError, Timeout::Error, Errno::EPIPE => e
97
result_options.merge!(
98
proof: e,
99
status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
100
)
101
end
102
103
disconnect if sock
104
105
::Metasploit::Framework::LoginScanner::Result.new(result_options)
106
end
107
108
private
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
133
# Validates the login data received from Redis and returns the correct Login status
134
# based upon the contents Redis sent back:
135
#
136
# No password - ( -ERR Client sent AUTH, but no password is set\r\n )
137
# Invalid password - ( -ERR invalid password\r\n )
138
# Valid password - (+OK\r\n)
139
def validate_login(data)
140
return if data.nil?
141
142
return Metasploit::Model::Login::Status::NO_AUTH_REQUIRED if no_password_set?(data)
143
return Metasploit::Model::Login::Status::INCORRECT if invalid_password?(data)
144
return Metasploit::Model::Login::Status::SUCCESSFUL if data.match(::Rex::Proto::Redis::Base::Constants::OKAY)
145
146
nil
147
end
148
149
def no_password_set?(data)
150
data.match(::Rex::Proto::Redis::Base::Constants::NO_PASSWORD_SET) ||
151
data.match(::Rex::Proto::Redis::Version6::Constants::NO_PASSWORD_SET)
152
end
153
154
def invalid_password?(data)
155
data.match(::Rex::Proto::Redis::Base::Constants::WRONG_PASSWORD) ||
156
data.match(::Rex::Proto::Redis::Version6::Constants::WRONG_PASSWORD)
157
end
158
159
# (see Base#set_sane_defaults)
160
def set_sane_defaults
161
self.connection_timeout ||= 30
162
self.port ||= DEFAULT_PORT
163
self.max_send_size ||= 0
164
self.send_delay ||= 0
165
end
166
end
167
end
168
end
169
end
170
171