CVE-2026-3903

Modular Connector <= 2.5.1 - Cross-Site Request Forgery via postConfirmOauth

mediumCross-Site Request Forgery (CSRF)
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
2.6.0
Patched in
1d
Time to patch

Description

The Modular DS: Monitor, update, and backup multiple websites plugin for WordPress is vulnerable to Cross-Site Request Forgery in all versions up to, and including, 2.5.1. This is due to missing nonce validation on the postConfirmOauth() function. This makes it possible for unauthenticated attackers to disconnect the plugin's OAuth/SSO connection via a forged request granted they can trick a site administrator into performing an action such as clicking on a link.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
Required
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.5.1
PublishedMarch 10, 2026
Last updatedMarch 11, 2026
Affected pluginmodular-connector

What Changed in the Fix

Changes introduced in v2.6.0

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan targets a CSRF vulnerability in the Modular Connector plugin, which allows an attacker to disconnect the site from the Modular DS dashboard by tricking an administrator into submitting a forged request to the `postConfirmOauth()` handler. ## 1. Vulnerability Summary * **Vulnera…

Show full research plan

This research plan targets a CSRF vulnerability in the Modular Connector plugin, which allows an attacker to disconnect the site from the Modular DS dashboard by tricking an administrator into submitting a forged request to the postConfirmOauth() handler.

1. Vulnerability Summary

  • Vulnerability: Cross-Site Request Forgery (CSRF)
  • Component: OauthController::postConfirmOauth() (Inferred location based on standard plugin architecture and provided controllers).
  • Affected Versions: <= 2.5.1
  • Impact: An attacker can force the WordPress site to disconnect from its management dashboard (Modular DS). This disrupts backups, monitoring, and security scans.
  • Root Cause: The postConfirmOauth function, which handles the finalization of the OAuth connection flow, fails to implement WordPress nonce validation (check_admin_referer or check_ajax_referer).

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php or /wp-admin/admin-post.php (The plugin uses a custom dispatcher).
  • Action: modular_connector_oauth_confirm or modular_connector_confirm_oauth (Inferred).
  • Method: POST
  • Authentication: Requires an active administrator session (Victim).
  • Parameters:
    • action: The hook registered to reach the dispatcher.
    • route: /oauth/confirm (Inferred based on the Laravel-style routing seen in BackupController.php).

3. Code Flow

  1. Entry Point: An administrator visits a malicious page while logged into the WordPress site.
  2. Request Trigger: The malicious page auto-submits a POST request to wp-admin/admin-ajax.php.
  3. Hook Registration: init.php calls \Modular\Connector\WordPress\Admin::setup(), which registers AJAX or admin-post actions.
  4. Routing: The action maps to a dispatcher that instantiates Modular\Connector\Http\Controllers\OauthController.
  5. Vulnerable Sink: The dispatcher calls postConfirmOauth(). Because this function lacks a nonce check, it proceeds to process the "confirmation" request.
  6. Action Taken: The function likely attempts to refresh or update the OAuth credentials. If provided with malformed or empty data via CSRF, it fails the handshake and clears the existing valid credentials in _modular_connection_access_token and _modular_connection_client_id (seen in ModularClient.php), effectively disconnecting the site.

4. Nonce Acquisition Strategy

According to the vulnerability description, this function is vulnerable specifically because it misses nonce validation. Therefore, no nonce is required to exploit this vulnerability.

5. Exploitation Strategy

Step 1: Discover exact action and route

Since the full controller and route files are not provided, the agent must first find the exact action string.

  1. Search for the method definition: grep -r "function postConfirmOauth" .
  2. Search for where this controller/method is routed: grep -r "oauth" . and look for strings like add_action or Route::post.

Step 2: Simulate Connected State

Before testing, ensure the plugin is "connected" so we can verify the disconnection.

  1. Set fake connection options:
    wp option update _modular_connection_client_id "fake_client_id"
    wp option update _modular_connection_access_token "fake_access_token"
    

Step 3: Execute CSRF

The agent will simulate the Admin's browser making the forged request using the http_request tool.

Hypothesized Request (to be refined by discovery):

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Body (URL Encoded):
    action=modular_connector_request&route=/oauth/confirm
    
    (Note: If discovery shows a different action name like modular_connector_oauth_confirm, use that instead).

Step 4: Verification

Confirm that the options have been cleared or modified, indicating a disconnection.

6. Test Data Setup

  1. Plugin Installation: Ensure modular-connector version 2.5.1 is active.
  2. Admin User: Ensure a standard admin user exists (e.g., admin/password).
  3. Connection Simulation:
    wp option update _modular_connection_client_id "poc_test_client"
    wp option update _modular_connection_access_token "poc_test_token"
    

7. Expected Results

  • The POST request should return a 200 OK or 302 Redirect.
  • The WordPress options associated with the Modular DS connection should be deleted or set to empty strings.
  • The response from the server should NOT be a "403 Forbidden" or a "Nonce Verification Failed" error.

8. Verification Steps

  1. Check the connection options via WP-CLI:
    wp option get _modular_connection_client_id
    wp option get _modular_connection_access_token
    
  2. Success Condition: The commands return an empty value or an error stating the option does not exist, whereas they previously contained "poc_test_client" and "poc_test_token".

9. Alternative Approaches

If the admin-ajax.php endpoint requires a different routing structure:

  1. Check if the plugin uses admin-post.php actions.
  2. Check for custom REST API routes: grep -r "register_rest_route" .
  3. If the function postConfirmOauth requires specific parameters (like a code or state), try adding dummy values: &code=123&state=abc. The goal is to trigger the logic that modifies the connection state without a nonce.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.