Skip to main content

Free 30-min security demo  — We'll scan your real code and show live findings, no commitment Book Now

Offensive360
ZeroDays CVE-2026-59257
High CVE-2026-59257 CVSS 8.8 n8n TypeScript

SQL Injection in n8n MySQL v1 executeQuery

CVE-2026-59257 is a SQL injection flaw in n8n's legacy MySQL v1 node that allows attackers to execute arbitrary SQL via expression interpolation.

Offensive360 Research Team
Affects: < 1.123.61, 2.x < 2.27.4, 2.28.x < 2.28.1
Source Code

Overview

CVE-2026-59257 is a classic SQL injection vulnerability residing in n8n’s legacy MySQL v1 node, specifically within its executeQuery operation. n8n is a widely deployed, self-hostable workflow automation platform used by enterprises to connect internal systems, APIs, and databases. Its node-based architecture allows users to build pipelines that process and route data between services — including relational databases — making the security posture of individual nodes critical to the overall attack surface.

The vulnerability arises because the MySQL v1 node evaluates n8n expression syntax ({{ ... }}) embedded inside raw SQL strings and interpolates the resulting values directly into the query before execution, with no parameterization or escaping applied. When a workflow chains a public-facing trigger — such as an HTTP Webhook node — to the MySQL v1 node’s executeQuery operation, any attacker who can reach that webhook endpoint gains the ability to inject arbitrary SQL statements executed under the privileges of the configured MySQL credential.

Security researchers identified this flaw across a wide swath of n8n releases, spanning the entire 1.x line prior to 1.123.61 and the 2.x line prior to 2.27.4 (with a specific backport fix landing in 2.28.1). Organizations running self-hosted n8n instances — whether on-premises or in cloud environments — that expose webhook endpoints to untrusted networks are directly in scope. SaaS-managed deployments that restrict expression-based MySQL node access may have a reduced but non-zero exposure depending on tenant isolation controls.

Technical Analysis

The root cause is straightforward: the MySQL v1 node constructs its query string by performing n8n expression evaluation on the raw SQL provided by the workflow designer, then hands that evaluated string directly to the MySQL driver for execution as a plain query string rather than a parameterized statement.

A simplified representation of the vulnerable code pattern looks like this:

// VULNERABLE — MySQL v1 node executeQuery (simplified)
async function executeQuery(
  this: IExecuteFunctions,
  connection: mysql2.Connection,
  index: number
): Promise<INodeExecutionData[]> {
  // User-supplied SQL template from node parameters, e.g.:
  // "SELECT * FROM users WHERE id = {{ $json["userId"] }}"
  const rawQuery = this.getNodeParameter('query', index) as string;

  // Expression evaluation resolves {{ ... }} blocks to their runtime values.
  // The result is a plain string — no type information, no escaping.
  const evaluatedQuery = this.evaluateExpression(rawQuery, index) as string;

  // evaluatedQuery is passed directly to the driver — no parameterization.
  const [rows] = await connection.promise().query(evaluatedQuery);

  return this.helpers.returnJsonArray(rows as IDataObject[]);
}

Consider a workflow where a Webhook node accepts a POST body { "userId": "1" } and feeds $json["userId"] into the SQL template SELECT * FROM users WHERE id = {{ $json["userId"] }}. After expression evaluation the driver receives the literal string SELECT * FROM users WHERE id = 1, which is benign. Now consider an attacker posting { "userId": "1 UNION SELECT table_name,2,3 FROM information_schema.tables-- -" }. The driver receives:

SELECT * FROM users WHERE id = 1 UNION SELECT table_name,2,3 FROM information_schema.tables-- -

This is a fully formed, multi-statement-capable SQL injection. Because evaluateExpression returns an untyped string and connection.query() accepts an arbitrary string, there is no layer between attacker input and the database engine. The contrast with the MySQL v2 node is instructive: v2 uses connection.execute() with a query template and a separate values array, which the underlying mysql2 driver binds server-side (or client-side with proper escaping), making the injection path structurally impossible regardless of input content.

The CVSS 8.8 (High) score reflects Network attack vector, Low complexity, No privileges required, No user interaction, and High confidentiality/integrity/availability impact — consistent with an unauthenticated network-reachable SQL injection where the only prerequisite is that the vulnerable workflow is live.

Impact

An attacker who can reach a webhook-triggered workflow using the MySQL v1 executeQuery operation can:

  • Exfiltrate data — dump any table readable by the configured MySQL user, including credentials, PII, financial records, or application secrets stored in the database.
  • Modify or destroy data — if the credential has INSERT, UPDATE, or DELETE privileges, the attacker can alter or wipe records. With DROP privileges, entire schemas are at risk.
  • Escalate within MySQL — if the credential holds FILE privilege or access to mysql.user, the attacker may read OS-level files (LOAD DATA INFILE) or manipulate authentication tables.
  • Pivot to other systems — in environments where the MySQL instance has network access to other internal services, DNS/TCP-based exfiltration techniques (e.g., via LOAD_FILE against UNC paths on Windows, or out-of-band DNS queries) can be leveraged.

The blast radius scales directly with the privilege level of the MySQL credential stored in the n8n instance. Least-privilege configurations reduce but do not eliminate exposure — even a read-only credential leaks sensitive data.

How to Fix It

Upgrade immediately. The n8n team has released patched versions across all affected branches:

# npm
npm install [email protected]        # 1.x users
npm install [email protected]          # 2.x users
npm install [email protected]          # 2.28.x users

# Docker (self-hosted)
docker pull n8nio/n8n:1.123.61
docker pull n8nio/n8n:2.27.4
docker pull n8nio/n8n:2.28.1

Migrate to the MySQL v2 node. The v2 node uses parameterized queries by design. In your workflows, replace any MySQL v1 executeQuery nodes with their v2 equivalents and use the dedicated parameter binding fields rather than embedding expression values directly in the SQL string:

// FIXED — MySQL v2 node pattern (parameterized)
async function executeQuery(
  this: IExecuteFunctions,
  connection: mysql2.Connection,
  index: number
): Promise<INodeExecutionData[]> {
  // SQL template uses positional placeholders — no expressions in the string.
  const queryTemplate = this.getNodeParameter('query', index) as string;
  // e.g.: "SELECT * FROM users WHERE id = ?"

  // Values are passed as a separate array, bound by the driver.
  const queryParameters = this.getNodeParameter('queryParameters', index, []) as unknown[];
  // e.g.: [resolvedUserId]

  // mysql2's execute() sends query and params in separate protocol frames.
  const [rows] = await connection.promise().execute(queryTemplate, queryParameters);

  return this.helpers.returnJsonArray(rows as IDataObject[]);
}

Network-level mitigations (defense-in-depth). Until an upgrade is applied, restrict webhook endpoint access to trusted IP ranges via firewall rules or reverse-proxy ACLs. Audit all active workflows for MySQL v1 executeQuery nodes receiving any expression-derived input. Apply least-privilege MySQL credentials — grant only the minimum set of privileges required for the workflow’s legitimate function.

Our Take

SQL injection through template string interpolation is one of the oldest vulnerability classes in existence, yet it continues to surface in modern tooling — often because the injection point is not the database layer itself but an abstraction layer above it. Workflow automation platforms are a particularly fertile ground for this pattern: the design surface encourages embedding dynamic expressions in query strings, and the distance between “this is a template” and “this is a SQL statement” can be invisible to workflow designers who are not security-trained.

For enterprises running workflow automation at scale, the lesson is architectural: any node that constructs queries from user-controlled data must use parameterization at the driver level. Convenience APIs that accept fully evaluated strings are categorically unsafe when the evaluation result includes external input. Platform vendors should treat the legacy node pattern as a deprecation-and-removal candidate, not merely a “use v2 instead” advisory footnote.

From a DAST perspective, webhook-triggered SQL injection is highly detectable with active scanning — standard UNION-based and error-based payloads against webhook endpoints will surface this within minutes. Organizations that had not routinely pointed an authenticated DAST scanner at their n8n webhook inventory were flying blind.

Detection with SAST

This vulnerability class maps to CWE-89: Improper Neutralization of Special Elements used in an SQL Command (‘SQL Injection’). SAST detection targets the following code pattern:

  • A string construction or template-evaluation call whose output flows into a database query execution function (query(), exec(), execute() when called with a single pre-composed string argument) without passing through a parameterization or escaping boundary.

Offensive360’s SAST engine flags this via taint-tracking rules that model evaluateExpression() and equivalent user-controlled expression resolvers as taint sources. The taint propagates through string concatenation, template literal interpolation, and format-string operations. A sink match is raised when the tainted value reaches any database execution call accepting a raw string. In this specific case the rule fires on the pattern connection.query(evaluatedString) where evaluatedString carries taint from an expression evaluation path.

Additional heuristic signals include: use of mysql or mysql2 query() (single-argument form) rather than execute() (two-argument parameterized form), and SQL-like string content containing {{ }} expression markers resolved at runtime.

References

#SQL Injection #n8n #MySQL #Workflow Automation

Detect this vulnerability class in your codebase

Offensive360 SAST scans your source code for CVE-2026-59257-class vulnerabilities and thousands of other patterns — across 60+ languages.