GeoServer jsonArrayContains SQL Injection — Unauthenticated SQLi to RCE
Executive Summary
An unauthenticated SQL injection exists in GeoTools' jsonArrayContains(<column>, <pointer>, <value>) function when GeoServer evaluates OGC Filters (CQL_FILTER or XML <Filter>) against a PostGIS DataStore backed by PostgreSQL 12 or later. The <value> argument is written into the generated jsonb_path_exists() SQL expression without escaping, letting a remote, unauthenticated attacker break out of the string literal and inject arbitrary SQL against any exposed WMS/WFS endpoint. It is a regression of CVE-2023-25158, scoped specifically to jsonArrayContains, and was disclosed publicly by researcher @q1uf3ng on X on 2026-08-12 at 10:46 UTC with no CVE ID assigned.
watchTowr observed exploitation/probing attempts beginning within hours of disclosure — hundreds of attempts from a small pool of source IP addresses, currently described as reconnaissance that triggers errors without proceeding further. Where the PostGIS database account used by GeoServer holds elevated privileges (superuser/sa-equivalent, or membership in pg_execute_server_program), the SQL injection escalates to full remote code execution on the database host via the PostgreSQL COPY ... TO PROGRAM technique. GeoServer/GeoTools have since shipped fixes (GeoServer 3.0.1, 2.28.5, 2.27.6 — GeoTools 35.1, 34.5, 33.6) under GHSA-mqjf-5f49-2fjh, but field patch adoption should be assumed slow given GeoServer's history with prior critical flaws (CVE-2024-36401).
The single highest-value defensive move in this pack is the process-lineage detection for the RCE pivot itself: a shell or scripting interpreter spawned directly from a postgres/postgres.exe parent process is an ancestry that essentially never occurs in legitimate PostgreSQL operation, and it requires only native Falcon endpoint telemetry — no external log source dependency.
Defender priority: patch to GeoServer 3.0.1 / 2.28.5 / 2.27.6 first; if that is not immediately possible, strip superuser and pg_execute_server_program from the GeoServer PostGIS service account today — this removes the RCE pivot even while the SQLi itself remains unpatched.
Source Review & Web Hunter Notes
Nine sources were fetched and saved verbatim during research; snapshots live in GeoServer-JsonArrayContains-Hunt-sources/. Full citations are in Section 18.
| # | Source | Tier | Contributed |
|---|---|---|---|
| 1 | The Hacker News | T2 | Disclosure quote, watchTowr timeline, later "Update" with fixed versions + GHSA ID |
| 2 | Security Affairs | T2 | watchTowr quotes, exposure context, interim mitigation guidance |
| 3 | Field Effect | T2 | H2 database angle, 2024 CVE-2024-36401 webshell/persistence precedent |
| 4 | CyberUpdates365 | T3 | Restates mechanics, WAF / least-privilege mitigation framing |
| 5 | DEV.to (Anoymask) | T3 | Raised (later resolved) regression-vs-bypass ambiguity; stresses log correlation before declaring compromise |
| 6 | GitHub Security Advisory GHSA-mqjf-5f49-2fjh | T1 | Authoritative technical description, function signature, affected/patched versions, CVSS vector |
| 7 | Hadrian.io technical analysis | T3 | Vulnerable code pattern, exploit payload shape, RCE pivot mechanism, privilege requirements |
| 8 | SecurityWeek | T2 | Independent corroboration of watchTowr quote/timeline |
| 9 | Aggregated search notes | T3 | Confirms no atomic scanning-IP list published anywhere indexed; affected-version range corroboration |
Confidence rated High: the core mechanism (GHSA-mqjf-5f49-2fjh, source #6) and the RCE pivot (source #7) are corroborated by ≥2 independent T1/T2-adjacent technical sources, and the exploitation timeline is confirmed independently by three separate press outlets (#1, #2, #8) all citing watchTowr.
Contradictions & gaps carried into this pack
- Affected GeoServer version lower bound is not precisely agreed across sources ("through 2.27.5" vs. "2.25.3 and later") — treat any version prior to the three patched releases as potentially vulnerable.
- No atomic scanning-IP addresses are published in any indexed source as of 2026-08-17 — this pack does not fabricate an IP blocklist.
- No confirmed webshell/malware for this 2026 campaign specifically — the webshell/botnet precedent is from the unrelated 2024 CVE-2024-36401 campaign, used only as a historical pattern.
Hunt Brief & Attack Chain
Hypothesis: an attacker sends a crafted jsonArrayContains( OGC filter to an internet-reachable GeoServer WMS/WFS endpoint, triggers the escaping bug against the PostGIS backend, and — where the DB account is over-privileged — escalates to OS command execution on the database host via COPY ... TO PROGRAM, then pivots toward the GeoServer application host itself for persistence.
| Stage | Attacker Action | Observable |
|---|---|---|
| 1 | Reconnaissance / mass probing | High-volume WMS/WFS requests, HTTP 4xx/5xx bursts, from a small IP pool |
| 2 | SQLi entry | jsonArrayContains( string with SQLi breakout characters in CQL_FILTER/XML Filter body |
| 3 | SQLi-to-RCE pivot | Shell/interpreter spawned directly from postgres/postgres.exe parent |
| 4 | App-host post-exploitation (if achieved) | Shell/discovery command spawned from java/javaw (GeoServer/Tomcat) parent |
| 5 | Persistence (historical pattern, unconfirmed for this campaign) | New .jsp/script file written under a webapps/geoserver-style path |
| 6 | C2 / resource hijacking (historical pattern, unconfirmed) | New outbound connections from GeoServer or DB host following the shell spawn |
Consolidated IOC Table
No atomic file hashes, IPs, or domains have been published for this campaign in any source reviewed. watchTowr's reporting (sources #1, #2, #8) describes only "hundreds of attempts from a small pool of source IP addresses" without enumerating them. This pack does not fabricate atomic indicators — detections below rely on behavioral/URL-pattern and process-lineage signals instead.
| Type | Value | Confidence | Action | Context |
|---|---|---|---|---|
| Behavioral / URL pattern | jsonArrayContains( in OGC filter | Medium | Hunt | Exploitation entry-point string (sources #6, #7, #9) |
| Reference ID | GHSA-mqjf-5f49-2fjh | High | Enrich | Authoritative advisory ID (source #6) — use for asset/patch tracking since no CVE exists |
| Process lineage | postgres/postgres.exe → shell/interpreter | High | Hunt | RCE pivot signature (source #7) |
| File hashes | (none published) | — | — | — |
| IPv4 (scanning) | (none published — do not fabricate) | — | — | — |
| Domains | (none published) | — | — | — |
Affected Surface & Telemetry Matrix
| Surface | Detail | Telemetry | Gap? |
|---|---|---|---|
GeoTools gt-jdbc-postgis | 35.0; 34.0 and later before 34.5; 33.1 and later before 33.6 | Version inventory / patch management | — |
| GeoServer application | Versions prior to 3.0.1 / 2.28.5 / 2.27.6 | Falcon process telemetry (java/Tomcat host) | Available |
| PostGIS DataStore | PostgreSQL 12+ with a JSON/String field reachable via OGC filter | Falcon process telemetry (DB host) — verify sensor coverage | Verify sensor deployed on DB tier |
| WMS/WFS OGC endpoints | Unauthenticated by default; accepts CQL_FILTER/XML Filter | HTTP/web-access log (WAF, reverse proxy, or GeoServer log connector) | GAP — requires HTTP log ingestion, not native to Falcon endpoint sensor |
| GeoServer webapps directory | Webshell drop surface (historical pattern) | Falcon file-write telemetry — coverage for non-PE script writes varies by sensor policy | Verify script-write coverage |
The most consequential gap: the actual SQLi entry point (stages 1–2 in the attack chain) is only observable via HTTP/web-access logs. If those are not ingested into Falcon LogScale, this pack's endpoint-native detections (Section 8) only catch the campaign from the RCE pivot onward — after the injection has already succeeded.
MITRE ATT&CK Mapping
| Tactic | Technique | Behavior | Query / Control |
|---|---|---|---|
| Reconnaissance | T1595 Active Scanning | Mass probing / error-triggering against OGC endpoints | Native Hunt 1 (Section 7) |
| Initial Access | T1190 Exploit Public-Facing Application | jsonArrayContains( SQLi via OGC filter | Native Hunt 2 (Section 7) |
| Execution | T1059 Command and Scripting Interpreter | COPY ... TO PROGRAM RCE pivot on DB host | CQL Q1 (Section 8) |
| Execution / Persistence | T1059, T1505.003 Web Shell | Shell/discovery command off GeoServer Java/Tomcat process | CQL Q2 (Section 8) |
| Persistence | T1505.003 Web Shell | Script file drop into webapps directory | CQL Q3 (Section 8) |
| Command and Control / Impact | T1071 (inferred), T1496 Resource Hijacking | Outbound connection following shell spawn (historical precedent, precautionary) | CQL Q4 (Section 8) — low confidence, manual review |
Native Audit-Log Hunts (HTTP / WAF / Reverse-Proxy)
The two hunts below target the actual SQL-injection entry point and are the highest-value detections in this pack, but they require HTTP/web-access-log telemetry (WAF, reverse proxy, or a GeoServer access-log connector) ingested into your SIEM/LogScale — this is not native Falcon endpoint sensor telemetry, so field and event names below are representative of a generic HTTP-log schema and must be validated against your actual ingested repo/parser before deployment. They are intentionally kept out of the CQL section (Section 8) since they are not standard Falcon data-model queries.
Native Hunt 1 — jsonArrayContains OGC Filter Entry Point
MITRE: T1190 — Exploit Public-Facing Application CONF: Medium FP: Low-Medium
Looks for inbound HTTP requests to GeoServer OGC WMS/WFS endpoints containing the literal string jsonArrayContains( in the query string or POST body.
// NATIVE HUNT: GeoServer jsonArrayContains OGC Filter Entry Point
// MITRE: T1190 | CONF: medium FP: low-med COST: low | REQUIRES: HTTP/web-access log ingestion
// FP NOTES: Legitimate GeoServer clients (QGIS, ArcGIS connectors) may use
// jsonArrayContains() for benign JSON-array filtering with no SQL breakout
// characters. Treat a bare hit as recon; corroborate with breakout characters
// (quote, semicolon, COPY, TO PROGRAM, double-dash comment) before escalating.
// Adapt field/event names to your ingested HTTP-log schema (WAF / reverse proxy /
// GeoServer access-log connector). Representative pseudo-query below:
source = geoserver_access_log OR waf_access_log
| request_url CONTAINS "jsonArrayContains("
| table timestamp, client_ip, http_method, request_url, response_code
| sort timestamp desc
Native Hunt 2 — Mass Scanning / High-Error-Rate OGC Request Burst
MITRE: T1595 — Active Scanning CONF: Medium FP: Medium
Looks for a burst of OGC service requests generating HTTP 4xx/5xx or GeoServer SQL-error responses from a small number of distinct source IPs in a short window — the "trigger errors, don't proceed further" pattern watchTowr described.
// NATIVE HUNT: GeoServer Mass Scanning / OGC Request Error Burst // MITRE: T1595 | CONF: medium FP: medium COST: low | REQUIRES: HTTP/web-access log ingestion // FP NOTES: Misconfigured legitimate GIS clients, uptime checks, and internal // vulnerability scanners can also generate error bursts against OGC endpoints. // Exclude known internal scanner/health-check source IPs; corroborate with // Native Hunt 1's jsonArrayContains( match before escalating. source = geoserver_access_log OR waf_access_log | request_url CONTAINS "/geoserver/" | response_code IN (400, 404, 500, 502, 503) | stats count AS error_count BY client_ip | sort error_count desc
CrowdStrike LogScale CQL Hunt Queries
Scheduled Search Name: HUNT: GeoServer SQLi-to-RCE - Shell Spawned from PostgreSQL/PostGIS Process (Query 1 below). Priority HIGH — escalate immediately if the spawned process is powershell.exe, cmd.exe, or a reverse-shell-style command line; escalate to Critical if followed by outbound network connections or additional process spawns.
Looks for: a shell/interpreter process whose direct parent is postgres/postgres.exe — the signature of the COPY ... TO PROGRAM RCE pivot. FP: DBA automation/backup tooling (custom archive_command/restore_command scripts, pg_cron-style extensions) can legitimately spawn shell children from a postgres-family parent — cross-check against your known DBA automation inventory.
// HUNT: GeoServer SQLi-to-RCE - Shell Spawned from PostgreSQL/PostGIS Process // MITRE: T1059 (Command and Scripting Interpreter) | T1190 (Exploit Public-Facing Application) // CONF: high FP: low COST: low | REQUIRES: Falcon sensor on the PostgreSQL/PostGIS database host // HYPOTHESIS: If the jsonArrayContains SQL injection (GHSA-mqjf-5f49-2fjh) has been // escalated to RCE via COPY ... TO PROGRAM, a shell/interpreter process will show // postgres/postgres.exe as its direct parent - an ancestry essentially never seen // in normal PostgreSQL operation. // FP NOTES: DBA automation/backup tooling (custom archive_command/restore_command // scripts, pg_cron-style extensions) can legitimately spawn shell children from a // postgres-family parent. Cross-check hits against known DBA automation before escalating. // TIMEFRAME / LOOKBACK: 14d (set via console time picker) #event_simpleName = /ProcessRollup2|SyntheticProcessRollup2/ | ParentBaseFileName = /^postgres(\.exe)?$/i | FileName = /^(sh|bash|dash|ksh|zsh|cmd\.exe|powershell\.exe|pwsh\.exe|cscript\.exe|wscript\.exe|python3?(\.exe)?|perl(\.exe)?)$/i | groupBy([aid, ComputerName, UserName, ParentBaseFileName, FileName], function=[count(as=SpawnCount), collect(CommandLine)], limit=500) | rename(field=CommandLine, as=ObservedCommandLines) | sort(SpawnCount, order=desc)
Looks for: a shell or discovery command spawned directly from the GeoServer Java/Tomcat process — the pattern GeoServer attackers repeated in the 2024 CVE-2024-36401 campaign. FP: JVM monitoring/APM agents and scheduled maintenance scripts occasionally shell out from a Java parent.
// HUNT: GeoServer Post-Exploitation - Shell/Discovery Command from Java/Tomcat // MITRE: T1059 (Command and Scripting Interpreter) | T1505.003 (Web Shell, precautionary) // CONF: medium FP: medium COST: low | REQUIRES: Falcon sensor on the GeoServer/Tomcat application host // HYPOTHESIS: If an attacker gains code execution on the GeoServer application host // itself, the Java/Tomcat servlet process will spawn an OS shell or discovery // command as a direct child - a pattern repeated in GeoServer's 2024 webshell campaign. // TUNING: exclude known JVM monitoring/APM javaagent wrappers (New Relic, AppDynamics, // Datadog) and scheduled GeoServer backup/maintenance scripts that legitimately shell // out from the Java process; scope to hosts confirmed running GeoServer. // TIMEFRAME / LOOKBACK: 14d (set via console time picker) #event_simpleName = /ProcessRollup2|SyntheticProcessRollup2/ | ParentBaseFileName = /^java(w)?(\.exe)?$/i | FileName = /^(sh|bash|dash|ksh|zsh|cmd\.exe|powershell\.exe|pwsh\.exe|cscript\.exe|wscript\.exe|whoami(\.exe)?|id|wget(\.exe)?|curl(\.exe)?|nc(\.exe)?|ncat(\.exe)?)$/i | groupBy([aid, ComputerName, UserName, ParentBaseFileName, FileName], function=[count(as=SpawnCount), collect(CommandLine)], limit=500) | rename(field=CommandLine, as=ObservedCommandLines) | sort(SpawnCount, order=desc)
Looks for: a new .jsp/.jspx/.war/.class file written under a GeoServer webapps deployment path. FP: legitimate GeoServer plugin installation and version upgrades also write files into webapps directories.
// HUNT: GeoServer Web Shell File Drop - Script Files Written to Webapps Directory // MITRE: T1505.003 (Web Shell) // CONF: medium FP: medium COST: medium | REQUIRES: Falcon sensor with file-write telemetry on the GeoServer host // HYPOTHESIS: If a web shell was dropped following exploitation (consistent with // GeoServer's 2024 precedent of webshell deployment ~2 weeks post-exploitation), // file-write telemetry will show a new script file under webapps/geoserver/... // TUNING: exclude known GeoServer version-upgrade/plugin-install maintenance windows; // correlate hits with a preceding jsonArrayContains( request (Native Hunt 1) or a // postgres/java shell-spawn hit (Q1/Q2) in the prior 24-48h before escalating. // COVERAGE NOTE: non-PE script-file write telemetry (.jsp on Linux) has variable // sensor coverage depending on OS/sensor policy - validate before relying on this alone. // TIMEFRAME / LOOKBACK: 30d (set via console time picker) #event_simpleName = /NewExecutableWritten|PeFileWritten/ | TargetFileName = *webapps* | TargetFileName = *geoserver* | TargetFileName = /\.(jsp|jspx|jar|war|class)$/i | table([@timestamp, aid, ComputerName, UserName, TargetFileName, SHA256HashData]) | sort(@timestamp, order=desc)
Looks for: a host that produced a Q1 postgres-parent shell-spawn hit also showing newly-established outbound network connections — a low-confidence correlation, not a definitive detection. FP: any legitimate outbound traffic from the affected host will appear here; every result requires manual analyst review.
// HUNT: GeoServer Post-Compromise Outbound Correlation (Precautionary/Heuristic) // MITRE: T1071 (inferred) | T1496 (Resource Hijacking, historical GeoServer precedent only) // CONF: low FP: high COST: medium | REQUIRES: Falcon sensor on both app and DB hosts; manual review mandatory // HYPOTHESIS: If this campaign follows GeoServer's historical exploitation pattern // (CVE-2024-36401 -> DDoS/crypto-mining botnets, residential proxies), a host that // produced a Q1 shell-spawn hit will also show new outbound connections nearby in time. // TUNING: this is a heuristic correlation with no atomic C2 IOCs behind it for this // specific 2026 campaign - do not auto-escalate or auto-block on this query alone; // every result requires manual analyst review before any action is taken. // TIMEFRAME / LOOKBACK: 7d (set via console time picker) #event_simpleName = /ProcessRollup2|SyntheticProcessRollup2/ | ParentBaseFileName = /^postgres(\.exe)?$/i | FileName = /^(sh|bash|dash|ksh|zsh|cmd\.exe|powershell\.exe|pwsh\.exe)$/i | groupBy([aid, ComputerName], function=count(as=ShellSpawnCount), limit=500) | join(query={#event_simpleName = NetworkConnectIP4}, field=[aid], key=[aid], mode=left) | !cidr(RemoteAddressIP4, subnet="10.0.0.0/8") | !cidr(RemoteAddressIP4, subnet="172.16.0.0/12") | !cidr(RemoteAddressIP4, subnet="192.168.0.0/16") | table([@timestamp, aid, ComputerName, RemoteAddressIP4, RemotePort, ShellSpawnCount]) | sort(@timestamp, order=desc)
Custom IOA Recommendations
Custom IOA Rule: PostgreSQL Parent Spawns Shell
Rule Group: Exploit-to-RCE Defense Rule Type: Process Creation Action: Detect (start here; do not promote to Block without a measured FP rate) Severity: High MITRE: T1059
Detection Logic:
- Parent Image Filename:
.*\\postgres(\.exe)?$ - Image Filename:
.*\\(sh|bash|cmd\.exe|powershell\.exe|pwsh\.exe)$
Description: Catches the COPY ... TO PROGRAM RCE pivot used to escalate the GeoServer jsonArrayContains SQLi (GHSA-mqjf-5f49-2fjh) into command execution on the PostGIS database host, in real time, before the shell completes execution.
FP Tuning Notes: exclude known DBA automation service accounts/scripts (custom archive_command/restore_command, pg_cron) if present in the environment.
Recommended Validation: on an isolated test PostgreSQL instance, run a benign COPY (SELECT 1) TO PROGRAM 'echo test' as a database superuser in a lab environment (never against production) to confirm the rule fires before promoting from Detect.
Machine-Readable IOC Appendix
No atomic IP, domain, or hash indicators are published for this campaign in any source reviewed (see Section 4). The blocks below surface the trackable reference IDs and behavioral signature instead of fabricated atomic IOCs.
type,value,action,severity,expiration,description,tags # No atomic hash/domain/IP indicators are published for this campaign as of 2026-08-17. # If your own investigation confirms a campaign-specific indicator, add it here with # its source cited in Section 18 and re-run the provenance check before shipping. sha256,REPLACE_WITH_SHA256,detect,high,2027-02-17,GeoServer jsonArrayContains post-exploitation payload,campaign:GeoServer-jsonArrayContains domain,REPLACE_WITH_C2_DOMAIN,detect,medium,2026-11-17,GeoServer jsonArrayContains post-exploitation C2,campaign:GeoServer-jsonArrayContains
substring: jsonArrayContains(
context: CQL_FILTER query parameter or XML <Filter> POST body on GeoServer WMS/WFS endpoints
corroborate_with: SQL breakout chars (', ;, --, COPY, TO PROGRAM)
source: 06-github-advisory-ghsa-mqjf.txt, 07-hadrian-io-technical.txt
GHSA-mqjf-5f49-2fjh (GitHub Security Advisory, org.geotools:gt-jdbc-postgis) CVE-2023-25158 (prior regression baseline, NOT the same as this issue) GEOT-7958 (GeoTools internal tracking reference) CVSS: 9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
[ ] GeoServer version = 3.0.1 / 2.28.5 / 2.27.6 or later [ ] GeoTools gt-jdbc-postgis = 35.1 / 34.5 / 33.6 or later [ ] PostGIS service role: rolsuper = false [ ] PostGIS service role: NOT a member of pg_execute_server_program [ ] WMS/WFS endpoints not publicly reachable, or WAF filters jsonArrayContains( [ ] HTTP/web-access logs for GeoServer ingested into Falcon LogScale
Detection Validation Gates
| Gate | Requirement | Status / How to Confirm |
|---|---|---|
| Telemetry ready | Falcon sensor deployed on both GeoServer application host and PostGIS/PostgreSQL database host (may be separate hosts) | Verify via Falcon host inventory before relying on Q1–Q4 |
| HTTP log ingestion | GeoServer/WAF/reverse-proxy access logs flowing into LogScale | Required for Native Hunts 1–2 (Section 7); spot-check with a broad query against your ingested repo |
| Benign baseline | Confirm no legitimate DBA automation or JVM monitoring agents match Q1/Q2 parent/child patterns | Run Q1/Q2 over a 30-day historical window before enabling alerting |
| Positive test | On an isolated lab PostgreSQL instance, trigger a benign COPY ... TO PROGRAM to confirm Q1 fires | Never test against production; use a non-destructive command |
| Promotion | Custom IOA (Section 9) runs in Detect mode 14+ days with measured FP rate under 5% before considering Block mode | Document FP rate in the hunt ticket (Section 16) before promotion |
Hardening & Prevention
MITRE Mitigations: M1051 (Update Software), M1042 (Disable/Remove Feature), M1030 (Network Segmentation), M1026 (Privileged Account Management), M1037 (Filter Network Traffic)
- Patch GeoServer/GeoTools. Upgrade to GeoServer 3.0.1, 2.28.5, or 2.27.6 (GeoTools 35.1/34.5/33.6). Framework: M1051; GHSA-mqjf-5f49-2fjh, CVSS 9.8. No CVE ID assigned as of 2026-08-17 — track by GHSA ID.
- Strip superuser and
pg_execute_server_programfrom the GeoServer PostGIS service account. Both are the named preconditions for the RCE pivot; removing them closes the RCE path even on unpatched instances. Framework: M1026. - Restrict public exposure of GeoServer OGC endpoints (VPN, authenticated reverse proxy, or IP allow-list) pending patch confirmation. Framework: M1030.
- Add a WAF/reverse-proxy filter blocking
jsonArrayContains(in inbound query strings and POST bodies to GeoServer paths, as a compensating control. Framework: M1037; ⚠ best-practice, not a substitute for patching.
- Disable
jsonArrayContainswhere it is not a business requirement (custom FunctionFactory allowlist). Framework: M1042; pilot on non-production first. - Segment GeoServer application hosts from PostGIS database hosts at the network layer — restrict port 5432 to the GeoServer host only. Framework: M1030.
- Ingest GeoServer/reverse-proxy HTTP access logs into LogScale — closes the coverage gap flagged in Sections 5 and 7 for the SQLi entry-point detections.
- Audit and rotate GeoServer PostGIS database credentials in case the disclosure-to-mitigation exposure window allowed credential harvesting via blind/error-based SQLi. Framework: M1026.
- Establish a GeoServer/GIS asset inventory with patch-level tracking, given the product's repeated critical-vulnerability history (CVE-2024-36401; CVE-2023-25157/25158; this 2026 regression). Framework: M1051 as an ongoing program.
- Move to least-privilege-by-default DB account provisioning for all GeoServer/GIS deployments. Framework: M1026; CIS PostgreSQL Benchmark role/privilege sections.
- Evaluate an OGC-aware API gateway/WAF tier for GeoServer given the repeated pattern of OGC-filter-based injection vulnerabilities in this product line. Framework: M1037; ⚠ best-practice.
Platform-specific notes
Linux GeoServer/Tomcat hosts: confirm the service account cannot write into the webapps/geoserver deployment path outside the normal deployment process. Apply CIS Linux Benchmark controls (least-privilege service account, no unnecessary setuid binaries, restricted sudo).
Windows GeoServer hosts: confirm the Tomcat/GeoServer service does not run as LocalSystem or a domain-privileged account; run under a dedicated low-privilege service account per MS Baseline service-hardening guidance.
PostgreSQL/PostGIS database hosts: beyond removing superuser/pg_execute_server_program, consider OS-level controls (restrictive SELinux/AppArmor policy on the postgres process, or blocking shell execution for the postgres OS user) for defense in depth. Enable statement logging (log_statement = 'ddl' minimum) to support forensic review.
What to verify after deployment
- Confirm GeoServer version reports 3.0.1, 2.28.5, or 2.27.6 (or later) via the admin UI, or confirm the bundled GeoTools JAR matches 35.1/34.5/33.6.
- Confirm the GeoServer PostGIS role has
rolsuperfalse and is not a member ofpg_execute_server_program(see Playbook, Section 13). - Confirm HTTP/access-log events are arriving in Falcon LogScale for the GeoServer host(s).
- Re-run CQL Q1 and Q2 (Section 8) against the lookback window to confirm no historical hits were missed before hardening was applied.
Deployable Playbooks
Step 1 — Strip elevated privileges from the GeoServer PostGIS role
What it does: removes the RCE pivot precondition. Why it works: M1026 — without superuser or pg_execute_server_program, COPY ... TO PROGRAM is rejected by PostgreSQL even if the SQLi still succeeds.
-- Run as a PostgreSQL superuser against the target database. -- Replace REPLACE_WITH_GEOSERVER_ROLE with your actual GeoServer service role name. REVOKE pg_execute_server_program FROM REPLACE_WITH_GEOSERVER_ROLE; ALTER ROLE REPLACE_WITH_GEOSERVER_ROLE NOSUPERUSER;
Verify:
SELECT rolname, rolsuper FROM pg_roles WHERE rolname = 'REPLACE_WITH_GEOSERVER_ROLE';
SELECT pg_has_role('REPLACE_WITH_GEOSERVER_ROLE', 'pg_execute_server_program', 'member');
-- Expected: rolsuper = f ; pg_has_role = f
Step 2 — WAF compensating control (interim, pending patch)
What it does: blocks requests containing the exploitation entry-point string. Why it works: M1037 — signature-based filtering of the vulnerable function call.
SecRule ARGS|REQUEST_BODY "@contains jsonArrayContains(" \
"id:900100,phase:2,deny,status:403,msg:'GeoServer jsonArrayContains OGC filter blocked (GHSA-mqjf-5f49-2fjh)'"
This is a compensating control only — attackers can vary casing/encoding. It does not replace patching.
Step 3 — Network restriction (interim, pending patch)
What it does: removes the internet-reachable attack surface. Why it works: M1030.
# Example host-based firewall rule restricting inbound access to the GeoServer # OGC service ports to a known management/VPN CIDR only. Adapt to your platform. # iptables example: iptables -A INPUT -p tcp --dport 8080 -s REPLACE_WITH_MGMT_CIDR -j ACCEPT iptables -A INPUT -p tcp --dport 8080 -j DROP
Deploying via your RMM: the scripts above are raw shell/SQL/WAF config. To wrap them for fleet deployment (admin checks, error handling, custom-field writeback for compliance tracking), hand them to your RMM deployment tooling and ask for a deployable package.
Containment Runbook
| Phase | Actions | Owner | Evidence to Preserve |
|---|---|---|---|
| Triage | Confirm Q1/Q2 hit is executed (not blocked) via #event_simpleName; confirm the host is a GeoServer app or PostGIS DB host | SOC Analyst | Alert event, ComputerName, timestamp |
| Confirm privilege precondition | Check current PostGIS role privileges (Section 13, Step 1 verify commands) | DBA / SOC | Query output, timestamp of check |
| Correlate entry point | Pivot to HTTP/access logs (Native Hunt 1, Section 7) for a preceding jsonArrayContains( request in the prior 24–48h | SOC Analyst | Matching request log lines |
| Scope | Run CQL Q1 environment-wide (Section 8 pivot set) to determine single-host vs. multi-host exposure | SOC Analyst | groupBy output by ComputerName |
| Contain | Isolate the host if execution confirmed + elevated DB privileges confirmed + any follow-on execution or outbound connection observed | IR Lead | Isolation timestamp, approval record |
| Eradicate | Apply Section 13 playbook (patch, privilege revocation, WAF rule); remove any dropped webshell files found via CQL Q3 | SOC + DBA | Before/after version and privilege checks |
| Recover | Re-run Q1–Q4 over the lookback window to confirm no residual activity; re-enable public access only after patch confirmed | SOC Analyst | Clean query run, sign-off |
Detection Coverage Map
| MITRE Technique | Behavior | Native Hunt | CQL | Custom IOA | Coverage |
|---|---|---|---|---|---|
| T1595 | Active Scanning / mass probing | Native Hunt 2 | — | — | Partial |
| T1190 | jsonArrayContains SQLi entry | Native Hunt 1 | — | — | Partial |
| T1059 | COPY ... TO PROGRAM RCE pivot (DB host) | — | Q1 | √ Custom IOA (S9) | Good |
| T1059 / T1505.003 | Shell off GeoServer Java/Tomcat | — | Q2 | — | Partial |
| T1505.003 | Web shell file drop | — | Q3 | — | Partial |
| T1071 / T1496 | C2 / resource hijacking (precautionary) | — | Q4 | — | GAP — heuristic only |
Coverage Summary: 6 of 6 identified techniques have at least partial detection coverage. The strongest, lowest-FP detection is Q1 (postgres-parent shell spawn), and it is the only technique with both a CQL hunt and a Custom IOA. The two HTTP-log-based Native Hunts (entry-point detections) are gated on HTTP/access-log ingestion — see Section 5's coverage gap note. Q4 is intentionally heuristic and should never drive automated response on its own.
Priority Gap: if HTTP/access-log telemetry is not ingested into Falcon LogScale, the two Native Hunts (the only detections for the actual SQLi entry point) cannot run — close this gap first (Section 12, Short-term tier).
Validation gates: see Section 11 for the telemetry-readiness, benign-baseline, positive-test, and promotion gates that must be confirmed before any of the above queries are relied on operationally.
Hunt Summary Ticket
TITLE: GeoServer jsonArrayContains SQL Injection to RCE (GHSA-mqjf-5f49-2fjh)
SEVERITY: Critical (CVSS 9.8) - exploited in the wild (mass probing observed)
SCOPE: Any GeoServer instance fronting a PostGIS DataStore with internet-
reachable WMS/WFS endpoints; versions prior to 3.0.1 / 2.28.5 / 2.27.6
HYPOTHESIS: Attacker sends a crafted jsonArrayContains( OGC filter to an exposed
GeoServer WMS/WFS endpoint; if the PostGIS DB account is over-privileged, SQLi
escalates to RCE on the DB host via COPY ... TO PROGRAM.
QUERIES: Native Hunt 1-2 (Section 7, HTTP log)
CQL Q1-Q4 (Section 8, Falcon endpoint telemetry)
DO FIRST: 1. Confirm GeoServer/GeoTools patch level across the fleet
2. Run CQL Q1 (postgres-parent shell spawn) over a 30d lookback
3. Confirm PostGIS service account privilege level (Section 13, Step 1)
FINDINGS: [Populate after queries are run in Falcon - this ticket describes what
a positive result looks like, not a pre-filled result. A hit on Q1
with an executed (not blocked) shell = treat as confirmed RCE.]
GAPS: HTTP/access-log ingestion required for Native Hunts 1-2 (SQLi entry-
point detection); no atomic scanning-IP IOCs published for this campaign.
ACTIONS: Immediate: patch + strip superuser/pg_execute_server_program (Section 12)
Pending approval: WAF rule deployment, network segmentation change
OWNER: [Assign SOC/DBA owner]
VERSION: v0.1 - 2026-08-17
Changelog
References
| Tier | Source | Used For | Access Date |
|---|---|---|---|
| T2 | The Hacker News — GeoServer Zero-Day Targeted in Active Exploitation Attempts | Disclosure quote, timeline, fixed versions | 2026-08-17 |
| T2 | Security Affairs — GeoServer Zero-Day Is Already Being Probed | watchTowr quotes, exposure context | 2026-08-17 |
| T2 | Field Effect — Early Exploitation Attempts Observed of GeoServer Zero Day | H2 DB angle, 2024 precedent | 2026-08-17 |
| T3 | CyberUpdates365 — Unpatched GeoServer Zero-Day: Active SQLi Leads to RCE | Mitigation framing | 2026-08-17 |
| T3 | DEV.to (Anoymask) — GeoServer jsonArrayContains SQL Injection analysis | Regression-vs-bypass discussion, log correlation guidance | 2026-08-17 |
| T1 | GitHub Security Advisory GHSA-mqjf-5f49-2fjh | Authoritative technical description, patched versions, CVSS | 2026-08-17 |
| T3 | Hadrian.io — Here Be Dragons: GeoServer Pre-Auth SQL Injection to RCE | RCE pivot mechanism, privilege requirements | 2026-08-17 |
| T2 | SecurityWeek — Hackers Exploiting Unpatched GeoServer Zero-Day | Independent timeline corroboration | 2026-08-17 |
| T3 | Aggregated web search research notes (multiple queries, see saved snapshot 09) | No-published-IP confirmation, version range corroboration | 2026-08-17 |
Full snapshot text for every source above is saved verbatim in GeoServer-JsonArrayContains-Hunt-sources/ (files 01–09).