SEO Redirection Plugin – 301 Redirect Manager <= 9.17 - Unauthenticated Stored Cross-Site Scripting
Description
The SEO Redirection Plugin – 301 Redirect Manager plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 9.17 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=9.17What Changed in the Fix
Changes introduced in v9.18
Source Code
WordPress.org SVNThis exploitation research plan targets **CVE-2026-52702**, an unauthenticated stored cross-site scripting (XSS) vulnerability in the **SEO Redirection Plugin – 301 Redirect Manager** (versions <= 9.17). ### 1. Vulnerability Summary The vulnerability arises from the plugin's feature that logs 404 e…
Show full research plan
This exploitation research plan targets CVE-2026-52702, an unauthenticated stored cross-site scripting (XSS) vulnerability in the SEO Redirection Plugin – 301 Redirect Manager (versions <= 9.17).
1. Vulnerability Summary
The vulnerability arises from the plugin's feature that logs 404 errors and redirection hits. When a visitor triggers a 404 error or a configured redirect, the plugin records metadata about the request—including the Request URL, Referer, and User-Agent—into the WordPress database.
Because the plugin fails to properly sanitize this input before storage or, more critically, fails to escape it when displaying the history logs in the admin dashboard (using the datagrid class), an unauthenticated attacker can inject malicious scripts into the logs. These scripts execute when an administrator views the "404 Errors" or "History" tabs.
2. Attack Vector Analysis
- Endpoint: Any non-existent URL (to trigger a 404 log) or a URL that matches an existing redirection rule.
- Vulnerable Parameters:
HTTP_REFERERheader,HTTP_USER_AGENTheader, or theREQUEST_URI(specifically query parameters). - Authentication: None (Unauthenticated).
- Preconditions: The plugin must have 404 logging enabled (this is a core feature and typically enabled by default).
3. Code Flow
- Entry Point: A visitor requests a URL that triggers
WPSR_redirect(hooked towpinseo-redirection.php). - Detection: If the request results in a 404, the plugin calls a logging function (e.g.,
WPSR_add_404_log). - Data Collection: The logging function retrieves request data. In
common/util.php, theget_current_URL()andget_ref()functions are used to fetch the URL and Referer.- Bypass Note:
util.phpshowsurldecode(sanitize_text_field($_SERVER['QUERY_STRING']))inWPSR_get_current_parameters. Applyingurldecodeafter sanitization effectively re-introduces malicious characters like<and>.
- Bypass Note:
- Storage: The unsanitized/decoded string is inserted into the 404 history table (e.g.,
wp_WP_SEO_404_History). - Sink (Admin View): An administrator navigates to the plugin settings:
wp-admin/options-general.php?page=seo-redirection.php&tab=404. - Rendering: The page uses the
datagridclass (common/controls/datagrid.class.php). The grid callsadd_data_coloradd_html_colto display the log entries. If these columns are rendered withoutesc_html(), the stored script executes.
4. Nonce Acquisition Strategy
No nonce is required for the injection phase because the logging mechanism is triggered by unauthenticated frontend requests.
To verify the exploit as an admin:
- The agent must log in as an administrator.
- Navigate to the 404 log tab:
admin_url('options-general.php?page=seo-redirection.php&tab=404'). - No nonce is needed to view the logs, though one may be needed if the exploit requires interacting with the "Delete" or "Redirect" buttons in the grid.
5. Exploitation Strategy
The goal is to trigger a 404 error while providing a malicious payload in the Referer or URL.
Step 1: Trigger 404 via Malicious Referer
- Method: GET
- URL:
http://[target]/this-page-does-not-exist-123 - Headers:
Referer: <script>alert('XSS_REFERER')</script>User-Agent: <img src=x onerror=alert('XSS_UA')>
Step 2: Trigger 404 via Malicious Query String
- Method: GET
- URL:
http://[target]/trigger-404?debug=%3Cscript%3Ealert('XSS_URL')%3C/script%3E - Note: Use URL encoding to ensure the payload survives the initial HTTP request.
Step 3: Admin Trigger
- Use
browser_navigateto visit the 404 History tab. - Monitor for dialogs/alerts using
browser_eval.
6. Test Data Setup
- Ensure the plugin SEO Redirection (v9.17) is active.
- Ensure 404 logging is enabled in the plugin settings (usually default).
- Create a "Canary" 404 request to confirm logging is working:
GET /canary-404- Verify entry appears in
wp-admin/options-general.php?page=seo-redirection.php&tab=404.
7. Expected Results
- The malicious payload is stored in the database table tracking 404 errors.
- When an administrator views the 404 History tab, the browser executes the injected JavaScript.
- The
datagridshould render the payload as raw HTML instead of literal text.
8. Verification Steps
After sending the malicious HTTP requests, verify the storage in the database via WP-CLI:
# Check if the payload exists in the 404 history table
wp db query "SELECT * FROM $(wp db prefix)WP_SEO_404_History ORDER BY id DESC LIMIT 5;"
Verify the output rendering:
- Navigate to the admin page.
- Inspect the HTML source of the table:
browser_eval("document.querySelector('.grid').innerHTML"). - Look for the unescaped
<script>or<img>tags.
9. Alternative Approaches
- Redirection Hit Vector: If the site has an existing redirect (e.g.,
/old->/new), request the "from" URL with a malicious Referer. The plugin might log successful redirects in a "History" tab (tab=history). - Admin Bar Vector: The plugin displays the latest 404 in the Admin Bar for logged-in users (see
WPSR_add_link_to_admin_bar). If therelative_urlis reflected there without escaping, XSS will trigger on every admin page load, not just the settings page.- Code Reference:
seo-redirection.phplines 135-155 uses$relative_urlinadmin_url(...)and node titles. Check ifadd_nodetitles are escaped.
- Code Reference:
Summary
The SEO Redirection Plugin for WordPress is vulnerable to unauthenticated stored Cross-Site Scripting (XSS) due to insufficient sanitization and escaping of request metadata, such as the Referer header, User-Agent, and visitor IP address. Attackers can inject malicious scripts by triggering 404 errors or redirection events, which are then executed in the context of an administrator viewing the plugin's log dashboard.
Vulnerable Code
/* common/util.php line 352 */ public function get_visitor_IP() { $ipaddress = sanitize_text_field($_SERVER['REMOTE_ADDR']); if (!empty($_SERVER['HTTP_CLIENT_IP'])) { $ipaddress = sanitize_text_field($_SERVER['HTTP_CLIENT_IP']); } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ipaddress = sanitize_text_field($_SERVER['HTTP_X_FORWARDED_FOR']); } return sanitize_text_field($ipaddress) ; } --- /* common/util.php line 445 */ public function WPSR_get_current_parameters($remove_parameter="") { if($_SERVER['QUERY_STRING']!='') { $qry = '?' . urldecode(sanitize_text_field($_SERVER['QUERY_STRING'])); --- /* common/controls/datagrid.class.php around line 442 (inferred from patch) */ if (is_array($ar)) { foreach ($ar as $key => $value) { $key_var = "db_" . $key; $html = str_ireplace('{' . $key_var . '}', ${$key_var} ?? '', $html); } }
Security Fix
@@ -442,7 +442,10 @@ if (is_array($ar)) { foreach ($ar as $key => $value) { $key_var = "db_" . $key; - $html = str_ireplace('{' . $key_var . '}', ${$key_var} ?? '', $html); + // Security: escape DB values before injecting them into HTML. + // Values like {db_ip} are placed inside both HTML attributes + // and text content; esc_attr() is safe for both. + $html = str_ireplace('{' . $key_var . '}', esc_attr(${$key_var} ?? ''), $html); } } $row[$i] = $html; @@ -352,15 +352,25 @@ public function get_visitor_IP() { - $ipaddress = sanitize_text_field($_SERVER['REMOTE_ADDR']); + $ipaddress = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field($_SERVER['REMOTE_ADDR']) : ''; if (!empty($_SERVER['HTTP_CLIENT_IP'])) { $ipaddress = sanitize_text_field($_SERVER['HTTP_CLIENT_IP']); } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { - $ipaddress = sanitize_text_field($_SERVER['HTTP_X_FORWARDED_FOR']); + // X-Forwarded-For may contain a comma-separated list; take the first entry. + $forwarded = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); + $ipaddress = sanitize_text_field(trim($forwarded[0])); } - return sanitize_text_field($ipaddress) ; + // Security: validate that the value is actually an IP address. + // sanitize_text_field() does not strip HTML attribute breakers like + // double quotes or event-handler payloads, which previously allowed + // stored XSS when the IP was rendered in the admin dashboard. + if (!filter_var($ipaddress, FILTER_VALIDATE_IP)) { + $ipaddress = ''; + } + + return $ipaddress; }
Exploit Outline
1. Identify an endpoint on the WordPress site that triggers a 404 error (any non-existent URL). 2. Craft an HTTP request to this endpoint containing an XSS payload (e.g., `<script>alert(document.cookie)</script>`) in headers like `Referer` or `User-Agent`. 3. Alternatively, supply a malicious payload in the query string of the URL; the plugin's `WPSR_get_current_parameters` function decodes the query string after sanitization, potentially re-introducing malicious characters. 4. If the server trusts proxy headers, the `X-Forwarded-For` header can also be used to inject a payload. 5. Wait for an administrator to log into the WordPress dashboard and navigate to 'Settings' > 'SEO Redirection' > '404 Errors' or 'History'. 6. The payload executes when the `datagrid` class renders the log table, as it replaces placeholders with raw database values without output escaping.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.