- 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
- Impact – High. 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.
Back to browsing around the site and looking for something of interest.
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.
And then, pop.
There it was. That little alert box, triggered from my earlier XSS attempts.
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.
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.
WHAT EXACTLY DOES THIS MEAN?
What we have 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.
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.
While specific numbers for the Subscriptions plugin were a little hard to come by WooCommerce itself is quite prevalent. With millions of downloads and around 28% of all online stores reportedly using WooCommerce along with around 93% of all WordPress ecommerce sites. Its distribution is fairly significant.
Additionally, troubling is that multiple reports conclude that around a third of all WordPress sites are still outdated and not running current versions of WordPress or its plugins.
WHAT TO DO NEXT?
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.
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. But as always it is advised to maintain the latest available version of the plugin on your live applications.
After the fix was released the vulnerability was also reported and assigned the reference number, CVE-2019-18834.
Security is too often treated as something separate from the development life cycle, but it actually forms an intrinsic part of any development process. Functionality testing is something that is conducted as a standard part of development already, which makes sense. If something doesn’t work, it can’t be launched. However, instead of asking the question “Does it work?”, the question instead should be “Does it work securely?”. As functional testing is conducted at each stage of the product lifecycle so should security testing, making sure that security is considered and included from the ground up.
This is part of the move to change security from being seen as a one off or point in time exercise and instead view security as more of a continuous process which takes you through the entire development lifecycle and continues beyond the release date, making sure your products and your organisation remain protected against known threats as well as new and emerging threats.