-
-
Notifications
You must be signed in to change notification settings - Fork 262
Introduction to writing modules
A module needs to be placed in either the auxiliary or exploit namespace. If your module does not support or require a payload to be used (i.e. executing of arbitrary PHP code on the remote system), then your module should be considered an auxiliary module.
To declare your module in the auxiliary namespace, simply prefix the class name with Wpxf::Auxiliary
:
class Wpxf::Auxiliary::YourModuleName < Wpxf::Module
end
Examples of auxiliary modules are:
- Modules which use SQL injection vulnerabilities to extract information from the target
- Modules which allow for files to be downloaded from the target
- Modules which provide directory listing disclosures on the target system
- Modules which escalate privileges on the target system
Alternatively, if your module does need to utilize a payload, it can be declared as an exploit as below:
class Wpxf::Exploit::YourModuleName < Wpxf::Module
end
In the initialize
method of your module, the first thing that should be done (after calling super
) is to register the module information. This will be used to serve information about your module when the user executes the info
command and will also appear in search results.
As can be seen in the code extract below, taken from the auxiliary/dos/load_scripts_dos module, we can use the update_info
method to specify a hash of data that defines the module.
update_info(
name: 'WordPress "load-scripts.php" DoS',
desc: %(
All versions of WordPress, as of March, 2018, are vulnerable to a
denial of service attack by making large amounts of requests to the
load-scripts.php file. This module allows users to configure a maximum
number of requests (via `max_requests`), and the number of threads to
use (`max_http_concurrency`) and will execute the requests and then
check the status of the website.
),
author: [
'Barak Tawily', # Vulnerability disclosure
'rastating' # WPXF module
],
references: [
['CVE', '2018-6389'],
['WPVDB', '9021'],
['URL', 'https://baraktawily.blogspot.co.uk/2018/02/how-to-dos-29-of-world-wide-websites.html']
],
date: 'Feb 05 2018'
)
The keys available to the update_info
method are as follows:
Name | Description | Required? |
---|---|---|
name | The name of the module | Yes |
desc | The description of the module | Yes |
author | An array of author names | Yes |
references | An array of reference arrays | No |
date | The date the vulnerability that the module uses was disclosed | Yes |
When specifying the references, the first element in each reference array is the type identifier and the second element is the reference identifier, or the URL if using the URL reference type. All supported reference types are as follows:
Key | Description |
---|---|
CVE | A CVE-ID reference |
EDB | An Exploit DB reference |
OSVDB | An OSVDB reference |
URL | A URL with relevant information (such as initial public disclosure) |
WPVDB | A WPScan Vulnerability Database reference |
To allow users to configure the module you need to register and use options in the module. In the auxiliary/dos/post_grid_file_deletion module, an option is registered to allow the user to specify the file to delete.
register_options([
StringOption.new(
name: 'remote_file',
desc: 'The relative or absolute path of the file to delete (relative to /wp-admin/)',
required: true
)
])
For a full list of available keys that can be specified for an option, have a look at the documentation for the initialize
method for the appropriate option class.
To access the value that the user has specified, you can use get_option_value, normalized_option_value or access the raw value entered directly using the datastore
attribute.
If we had a boolean option called "test", these three methods of accessing its value would appear as follows if the option was set to 1 by the user using set test 1
:
puts get_option_value('test') # Outputs 1
puts normalized_option_value('test') # Outputs true
puts datastore['test'] # Outputs 1
The option classes available are:
In most cases, there are non-intrusive checks we can perform to determine whether or not the target is vulnerable to the module we're writing. If this is the case, you should implement the check method.
The return value of check
indicates to WPXF whether or not the module is vulnerable and accepts the following values:
Return Value | Description |
---|---|
:safe | The target is not vulnerable to this module |
:vulnerable | The target appears to be vulnerable to this module |
:unknown | A status could not be determined for the target |
Usually, a target's vulnerability status can be determined by doing some basic fingerprinting. To aid with this, a number of helper methods can be found in the Wpxf::WordPress::Fingerprint mixin.
All the code used to run the module should be placed in the run
method. This method returns true if the module was executed successfully or false if anything went wrong.
The first line of the method should be a call to super
, which will verify that the target is running WordPress and is online or return false. If you wanted to make a module that verifies the target is online and running WordPress and then outputs an arbitrary string to the console, you could implement like this:
def run
return false unless super # Verify that it's online or return false
emit_info 'Hello world!' # Output "Hello world!" to the console
true # Return true to indicate the module ran successfully
end
The Wpxf::Net::HttpClient mixin provides a variety of helper functions in order to make HTTP interaction easier. All of the methods provided to execute HTTP requests return a Wpxf::Net::HttpResponse which contains the response code
, body
, headers
, cookies
and a boolean indicating whether or not the request timed_out?
.
Below are some examples taken from existing modules that demonstrate how the various methods can be used.
res = download_file(
url: downloader_url,
method: :get,
params: { 'file_link' => remote_file },
local_filename: export_path
)
res = execute_get_request(url: payload_url)
res = execute_post_request(
url: wordpress_url_admin_ajax,
params: { 'action' => 'import_data' },
body: { 'name' => name, 'code' => encoded_value },
cookie: cookie
)
def payload_body_builder(payload_name)
builder = Utility::BodyBuilder.new
builder.add_field('elementCode', 'ajaxUpload')
builder.add_file_from_string('wpshop_file', payload.encoded, payload_name)
builder
end
builder = payload_body_builder(payload_name)
emit_info 'Uploading payload...'
res = nil
builder.create do |body|
res = execute_post_request(url: uploader_url, body: body)
end
In the context of an exploit module, you will have access to the payload
object, which is an instance of Wpxf::Payload. This object allows you to access the encoded
payload and the payload in its raw
form. In most cases, the method you'll want to use is encoded
.
Included in WPXF is a helper method for uploading the selected payload as a WordPress plugin, for instances where you are able to acquire a valid WordPress admin session.
Below is an example taken from exploit/shell/admin_shell_upload which demonstrates how to do this:
emit_info 'Uploading payload...'
res = upload_payload_as_plugin_and_execute(
Utility::Text.rand_alpha(10),
Utility::Text.rand_alpha(10),
session_cookie
)
In order for this example to work, the requires_authentication method must be overridden and return true
; otherwise, session_cookie
will be nil
. Alternatively, as long as a valid session cookie is passed into upload_payload_as_plugin_and_execute
, the use of requires_authentication is not needed.
You are encouraged to use the emit_*
methods wherever possible to output information to the screen as this allows for a consistent output style to be maintained, however, falling back to puts
, print
etc. may be required in some instances and within reason is acceptable.
Documentation for all the emit_*
methods can be found in the documentation here: https://rastating.github.io/wordpress-exploit-framework/Wpxf/OutputEmitters.html
For more information about the WPXF API, take a look at the documentation at https://rastating.github.io/wordpress-exploit-framework