
Scripts Python pour inventorier les points de terminaison WFS de GeoServer et vérifier les vulnérabilités d'injection SQL basée sur le temps dans PostGIS/GeoTools, avec un mode PoC dédié pour les tests autorisés.
Ce dépôt contient deux scripts Python pour l'examen des points de terminaison WFS de GeoServer.
wfs_inventory.pyObjectif : Inventorier les couches, les champs XSD et les valeurs WFS, et vérifier facultativement les candidats via un contrôle basé sur le temps.
Exemples d'exécution :
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.pyObjectif : Point d'entrée combiné pour l'inventaire ainsi qu'un mode PoC séparé.
Exemples d'exécution :
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()"
Remarque importante : Le contrôle basé sur le temps et le mode PoC ne doivent être utilisés que contre des systèmes pour lesquels une autorisation de test explicite a été accordée. Le mode d'inventaire normal n'utilise que des opérations WFS régulières.
Rendre facultativement exécutable :
chmod +x wfs_inventory.py geoserver_sqli_working.py
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 4
Le chemin standard /geoserver/wfs est ajouté automatiquement. Les indications suivantes sont donc équivalentes :
https://HOST
https://HOST/geoserver
https://HOST/geoserver/wfs
En cas d'installation différente, le chemin WFS complet doit être spécifié.
Uniquement pour les systèmes explicitement autorisés :
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 4 \
--sleep 1
Pendant le contrôle, une mesure par candidat apparaît sur 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 écran réussi n'est pas encore un résultat positif. Ce n'est que si la deuxième mesure, plus longue, réussit également qu'un bloc de paramètres est émis.
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 4 \
--sleep 1 \
--valid-diagnose output.txt
--valid-diagnose FILE active automatiquement le diagnostic détaillé et l'écrit dans le fichier spécifié.
Le mode standard auto traite les informations WFS dans cet ordre :
GetCapabilities est ouvert une fois :
/geoserver/wfs?service=WFS&acceptVersions=2.0.0&request=GetCapabilities
La réponse XML est diffusée en continu. Dès qu'un FeatureType/Name est trouvé, le prochain typeName est déterminé.
Pour cette couche, DescribeFeatureType est immédiatement exécuté avec version=2.0.0.
Le script extrait les éléments XSD et sélectionne les champs chaîne/JSON avec un nom ressemblant à un ID ou à un numéro.
Le candidat est vérifié en fonction de l'appel :
--sleep : les valeurs d'échantillon doivent être du JSON syntaxiquement valide.--sleep N : une requête de contrôle et une requête de vérification basée sur le temps sont mesurées. La vérification de la valeur JSON est alors ignorée.Le résultat est immédiatement émis et vidé.
Ce n'est qu'ensuite que le typeName suivant est lu depuis la réponse GetCapabilities en cours.
Ainsi, une grande réponse GetCapabilities n'a pas besoin d'être entièrement traitée avant que le premier résultat n'apparaisse.
Sans --sleep, le mode automatique prend en compte par défaut les champs chaîne/JSON avec un nom ressemblant à un ID ou à un numéro.
Avec --sleep, --candidate-scope auto utilise en revanche tous les champs simples non géométriques. Le type XSD et le modèle de nom d'ID ne bloquent alors plus le contrôle de synchronisation. Cela évite les faux négatifs pour les types XSD numériques, date/booléen ou spécifiques au fabricant.
Les modèles de noms reconnus incluent :
id
*_id
*_fid
nr_*
*_nr
*nummer*
fid
uuid
guid
key
objectid
Lors du contrôle basé sur le temps, le nom de champ doit en outre être un identifiant simple au format [A-Za-z_][A-Za-z0-9_]*.
Sans --sleep, on vérifie si les valeurs observées peuvent être interprétées syntaxiquement comme du JSON. C'est pourquoi, par exemple, la chaîne "383205" est également considérée comme un candidat, car son contenu représente un nombre JSON valide. Cette vérification est une heuristique et non une preuve de vulnérabilité.
Avec --sleep 1, seule la mesure du temps décide. Un candidat est considéré comme positif par défaut si la requête de vérification nécessite au moins 70 pour cent du temps de sommeil demandé en plus par rapport à la requête de contrôle. --timing-result-type auto vérifie d'abord resultType=hits et, en cas de résultat négatif, ensuite resultType=results.
Les mesures provisoirement positives sont obligatoirement confirmées avec un temps de sommeil plus long. Sans --confirm-sleep explicite, le script utilise max(3, --sleep * 3), limité à 10 secondes. Ainsi, un pic de latence unique avec --sleep 1 ne conduit plus à vulnerable=true.
La sortie standard contient un bloc par couche valide :
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 contient le point de terminaison WFS normalisé et les valeurs appropriées pour --typename et --field_name.
--valid-fields N compte les blocs de couches, pas les champs individuels. S'il existe moins de N couches valides, le reste du catalogue est examiné plus avant. Avec --max-layers, la durée d'exécution maximale peut être limitée.
Les options --diagnose ou --valid-diagnose FILE ajoutent entre autres :
GetCapabilities utilisée,DescribeFeatureType,GetFeature générée etExemple :
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 10 \
--valid-diagnose diagnose.txt
python3 wfs_inventory.py \
--url https://HOST \
--mode layers
Limiter à un espace de noms :
python3 wfs_inventory.py \
--url https://HOST \
--mode layers \
--namespace fink
python3 wfs_inventory.py \
--url https://HOST \
--mode fields \
--layer namespace:layer
Chaque champ est émis sous forme d'objet JSON avec le nom, le type XSD, nillable, id_candidate et 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
Exporter toutes les propriétés :
python3 wfs_inventory.py \
--url https://HOST \
--mode values \
--layer namespace:layer \
--all-properties \
--max-features 100
Avec --unique, les combinaisons identiques de valeurs de champs sélectionnées ne sont émises qu'une seule fois.
wfs_inventory.py| Paramètre | Défaut | Signification |
|---|---|---|
--url URL | requis | Hôte, base GeoServer ou point de terminaison WFS complet |
--mode auto|layers|fields|values | auto | Mode de fonctionnement à exécuter |
--layer NAMESPACE:LAYER | – | Limiter le mode automatique à un type d'entité ; requis pour fields et values |
--namespace PREFIX | – | Ne considérer que les couches de ce préfixe d'espace de noms |
--capabilities-file FILE | – | Utiliser une réponse GetCapabilities locale au lieu d'un téléchargement |
--field NAME | répétable | Propriété à exporter en mode values |
--all-properties | désactivé | Exporter toutes les propriétés en mode values |
--unique | désactivé | Supprimer les combinaisons de valeurs de champs en double |
--page-size N | 500 | Entités par page GetFeature ; plage de 1 à 5000 |
--sample-size N | 5 | Valeurs d'échantillon par couche pour la vérification de syntaxe JSON ; plage de 1 à 100 |
--sleep SECONDS | 0 | Activer la vérification basée sur le temps ; 0 ou 1 à 10 secondes autorisés |
--candidate-scope auto|id|all | auto | Sélection des candidats ; auto utilise les champs ID sans sommeil et tous les champs non géométriques avec sommeil |
--timing-result-type auto|hits|results | auto | Chemin de requête du contrôle de synchronisation ; auto essaie hits, puis results |
geoserver_sqli_working.py peut appeler directement la fonction d'inventaire. Dès que --valid-fields est spécifié, seul l'inventaire est exécuté et le programme se termine ensuite.
python3 geoserver_sqli_working.py \
--target https://HOST \
--valid-fields 4 \
--sleep 1 \
--valid-diagnose output.txt
Les paramètres suivants sont transmis en interne à 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
Sans --sleep explicite, le contrôle basé sur le temps reste désactivé en mode inventaire.
Uniquement pour les systèmes de test explicitement autorisés :
python3 geoserver_sqli_working.py \
--target https://HOST/geoserver/wfs \
--typename namespace:layer \
--field_name FIELD \
--sleep 5
En mode PoC, le chemin cible n'est pas normalisé automatiquement. Ici, le point de terminaison WFS complet doit être spécifié.
Le mode exécute d'abord un test de référence/sommeil, teste ensuite les variantes Oracle basées sur le temps existantes et, en cas de confirmation réussie, lit par défaut les métadonnées du serveur/base de données. Avec --query, une requête scalaire personnalisée peut être spécifiée à la place.
Le mode PoC désactive actuellement la vérification du certificat TLS en interne. Pour un inventaire pur,
wfs_inventory.pydoit être préféré, car TLS y est vérifié par défaut.
geoserver_sqli_working.py| Paramètre | Défaut | Signification |
|---|---|---|
--target URL | requis | Hôte cible ou point de terminaison WFS ; utiliser le chemin WFS complet en mode PoC |
--typename NAME | fink_bku:fink_meta_mitte_suedwest | Type d'entité pour le mode PoC |
--field_name NAME | requis en mode PoC | Nom de champ XSD simple pour jsonArrayContains |
--field-name NAME | Alias | Alias pour --field_name |
--valid-fields N | – | Activer le mode inventaire et s'arrêter après N couches valides |
--valid-diagnose FILE | – | Écrire le rapport d'inventaire détaillé dans FILE ; nécessite --valid-fields |
--sleep SECONDS | PoC : 5, inventaire : désactivé | Durée de sommeil du mode respectif |
--candidate-scope auto|id|all | auto | Sélection des candidats en mode inventaire |
--timing-result-type auto|hits|results | auto | Chemin de requête de synchronisation en mode inventaire |
--sleep-threshold RATIO | 0.7 | Seuil de synchronisation en mode inventaire |
--confirm-sleep SECONDS | automatique | Sommeil de confirmation en mode inventaire |
--query SQL | – | Requête scalaire personnalisée en mode PoC |
--debug | désactivé | Afficher les conditions, durées d'exécution et décisions en mode PoC |
Le mode automatique peut générer des lignes JSON au lieu de blocs de texte :
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 10 \
--report-format jsonl \
--output report.jsonl
Avec --diagnose, chaque enregistrement contient en outre des URL, des métadonnées de candidats, des valeurs de vérification et des URL GetFeature spécifiques aux champs.
Utiliser un proxy :
python3 wfs_inventory.py \
--url https://HOST \
--proxy http://127.0.0.1:8080 \
--valid-fields 4
Approuver son propre CA de proxy :
python3 wfs_inventory.py \
--url https://HOST \
--proxy http://127.0.0.1:8080 \
--proxy-ca proxy-ca.pem \
--valid-fields 4
Désactiver la vérification TLS pour un système de test autorisé :
python3 wfs_inventory.py \
--url https://HOST \
--insecure \
--valid-fields 4
--proxy-ca et --insecure ne peuvent pas être utilisés ensemble.
Pour les grandes réponses GetCapabilities, les options suivantes aident :
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 4 \
--max-layers 100 \
--sample-size 1 \
--timeout 10 \
--retries 0 \
--delay 0
Remarques :
GetCapabilities n'est demandé qu'une seule fois puis diffusé en continu.--valid-fields 4 ne termine l'analyse qu'après quatre couches valides. S'il existe moins de résultats, la recherche se poursuit jusqu'à la fin ou jusqu'à --max-layers.--sample-size 1 réduit l'effort de l'heuristique JSON.--timing-result-type auto, deux requêtes results supplémentaires sont exécutées après un résultat hits négatif.--candidate-scope id réduit le nombre de requêtes de synchronisation, mais peut omettre des champs vulnérables avec d'autres noms.--namespace et --start-layer-index peuvent en outre limiter l'espace de recherche.The read operation timed out--timeout si le serveur répond lentement.--retries 0 pour éviter de longues nouvelles tentatives.--max-layers et --namespace.--sample-size 1.--sleep, les candidats doivent réussir la vérification XSD/nom et la vérification de syntaxe JSON selon --candidate-scope.--sleep, seuls les candidats confirmés dans le temps sont émis comme bloc de paramètres ; les mesures négatives apparaissent comme [sleep-check] sur stderr.--max-layers.Cannot do natural order without a primary keyPour la première page GetFeature, le script n'envoie pas startIndex=0, car certaines couches GeoServer/JDBC sans clé primaire imposent déjà un tri naturel de cette manière. Lors de l'exportation de pages supplémentaires, une telle couche peut néanmoins nécessiter une clé primaire ou un tri pris en charge côté serveur.
schema does not define ...TypeLe script prend en compte à la fois le <LayerName>Type habituel et un complexType différent, référencé dans l'élément de couche XSD global ou anonyme. Si l'erreur persiste, la réponse DescribeFeatureType concernée doit être vérifiée avec --diagnose.
Le message d'erreur OWS complet est émis sur stderr. Les causes fréquentes sont des propriétés non prises en charge, des spécifications de pagination côté serveur ou une configuration de source de données spécifique à la couche.
python3 wfs_inventory.py --help
python3 geoserver_sqli_working.py --help
--sleep-threshold RATIO0.7 |
| Proportion requise du temps de sommeil ; plage de 0.5 à 1.0 |
--confirm-sleep SECONDS | 0/automatique | Sommeil de confirmation ; 0 utilise au moins 3× le premier temps de sommeil, plage de 1 à 10 |
--start-layer-index N | 0 | Ignorer les N premières couches diffusées |
--max-layers N | 0 | Traiter au plus N couches ; 0 signifie illimité |
--valid-fields N | 0 | S'arrêter après N blocs de couches valides ; 0 signifie illimité |
--valid N | Alias | Alias rétrocompatible pour --valid-fields |
--max-features N | 0 | S'arrêter après N entités en mode values ; 0 signifie illimité |
--format jsonl|csv|text | jsonl | Format de sortie en mode values |
--output FILE | stdout | Écrire le rapport ou les valeurs dans un fichier |
--report-format blocks|jsonl | blocks | Format du rapport automatique |
--diagnose | désactivé | Ajouter les URL, types, drapeaux de sélection et statistiques de vérification |
--valid-diagnose FILE | – | Activer le diagnostic et l'écrire directement dans FILE |
--delay SECONDS | 0.1 | Pause entre les étapes de résultat/vérification |
--timeout SECONDS | 30 | Délai d'expiration par requête HTTP |
--retries N | 2 | Nouvelles tentatives après délai d'expiration ou erreur réseau ; plage de 0 à 10 |
--proxy URL | – | Proxy HTTP(S), par exemple http://127.0.0.1:8080 |
--proxy-ca FILE | – | Certificat CA PEM pour approuver un certificat de proxy |
--insecure | désactivé | Désactiver la vérification du certificat TLS |
--authorization TEXT | I_AM_AUTHORIZED | Confirmation de sécurité ; doit être exactement I_AM_AUTHORIZED |