Skip to content

Commit df34274

Browse files
committed
Land rapid7#6409, Add auxiliary/scanner/redis/redis_login
This also changes: * The Msf::Auxiliary::Redis for the naming & PASSWORD datastore option * auxiliary/scanner/redis/redis_server module name * Removes auxiliary/scanner/misc/redis_server, because it was deprecated.
2 parents 5873803 + 618f379 commit df34274

File tree

5 files changed

+187
-87
lines changed

5 files changed

+187
-87
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
require 'metasploit/framework/login_scanner/base'
2+
require 'metasploit/framework/login_scanner/rex_socket'
3+
require 'metasploit/framework/tcp/client'
4+
5+
module Metasploit
6+
module Framework
7+
module LoginScanner
8+
9+
# This is the LoginScanner class for dealing with REDIS.
10+
# It is responsible for taking a single target, and a list of credentials
11+
# and attempting them. It then saves the results.
12+
13+
class Redis
14+
include Metasploit::Framework::LoginScanner::Base
15+
include Metasploit::Framework::LoginScanner::RexSocket
16+
include Metasploit::Framework::Tcp::Client
17+
18+
DEFAULT_PORT = 6379
19+
LIKELY_PORTS = [ DEFAULT_PORT ]
20+
LIKELY_SERVICE_NAMES = [ 'redis' ]
21+
PRIVATE_TYPES = [ :password ]
22+
REALM_KEY = nil
23+
24+
# This method can create redis command which can be read by redis server
25+
def redis_proto(command_parts)
26+
return if command_parts.blank?
27+
command = "*#{command_parts.length}\r\n"
28+
command_parts.each do |p|
29+
command << "$#{p.length}\r\n#{p}\r\n"
30+
end
31+
command
32+
end
33+
34+
# This method attempts a single login with a single credential against the target
35+
# @param credential [Credential] The credential object to attempt to login with
36+
# @return [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object
37+
def attempt_login(credential)
38+
result_options = {
39+
credential: credential,
40+
status: Metasploit::Model::Login::Status::INCORRECT,
41+
host: host,
42+
port: port,
43+
protocol: 'tcp',
44+
service_name: 'redis'
45+
}
46+
47+
disconnect if self.sock
48+
49+
begin
50+
connect
51+
select([sock], nil, nil, 0.4)
52+
53+
command = redis_proto(['AUTH', "#{credential.private}"])
54+
sock.put(command)
55+
result_options[:proof] = sock.get_once
56+
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
68+
69+
rescue Rex::ConnectionError, EOFError, Timeout::Error, Errno::EPIPE => e
70+
result_options.merge!(
71+
proof: e,
72+
status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
73+
)
74+
end
75+
disconnect if self.sock
76+
::Metasploit::Framework::LoginScanner::Result.new(result_options)
77+
end
78+
79+
private
80+
81+
# (see Base#set_sane_defaults)
82+
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
87+
end
88+
end
89+
end
90+
end
91+
end

lib/msf/core/auxiliary/redis.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def initialize(info = {})
2020
register_options(
2121
[
2222
Opt::RPORT(6379),
23-
OptString.new('Password', [false, 'Redis password for authentication test', 'foobared'])
23+
OptString.new('PASSWORD', [false, 'Redis password for authentication test', 'foobared'])
2424
]
2525
)
2626

@@ -54,7 +54,7 @@ def redis_command(*commands)
5454
if /(?<auth_response>ERR operation not permitted|NOAUTH Authentication required)/i =~ command_response
5555
fail_with(::Msf::Module::Failure::BadConfig, "#{peer} requires authentication but Password unset") unless datastore['Password']
5656
vprint_status("Requires authentication (#{printable_redis_response(auth_response, false)})")
57-
if (auth_response = send_redis_command('AUTH', datastore['Password']))
57+
if (auth_response = send_redis_command('AUTH', datastore['PASSWORD']))
5858
unless auth_response =~ /\+OK/
5959
vprint_error("Authentication failure: #{printable_redis_response(auth_response)}")
6060
return

modules/auxiliary/scanner/misc/redis_server.rb

Lines changed: 0 additions & 84 deletions
This file was deleted.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
require 'metasploit/framework/login_scanner/redis'
8+
require 'metasploit/framework/credential_collection'
9+
10+
class MetasploitModule < Msf::Auxiliary
11+
12+
include Msf::Exploit::Remote::Tcp
13+
include Msf::Auxiliary::Scanner
14+
include Msf::Auxiliary::Report
15+
include Msf::Auxiliary::AuthBrute
16+
include Msf::Auxiliary::Redis
17+
18+
def initialize(info = {})
19+
super(
20+
update_info(
21+
info,
22+
'Name' => 'Redis Login Utility',
23+
'Description' => 'This module attempts to authenticate to an REDIS service.',
24+
'Author' => [ 'Nixawk' ],
25+
'References' => [
26+
['URL', 'http://redis.io/topics/protocol']
27+
],
28+
'License' => MSF_LICENSE))
29+
30+
register_options(
31+
[
32+
OptPath.new('PASS_FILE',
33+
[
34+
false,
35+
'The file that contains a list of of probable passwords.',
36+
File.join(Msf::Config.install_root, 'data', 'wordlists', 'unix_passwords.txt')
37+
])
38+
], self.class)
39+
40+
# redis does not have an username, there's only password
41+
deregister_options('USERNAME', 'USER_AS_PASS', 'USERPASS_FILE', 'USER_FILE', 'DB_ALL_USERS', 'DB_ALL_CREDS')
42+
end
43+
44+
def run_host(ip)
45+
cred_collection = Metasploit::Framework::CredentialCollection.new(
46+
blank_passwords: datastore['BLANK_PASSWORDS'],
47+
pass_file: datastore['PASS_FILE'],
48+
password: datastore['PASSWORD'],
49+
# The LoginScanner API refuses to run if there's no username, so we give it a fake one.
50+
# But we will not be reporting this to the database.
51+
username: 'redis'
52+
)
53+
54+
cred_collection = prepend_db_passwords(cred_collection)
55+
56+
scanner = Metasploit::Framework::LoginScanner::Redis.new(
57+
host: ip,
58+
port: rport,
59+
proxies: datastore['PROXIES'],
60+
cred_details: cred_collection,
61+
stop_on_success: datastore['STOP_ON_SUCCESS'],
62+
connection_timeout: 30
63+
)
64+
65+
scanner.scan! do |result|
66+
credential_data = result.to_h
67+
credential_data.merge!(
68+
module_fullname: self.fullname,
69+
workspace_id: myworkspace_id
70+
)
71+
72+
case result.status
73+
when Metasploit::Model::Login::Status::SUCCESSFUL
74+
credential_data.delete(:username) # This service uses no username
75+
credential_core = create_credential(credential_data)
76+
credential_data[:core] = credential_core
77+
create_credential_login(credential_data)
78+
79+
if datastore['VERBOSE']
80+
vprint_good "#{peer} - LOGIN SUCCESSFUL: #{result.credential} (#{result.status}: #{result.proof})"
81+
else
82+
print_good "#{peer} - LOGIN SUCCESSFUL: #{result.credential}"
83+
end
84+
when Metasploit::Model::Login::Status::NO_AUTH_REQUIRED
85+
vprint_error "#{peer} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})"
86+
break
87+
else
88+
invalidate_login(credential_data)
89+
vprint_error "#{peer} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})"
90+
end
91+
end
92+
end
93+
end

modules/auxiliary/scanner/redis/redis_server.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class MetasploitModule < Msf::Auxiliary
1212

1313
def initialize(info = {})
1414
super(update_info(info,
15-
'Name' => 'Redis Scanner',
15+
'Name' => 'Redis Command Execute Scanner',
1616
'Description' => %q(
1717
This module locates Redis endpoints by attempting to run a specified
1818
Redis command.

0 commit comments

Comments
 (0)