Precursor Security
Intelligence Library
Threat Intelligence

WooCommerce Subscriptions Persistent XSS (CVE-2019-18834)

2 September 2024
·
11 min read
·Precursor Security

A persistent Cross-Site Scripting vulnerability in WooCommerce Subscriptions before version 2.6.3 allowed unauthenticated users to execute malicious scripts in the WordPress admin panel, leading to potential account takeover and privilege escalation. Timely updates and integrated security practices directly affect risk exposure.

What Is CVE-2019-18834?

CVE (Common Vulnerabilities and Exposures) 2019-18834 is a persistent XSS vulnerability discovered in the WooCommerce Subscriptions plugin for WordPress. The details below summarise the key facts established at the time of disclosure.

  • CVE - CVE-2019-18834
  • Vulnerability - Persistent Cross Site Scripting
  • Affected - WooCommerce Subscriptions before version 2.6.3
  • Location - Client Billing Information
  • Remediation - Update to the latest available version or at least version 2.6.3
  • CVSS (Common Vulnerability Scoring System) v3.1 Base Score - 6.1 (Medium)
  • Impact - Compromise of admin console

I was working on a test last year for a fairly standard WordPress site. The site wasn't completely finished but had all the core functionality setup which would form part of the final site. No custom content was included, just stock WordPress with a few plugins, everything up to date, so the test was only planned as a short engagement.

The plugins used in this build were mainly developed by WooCommerce. I was aware of this suite of plugins, but not really had the opportunity to test them to any degree or depth before. Looking at the WooCommerce site, they have been running for years and have recorded millions of downloads so any thoughts of coming across anything of interest on my test were quickly dwindling.

Through browsing around the site, I came across the client facing interface for the WooCommerce Subscriptions plugin. This was a fairly simple form to enter some basic customer details like name, address, phone number that kind of thing and it had a checkout page which listed the details you entered.

Since there was reflected content in the checkout page, I was immediately thinking of some potential for Cross Site Scripting (XSS), and so started trying some basics to see how certain characters would be handled. I usually default to an "img src" approach, as I often have a better initial success rate with this so tried some standards like:

"><img src=a onerror=alert(9)>

No joy there. Responses came back with just the ">, so clearly WooCommerce have come across XSS before and accounted for it. To be expected really considering their time spent in development.

So, I started trying a few variations to see if I could enumerate exactly what would trigger this filter. I guessed it would be just a straight up rejection of anything between <>, and after a few submissions, that's certainly what it was looking like, anything submitted in a tag would be rejected outright and the whole thing removed from the response. After a few attempts at obfuscation, I tried throwing in a couple extra brackets <>. It's a fairly simple variant and I didn't expect much, but why not, YOLO.

Surprisingly, this actually went through. It turns out there wasn't actually recursive filtering happening, so:

"><img src=a o<>nerror=alert(9)>

Came back as:

"><img src=a onerror=alert(9)>

Only the first identified instance of the angled brackets was being identified and removed, and the two halves of my valid payload on either side were then being brought together to form my complete valid XSS payload.

It seemed a little odd this hadn't been accounted for before now, but here we were. Unfortunately, this brought me straight into another level of protection, HTML encoding. My required characters, in this case quotes, were being encoded in the response page. So, that was a fun few minutes, but looked like it was a dead-end.

That avenue closed, the test continued.

Looking through the WordPress admin panel I began going through each page that WooCommerce have added in there, grasping at straws really, looking for any content handling that was a little unusual.

There were some of my XSS attempts showing up in the subscriptions list, nothing of particular interest there, more HTML encoding same as before.

An alert box fired, triggered by the earlier XSS attempts stored in the admin panel.

So, it looks like there was some variation in content handling after all. A little mouseover function of the subscription text was loading a pop-up box and making a call out to the following URL to populate the content.

https://xxxxx.xxxxx.xxxxx/wp-admin/user-edit.php?user_id=15&wp_http_referer=%2Fwp-admin%2Fusers.php

This call then loaded in my XSS attempts without any of the previous encoding.

So, What Is the Problem Here?

This is a combination of issues. First, the client submission of content is only subjected to a non-recursive filter. This allows valid XSS payloads to slip through with a fairly simple filter evasion technique.

Secondly, the application does not conduct correct encoding of the client defined output, as it is returned into the admin panel.

This dual failure corresponds to CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting'), the standardised classification for XSS vulnerabilities. Output encoding at the point of render, as mandated by the OWASP XSS Prevention Cheat Sheet, would have prevented the payload from executing regardless of what reached the database. OWASP's defence-in-depth principle requires that both input filtering and output encoding are applied independently; relying on either alone is insufficient.

What Exactly Does This Mean?

This vulnerability is potentially quite serious. It's a Persistent Cross Site Scripting vulnerability, meaning the data is stored by the application and doesn't require the payload to be submitted from a victim such as with Reflected Cross Site Scripting.

To understand why this matters, it helps to see how the three main XSS variants compare:

AttributeReflected XSSPersistent XSSDOM-based XSS
StorageNot stored; payload in requestStored in database or fileNot stored; payload in client-side script
Victim action requiredMust click a crafted linkOnly needs to visit the pageMust visit a page with vulnerable JS
Affected usersTypically one targeted userAll users who load the infected pageUsers of the specific affected page
Detection difficultyLower; payload visible in URLHigher; payload hidden in stored dataHighest; no server-side trace
CVE-2019-18834 appliesNoYes - payload stored via subscription formNo

Persistent XSS also has a much higher likelihood of success, because you can submit the XSS payload and then just wait. Coercing a user into clicking a link, through phishing, isn't really required as you can simply assume that a user will eventually navigate to the affected page and trigger the payload, this can be much more likely on a page regularly used for management and administration, such as in this instance.

To make matters worse, this instance of XSS directly ties into account takeover and privilege escalation attacks. The payload is submitted by an unauthenticated user of the site, but then triggers in the WordPress admin panel allowing for targeted attacks directly against the admin users of the site. Unlike the simple and visual alert box example provided here, an actual attack would likely be invisible to the victim. An attacker defined script, quietly running in the background, capturing authenticated session tokens, keystrokes, or any of a number of XSS payload variants.

The NVD (National Vulnerability Database) rates CVE-2019-18834 with a CVSS v3.1 base score of 6.1 (Medium), reflecting the unauthenticated nature of the write path and the admin-context execution on the read path.

While specific numbers for the Subscriptions plugin were a little hard to come by, WooCommerce itself is quite prevalent. According to BuiltWith CMS usage statistics, approximately 28% of all online stores were using WooCommerce as of 2019, representing around 93% of all WordPress ecommerce sites. WooCommerce lists over 5 million active installations on WordPress.org. Its distribution is fairly significant.

Additionally, troubling is that Sucuri's 2023 Hacked Website & Malware Threat Report found that 39.1% of CMS applications were outdated at the time of compromise - and 13.97% of compromised websites had at least one vulnerable plugin or theme in place at the point of infection.

What Should WooCommerce Site Owners Do After This Vulnerability?

Update. A simple enough solution to a large array of attacks, including this one. Ideally through automatic updating configured for your WordPress instance.

To the credit of the WooCommerce team, they were very responsive and easy to work with when disclosing this vulnerability. After providing the details to them, the whole thing was addressed, and patches released within a couple of days.

CVE-2019-18834 was discovered and responsibly disclosed by Jordan Carter at Precursor Security. The timeline below anchors the key events:

EventDate
Vulnerability identified in WooCommerce Subscriptions v2.6.12019 (exact date from researcher records)
Responsible disclosure to WooCommerce team2019 (exact date from researcher records)
Patch released in v2.6.3Within days of disclosure
CVE-2019-18834 assigned by MITRE2019 (NVD record published 07/23/2020)

This vulnerability was identified in a test conducted against WooCommerce Subscriptions v2.6.1, and a patch was released in v2.6.3 to resolve this issue. Always run the latest version of every plugin on live applications.

After the fix was released the vulnerability was also reported and assigned the reference number, CVE-2019-18834.

What Does Secure Development Look Like Going Forward?

Security is too often treated as something separate from the development life cycle - this vulnerability is a clear example of what happens when input filtering and output encoding are treated as separate concerns rather than independent, mandatory controls. For WooCommerce site owners and development teams, four concrete steps directly address the class of failure seen in CVE-2019-18834:

  1. Implement recursive input filtering. Reject payloads at every nesting level, not just the outermost pass. A single-pass filter is bypassable with exactly the technique demonstrated here.
  2. Enforce output encoding at the point of render. Independently of whatever input filtering does or does not catch, data rendered into an HTML context must be encoded for that context. The OWASP XSS Prevention Cheat Sheet defines the correct encoding rule for each rendering context - HTML body, HTML attribute, JavaScript, CSS, and URL.
  3. Schedule plugin update audits. Set a defined cadence - weekly, or triggered on each plugin release - for reviewing installed versions against the WordPress.org changelog. The Sucuri 2023 Hacked Website Report found that over a third of CMS applications were running outdated versions at the point of compromise.
  4. Integrate DAST (Dynamic Application Security Testing) into your CI/CD (Continuous Integration / Continuous Delivery) pipeline. Automated security scanning at each build catches XSS regressions before they reach production, reducing the window between introduction and discovery from months to hours.

If your organisation runs WooCommerce or any web application handling authenticated sessions and user-submitted data, a web application penetration test provides the same adversarial validation that uncovered this vulnerability - testing not just whether functionality works, but whether it works securely.


Frequently Asked Questions

What is CVE-2019-18834?

CVE-2019-18834 is a persistent (stored) Cross-Site Scripting vulnerability in WooCommerce Subscriptions for WordPress, affecting all versions before 2.6.3. It allowed unauthenticated users to submit a crafted XSS payload via the subscription billing form. The payload was stored in the database and later executed in the WordPress admin panel, enabling potential admin session hijacking and privilege escalation. The NVD rates it CVSS v3.1 6.1 (Medium).

What is the difference between persistent XSS and reflected XSS?

Reflected XSS requires a victim to click a specially crafted link - the payload travels in the HTTP request and is never stored. Persistent (stored) XSS is embedded into the application's database or files, so it executes automatically for every user who loads the affected page, with no crafted link required. This makes persistent XSS significantly more dangerous in high-traffic, high-privilege contexts such as WordPress admin panels.

Was CVE-2019-18834 ever patched?

Yes. WooCommerce released a patch in version 2.6.3 within days of responsible disclosure. The fix addressed the non-recursive input filter by enforcing multi-pass sanitisation to prevent the nested-bracket bypass, and added output encoding at the point of render. All sites running WooCommerce Subscriptions before v2.6.3 should update immediately.

How do I know if my WooCommerce site is vulnerable to XSS?

Automated scanners can identify known XSS patterns but will not catch novel filter bypass techniques of the kind exploited in CVE-2019-18834. A manual web application penetration test - conducted by a security researcher who understands filter logic and evasion techniques - is the most reliable method for identifying both known and unknown XSS exposure in a WooCommerce or WordPress environment.

What should I do to prevent XSS in WooCommerce plugins I develop?

Apply two independent controls: recursive input filtering that rejects payloads at every nesting level, and context-aware output encoding at the point of render. The OWASP XSS Prevention Cheat Sheet specifies the correct encoding method for each HTML rendering context. Neither control alone is sufficient - CVE-2019-18834 demonstrates exactly what happens when only one layer is applied.

Expert Guidance

Get expert threat intelligence

Our CREST-accredited SOC monitors threats 24/7 across UK and EMEA — from CVE triage to live incident response.