Omnisend for WooCommerce <= 1.18.0 - Unauthenticated Omnisend Account Takeover via Predictable Connect Token
Description
The Omnisend for WooCommerce plugin for WordPress is vulnerable to an unauthenticated account takeover via insufficiently random values in versions up to, and including, 1.18.0. This is due to the generate_install_url() function deriving the OAuth connect token solely from the Unix timestamp at page-load time (hash('sha256', time())), which yields only ~86,400 distinct values per day and is trivially brute-forceable. This makes it possible for unauthenticated attackers to predict the connect token, authenticate to the POST /wp-json/omnisend-api/v1/connect REST endpoint, and replace the store's omnisend_api_key and brand_id with attacker-supplied values, redirecting all customer PII synchronization, order webhooks, and marketing communications to the attacker's Omnisend account.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:NTechnical Details
<=1.18.0What Changed in the Fix
Changes introduced in v1.18.1
Source Code
WordPress.org SVN# Vulnerability Research Plan: CVE-2026-42668 - Omnisend Account Takeover ## 1. Vulnerability Summary The **Omnisend for WooCommerce** plugin (up to and including version 1.18.0) contains a critical vulnerability where the OAuth connection token is generated using a predictable value. Specifically,…
Show full research plan
Vulnerability Research Plan: CVE-2026-42668 - Omnisend Account Takeover
1. Vulnerability Summary
The Omnisend for WooCommerce plugin (up to and including version 1.18.0) contains a critical vulnerability where the OAuth connection token is generated using a predictable value. Specifically, the Omnisend_Install::generate_install_url() function creates a connect_token using hash('sha256', time()). Since time() is a Unix timestamp with second-level precision, the resulting token space is extremely small (~86,400 values per day), allowing an unauthenticated attacker to brute-force the token and gain access to the POST /wp-json/omnisend-api/v1/connect REST endpoint.
Successful exploitation allows an attacker to overwrite the omnisend_api_key and omnisend_account_id (brand ID) settings, effectively hijacking the data synchronization between the WordPress store and Omnisend. This results in sensitive customer PII and order data being sent to the attacker's Omnisend account.
2. Attack Vector Analysis
- REST Endpoint:
POST /wp-json/omnisend-api/v1/connect - Permissions: Restricted by the
validate_connect_tokenpermission callback, which requires a validconnect_token. - Vulnerability: The
connect_tokenis generated using a predictabletime()value and persists in theomnisend_connect_tokenoption until used or explicitly cleared. - Precondition: The
omnisend_connect_tokenmust have been generated (typically by an administrator visiting the Omnisend settings page) but not yet used to complete a connection.
3. Code Flow
- Token Generation:
- When an administrator visits the settings page,
Omnisend_Install::generate_install_url()(inincludes/class-omnisend-install.php) is called. - It checks if
omnisend_connect_tokenis empty:$token = get_option( 'omnisend_connect_token', '' ); if ( $token === '' ) { $token = hash( 'sha256', time() ); update_option( 'omnisend_connect_token', $token ); }
- When an administrator visits the settings page,
- REST Authentication:
- The
/connectroute usesvalidate_connect_tokenas itspermission_callback(includes/omnisend-api.php). validate_connect_tokenextractsconnect_tokenfrom the JSON body and compares it to the stored option:$token = get_option( 'omnisend_connect_token', '' ); if ( $token !== $request['connect_token'] ) { // 401 Unauthorized if mismatch
- The
- Account Takeover:
- If the token matches,
omnisend_connect_accountis executed. - It updates the API keys with the attacker-provided values:
update_option( 'omnisend_connect_token', null ); Omnisend_Settings::set_brand_id( $body['brand_id'] ); update_option( 'omnisend_api_key', $body['omnisend_api_key'] );
- If the token matches,
4. Nonce Acquisition Strategy
This vulnerability does not require a WordPress nonce for the final exploitation of the REST endpoint. The permission_callback only validates the connect_token.
However, to trigger the generation of the token if it doesn't exist, an administrator must visit the settings page. For the purpose of a PoC in a test environment, the agent should simulate this by navigating to the Omnisend settings page or manually calling the function via WP-CLI.
5. Exploitation Strategy
Step 1: Synchronize Time
Obtain the server's current time to establish a baseline for the brute-force attack.
- Request:
GET / - Action: Extract the
Dateheader from the response. Convert this to a Unix timestamp.
Step 2: Brute Force Token
Iterate through timestamps backwards from the current time (e.g., covering the last 24 hours).
- Target Endpoint:
POST /wp-json/omnisend-api/v1/connect - Method:
POST - Headers:
Content-Type: application/json - Payload:
{ "connect_token": "HASH_OF_TIMESTAMP", "brand_id": "attacker_brand_123", "omnisend_api_key": "attacker_api_key_456" } - Logic:
- Calculate
token = hash('sha256', (string)$timestamp). - Send request.
- If response is
401(omnisend_incorrect_connect_token), decrement timestamp and retry. - If response is
403(omnisend_connect_denied), the token is empty or already used (abort/reset). - If response is
200(success: true), the takeover is successful.
- Calculate
6. Test Data Setup
- Install and activate the plugin:
wp plugin install omnisend-connect --version=1.18.0 --activate. - Ensure the plugin is in an "unconnected" state.
- Critical Step: Simulate an admin visit to generate the token:
- Use
wp eval "Omnisend_Install::get_registration_url();"OR - Login as admin and navigate to
wp-admin/admin.php?page=omnisend-woocommerce.
- Use
7. Expected Results
- The brute-force script should eventually identify the timestamp used during the "admin visit" simulation.
- The server will return a
200 OKresponse with{"success": true}. - The
omnisend_api_keyoption will be updated toattacker_api_key_456.
8. Verification Steps
After the HTTP exploit succeeds, verify the database state using WP-CLI:
- Check Brand ID:
wp option get omnisend_account_id - Check API Key:
wp option get omnisend_api_key - Check Token cleared:
wp option get omnisend_connect_token(should be empty/null)
9. Alternative Approaches
If the plugin is already connected, the validate_connect_token function returns a 403. In a real-world scenario, the attacker would wait for a "disconnect" event (or trigger one if another vulnerability exists) to force the site back into the connectable state. For this PoC, focus on the initial connection phase or a site where the admin has navigated to the setup page but hasn't finalized the connection.
Summary
The Omnisend for WooCommerce plugin is vulnerable to an unauthenticated account takeover due to the use of a predictable OAuth connection token generated using only the current Unix timestamp. Attackers can brute-force the approximately 86,400 possible tokens for a given day to authenticate to a sensitive REST endpoint and overwrite the store's API keys. This allows the attacker to hijack the synchronization of sensitive customer PII and order data to their own Omnisend account.
Vulnerable Code
// includes/class-omnisend-install.php (around line 234 in version 1.18.0) private static function generate_install_url() { $token = get_option( 'omnisend_connect_token', '' ); if ( $token === '' ) { $token = hash( 'sha256', time() ); update_option( 'omnisend_connect_token', $token ); } --- // includes/omnisend-api.php (around line 170 in version 1.18.0) function validate_connect_token( WP_REST_Request $request ) { $body = json_decode( $request->get_body(), true ); if ( ! isset( $body['connect_token'] ) ) { return new WP_Error( 'omnisend_missing_connect_token', 'Missing connect token in request.', array( 'status' => 400 ) ); } $token = get_option( 'omnisend_connect_token', '' ); if ( $token === '' ) { return new WP_Error( 'omnisend_connect_denied', 'Connect token is already used.', array( 'status' => 403 ) ); } if ( $token !== $request['connect_token'] ) { return new WP_Error( 'omnisend_incorrect_connect_token', 'Connect token is incorrect.', array( 'status' => 401 ) ); } return true; }
Security Fix
@@ -231,7 +231,7 @@ $token = get_option( 'omnisend_connect_token', '' ); if ( $token === '' ) { - $token = hash( 'sha256', time() ); + $token = bin2hex( random_bytes( 32 ) ); update_option( 'omnisend_connect_token', $token ); } @@ -147,34 +147,30 @@ $body = json_decode( $request->get_body(), true ); if ( ! isset( $body['connect_token'] ) ) { - return new WP_Error( - 'omnisend_missing_connect_token', - 'Missing connect token in request.', - array( 'status' => 400 ) - ); + return omnisend_connect_token_invalid_error(); } $token = get_option( 'omnisend_connect_token', '' ); if ( $token === '' ) { - return new WP_Error( - 'omnisend_connect_denied', - 'Connect token is already used.', - array( 'status' => 403 ) - ); + return omnisend_connect_token_invalid_error(); } - if ( $token !== $request['connect_token'] ) { - return new WP_Error( - 'omnisend_incorrect_connect_token', - 'Connect token is incorrect.', - array( 'status' => 401 ) - ); + if ( ! hash_equals( $token, (string) $request['connect_token'] ) ) { + return omnisend_connect_token_invalid_error(); } return true; } +function omnisend_connect_token_invalid_error() { + return new WP_Error( + 'omnisend_invalid_connect_token', + 'Connect token is invalid.', + array( 'status' => 403 ) + ); +}
Exploit Outline
The exploit targets the vulnerable OAuth connection process of the Omnisend plugin. 1. **Preparation**: The attacker identifies a target site where the administrator has initiated but not completed the Omnisend connection process (or triggers the process if possible). This populates the `omnisend_connect_token` option with a predictable value: `hash('sha256', time())`. 2. **Time Synchronization**: The attacker sends a request to the target site (e.g., `GET /`) and captures the `Date` header to synchronize their local clock with the server's Unix timestamp. 3. **Brute-Force**: The attacker iterates through potential timestamps (typically backwards from the current time) and calculates the SHA256 hash for each second. 4. **Endpoint Hit**: For each candidate token, the attacker sends an unauthenticated `POST` request to `/wp-json/omnisend-api/v1/connect` with a payload containing the candidate `connect_token`, an attacker-controlled `brand_id`, and an `omnisend_api_key`. 5. **Validation**: A response code of `401` indicates an incorrect token, while a `200 OK` response indicates a successful guess and account takeover. 6. **Takeover**: Once successful, the plugin replaces the legitimate connection settings with the attacker's credentials, rerouting all subsequent WooCommerce webhooks and customer data syncs to the attacker's infrastructure.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.