CVE-2026-59520

CrawlWP SEO – Instant Search Engine Indexing & SEO Performance Monitor <= 3.0.16 - Cross-Site Request Forgery

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

Description

The CrawlWP SEO – Instant Search Engine Indexing & SEO Performance Monitor plugin for WordPress is vulnerable to Cross-Site Request Forgery in versions up to, and including, 3.0.16. This is due to missing or incorrect nonce validation on a function. This makes it possible for unauthenticated attackers to perform an unauthorized action 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<=3.0.16
PublishedJuly 5, 2026
Last updatedJuly 7, 2026
Affected pluginmihdan-index-now

What Changed in the Fix

Changes introduced in v3.0.17

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan details the exploitation of **CVE-2026-59520**, a Cross-Site Request Forgery (CSRF) vulnerability in the **CrawlWP SEO** plugin for WordPress. The vulnerability arises from a conditional nonce verification logic error and missing nonce checks on sensitive administrative functions.…

Show full research plan

This research plan details the exploitation of CVE-2026-59520, a Cross-Site Request Forgery (CSRF) vulnerability in the CrawlWP SEO plugin for WordPress. The vulnerability arises from a conditional nonce verification logic error and missing nonce checks on sensitive administrative functions.

1. Vulnerability Summary

The vulnerability is primarily located in the Mihdan\IndexNow\Views\Log_List_Table::bulk_action_handler method. This function implements a "conditional" nonce check: it only verifies the nonce if the _wpnonce parameter is present in the request. If the parameter is omitted entirely, the verification is skipped, and the function proceeds to perform the requested action (such as deleting logs).

Additionally, the Mihdan\IndexNow\Providers\Yandex\YandexWebmaster::get_api_token method, which handles OAuth token storage, is hooked to admin_init and lacks any nonce validation, allowing an attacker to overwrite the site's Yandex API tokens via a GET request.

2. Attack Vector Analysis

  • Vulnerable Endpoints:
    • Log Deletion: /wp-admin/admin.php?page=crawlwp&wposa-menu=crawlwp_log (POST)
    • Yandex OAuth Hijacking: /wp-admin/admin.php?page=crawlwp&code=[CODE]&state=yandex-webmaster (GET)
  • Authentication: Requires a victim with manage_options capabilities (Administrator).
  • Payload Parameter:
    • For Log Deletion: action=delete and log_rows[] (array of IDs).
    • For Yandex Hijacking: code (attacker's OAuth code).
  • Preconditions: The plugin must be active. For the OAuth attack, the plugin must be configured with a Client ID/Secret (or the attacker provides their own via CSRF to the settings page first).

3. Code Flow

  1. Entry Point: The admin_init hook triggers Mihdan\IndexNow\Views\Settings::setup_hooks.
  2. Hook Registration: Settings::setup_hooks registers a callback on wpposa_load_menu_hook.
  3. Instantiation: When an admin visits the plugin settings page with wposa-menu=crawlwp_log, the Log_List_Table class is instantiated.
  4. Vulnerable Sink: The Log_List_Table::__construct calls bulk_action_handler().
  5. Bypass Logic (src/Views/Log_List_Table.php):
    private function bulk_action_handler() {
        global $wpdb;
        // BUG: Nonce is only verified IF it is provided. 
        // If $_POST['_wpnonce'] is empty/unset, the check is bypassed.
        if ( ! empty($_POST['_wpnonce']) && ! wp_verify_nonce(wp_unslash($_POST['_wpnonce']), 'bulk-' . $this->_args['plural'])) {
            return;
        }
    
        // Action proceeds if nonce is omitted
        if (isset($_POST['log_rows']) && is_array($_POST['log_rows']) && 'delete' === $this->current_action()) {
            // ... deletion logic ...
        }
    }
    

4. Nonce Acquisition Strategy

No nonce is required for this exploit.
The vulnerability in the bulk_action_handler is a conditional bypass. By explicitly omitting the _wpnonce parameter from the POST request, the ! empty($_POST['_wpnonce']) condition evaluates to false, causing the security check to be skipped entirely.

For the Yandex OAuth vulnerability in src/Providers/Yandex/YandexWebmaster.php, there is no nonce check implemented at all.

5. Exploitation Strategy

This plan focuses on unauthorized log deletion to demonstrate the CSRF.

Step 1: Identify Log IDs
The attacker must know the IDs of the logs to delete. In a real-world scenario, an attacker might target a wide range of IDs (e.g., 1 to 100).

Step 2: Forge the CSRF Request
Use the http_request tool to simulate an administrator submitting a bulk deletion request without a nonce.

  • URL: http://localhost:8080/wp-admin/admin.php?page=crawlwp&wposa-menu=crawlwp_log
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body: action=delete&log_rows%5B%5D=1&log_rows%5B%5D=2 (URL-encoded action=delete&log_rows[]=1&log_rows[]=2)

6. Test Data Setup

Before running the exploit, ensure there is data in the log table:

  1. Identify Table Name:
    wp db tables | grep logs
    
    (Likely wp_crawlwp_logs or wp_mihdan_index_now_log).
  2. Insert Dummy Logs:
    wp eval 'global $wpdb; $table = (new Mihdan\IndexNow\Logger\Logger(new Mihdan\IndexNow\Container()))->get_logger_table_name(); $wpdb->insert($table, ["message" => "Security Test", "created_at" => current_time("mysql")]);'
    
  3. Verify Presence:
    wp db query "SELECT * FROM wp_mihdan_index_now_log;"
    

7. Expected Results

  • The http_request should return a 200 OK response (the WordPress admin page).
  • The log entries specified in the log_rows parameter should be deleted from the database.
  • No "Failure" or "Invalid Nonce" message should appear because the check was bypassed.

8. Verification Steps

After the exploit, verify the database state via WP-CLI:

# Check if the log entries still exist
wp db query "SELECT count(*) FROM wp_mihdan_index_now_log WHERE log_id IN (1, 2);"
# Expected output: 0

9. Alternative Approaches

Yandex OAuth Account Hijack:
Tricking the admin into clicking a link can force the site to link to an attacker-controlled Yandex account.

  • URL: http://localhost:8080/wp-admin/admin.php?page=crawlwp&code=ATTACKER_OAUTH_CODE&state=yandex-webmaster
  • Result: The plugin will attempt to save ATTACKER_OAUTH_CODE and exchange it for tokens, overwriting any existing legitimate configuration.

Site Verification Reset:
The yandex_webmaster_find_website function in src/Providers/Yandex/YandexWebmaster.php also lacks a nonce.

  • Payload: POST to /wp-admin/admin.php?page=crawlwp with submit_crawlwp_yandex_webmaster=1.
  • Result: Forces the plugin to re-run the website discovery logic and potentially change the host_id setting.

Check if your site is affected.

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