Skip to content

Commit afa973e

Browse files
committed
Fix reids_login scanner when auth is enabled
1 parent 4c81b39 commit afa973e

File tree

3 files changed

+195
-11
lines changed

3 files changed

+195
-11
lines changed

documentation/modules/auxiliary/scanner/redis/redis_login.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,36 @@ Note that Redis does not require a username to log in; login is done purely via
88

99
A complete installation guide for Redis can be found [here](https://redis.io/topics/quickstart)
1010

11+
## Setup
12+
13+
Run redis in docker without auth:
14+
15+
```
16+
docker run --rm -p 6379:6379 redis
17+
```
18+
19+
Optionally setting the default password for the implicit `default` username account, connect to the running Redis instance and set a password:
20+
21+
```
22+
$ nc 127.0.0.1 6379
23+
config set requirepass mypass
24+
+OK
25+
```
26+
27+
Optionally creating an enabled `test_user` user account with password `mypass` - if ACL is supported (Redis >= 6.0.0):
28+
29+
```
30+
$ nc 127.0.0.1 6379
31+
ACL SETUSER test_user allkeys on +@string +@set -SADD >mypass
32+
```
33+
34+
Optionally creating a disabled `test_user_disabled` user account with password `mypass` - if ACL is supported (Redis >= 6.0.0):
35+
36+
```
37+
$ nc 127.0.0.1 6379
38+
ACL SETUSER test_user_disabled allkeys off +@string +@set -SADD >mypass
39+
```
40+
1141
## Verification Steps
1242
1. Do: `use auxiliary/scanner/redis/redis_login`
1343
2. Do: `set RHOSTS [ips]`

lib/metasploit/framework/credential_collection.rb

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,10 @@ def prepend_cred(cred)
8888
# @yieldparam credential [Metasploit::Framework::Credential]
8989
# @return [void]
9090
def each_filtered
91-
if password_spray
92-
each_unfiltered_password_first do |credential|
93-
next unless self.filter.nil? || self.filter.call(credential)
94-
95-
yield credential
96-
end
97-
else
98-
each_unfiltered_username_first do |credential|
99-
next unless self.filter.nil? || self.filter.call(credential)
91+
each_unfiltered do |credential|
92+
next unless self.filter.nil? || self.filter.call(credential)
10093

101-
yield credential
102-
end
94+
yield credential
10395
end
10496
end
10597

@@ -121,6 +113,9 @@ def each_unfiltered
121113
if blank_passwords
122114
yield Metasploit::Framework::Credential.new(private: "", realm: realm, private_type: :password)
123115
end
116+
if nil_passwords
117+
yield Metasploit::Framework::Credential.new(private: nil, realm: realm, private_type: :password)
118+
end
124119
if pass_fd
125120
pass_fd.each_line do |pass_from_file|
126121
pass_from_file.chomp!
@@ -177,6 +172,12 @@ def private_type(private)
177172
end
178173

179174
class CredentialCollection < PrivateCredentialCollection
175+
# @!attribute password_spray
176+
# Whether password spray is enabled. When true, each password is tried against each username first.
177+
# Otherwise the default bruteforce logic will attempt all passwords against the first user, before
178+
# continuing to the next user
179+
#
180+
# @return [Boolean]
180181
attr_accessor :password_spray
181182

182183
# @!attribute additional_publics
@@ -233,6 +234,29 @@ def add_public(public_str='')
233234
additional_publics << public_str
234235
end
235236

237+
# Combines all the provided credential sources into a stream of {Credential}
238+
# objects, yielding them one at a time
239+
#
240+
# @yieldparam credential [Metasploit::Framework::Credential]
241+
# @return [void]
242+
def each_filtered
243+
if password_spray
244+
each_unfiltered_password_first do |credential|
245+
next unless self.filter.nil? || self.filter.call(credential)
246+
247+
yield credential
248+
end
249+
else
250+
each_unfiltered_username_first do |credential|
251+
next unless self.filter.nil? || self.filter.call(credential)
252+
253+
yield credential
254+
end
255+
end
256+
end
257+
258+
alias each each_filtered
259+
236260
# When password spraying is enabled, do first passwords then usernames
237261
# i.e.
238262
# username1:password1
@@ -334,6 +358,9 @@ def each_unfiltered_password_first
334358
if blank_passwords
335359
yield Metasploit::Framework::Credential.new(public: add_public, private: "", realm: realm, private_type: :password)
336360
end
361+
if nil_passwords
362+
yield Metasploit::Framework::Credential.new(public: add_public, private: nil, realm: realm, private_type: :password)
363+
end
337364
if user_fd
338365
user_fd.each_line do |user_from_file|
339366
user_from_file.chomp!
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
require 'spec_helper'
2+
require 'metasploit/framework/credential_collection'
3+
4+
RSpec.describe Metasploit::Framework::PrivateCredentialCollection do
5+
6+
subject(:collection) do
7+
described_class.new(
8+
nil_passwords: nil_passwords,
9+
blank_passwords: blank_passwords,
10+
pass_file: pass_file,
11+
password: password,
12+
prepended_creds: prepended_creds,
13+
additional_privates: additional_privates
14+
)
15+
end
16+
17+
before(:each) do
18+
# The test suite overrides File.open(...) calls; fall back to the normal behavior for any File.open calls that aren't explicitly mocked
19+
allow(File).to receive(:open).with(anything).and_call_original
20+
allow(File).to receive(:open).with(anything, anything).and_call_original
21+
allow(File).to receive(:open).with(anything, anything, anything).and_call_original
22+
end
23+
24+
let(:nil_passwords) { nil }
25+
let(:blank_passwords) { nil }
26+
let(:password) { "pass" }
27+
let(:pass_file) { nil }
28+
# PrivateCredentialCollection yields `nil` as the username; unlike CredentialCollection
29+
let(:username) { nil }
30+
let(:prepended_creds) { [] }
31+
let(:additional_privates) { [] }
32+
33+
describe "#each" do
34+
specify do
35+
expect { |b| collection.each(&b) }.to yield_with_args(Metasploit::Framework::Credential)
36+
end
37+
38+
context "when given a pass_file" do
39+
let(:password) { nil }
40+
let(:pass_file) do
41+
filename = "foo"
42+
stub_file = StringIO.new("asdf\njkl\n")
43+
allow(File).to receive(:open).with(filename,/^r/).and_return stub_file
44+
45+
filename
46+
end
47+
48+
specify do
49+
expect { |b| collection.each(&b) }.to yield_successive_args(
50+
Metasploit::Framework::Credential.new(public: username, private: "asdf"),
51+
Metasploit::Framework::Credential.new(public: username, private: "jkl"),
52+
)
53+
end
54+
end
55+
56+
context "when :nil_passwords is true" do
57+
let(:nil_passwords) { true }
58+
specify do
59+
expect { |b| collection.each(&b) }.to yield_successive_args(
60+
Metasploit::Framework::Credential.new(public: username, private: password),
61+
Metasploit::Framework::Credential.new(public: username, private: nil),
62+
)
63+
end
64+
end
65+
66+
context "when :blank_passwords is true" do
67+
let(:blank_passwords) { true }
68+
specify do
69+
expect { |b| collection.each(&b) }.to yield_successive_args(
70+
Metasploit::Framework::Credential.new(public: username, private: password),
71+
Metasploit::Framework::Credential.new(public: username, private: ""),
72+
)
73+
end
74+
end
75+
76+
end
77+
78+
describe "#empty?" do
79+
context "when :password is not set" do
80+
let(:username) { nil }
81+
let(:password) { nil }
82+
specify do
83+
expect(collection.empty?).to eq true
84+
end
85+
86+
context "and :prepended_creds is not empty" do
87+
let(:prepended_creds) { [ "test" ] }
88+
specify do
89+
expect(collection.empty?).to eq false
90+
end
91+
end
92+
93+
context "and :additional_privates is not empty" do
94+
let(:additional_privates) { [ "test_private" ] }
95+
specify do
96+
expect(collection.empty?).to eq false
97+
end
98+
end
99+
100+
context "and :additional_publics is not empty" do
101+
let(:additional_publics) { [ "test_public" ] }
102+
specify do
103+
expect(collection.empty?).to eq true
104+
end
105+
end
106+
end
107+
108+
context "when :password is set" do
109+
let(:password) { 'pass' }
110+
specify do
111+
expect(collection.empty?).to eq false
112+
end
113+
end
114+
end
115+
116+
describe "#prepend_cred" do
117+
specify do
118+
prep = Metasploit::Framework::Credential.new(public: "foo", private: "bar")
119+
collection.prepend_cred(prep)
120+
expect { |b| collection.each(&b) }.to yield_successive_args(
121+
prep,
122+
Metasploit::Framework::Credential.new(public: username, private: password),
123+
)
124+
end
125+
end
126+
127+
end

0 commit comments

Comments
 (0)