Security Risk: Dangerous
Exploitation Level: Easy/Remote
DREAD Score: 8/10
Vulnerability: Stored XSS
Patched Version: 3.7.1
During a routine audit for our WAF, we discovered a critical stored XSS affecting the Jetpack WordPress plugin, one of the most popular plugins in the WordPress ecosystem.
Vulnerability Disclosure Timeline:
- September 10th, 2015 – Initial report to Automattic security team
- September 10th, 2015 – Automattic security team acks receipt of report, sets patch date for September 22nd
- September 28th, 2015 – Patch made public with the release of Jetpack 3.7.1 and 3.7.2
- October 1st, 2015 – Sucuri Public Disclosure of Vulnerability
Are You At Risk?
We first noticed the XSS in Jetpack while checking how the plugin managed emails that were sent through pages that contained a contact form:
At first, It looked okay. It’s passing the email address through the same filter (pre_comment_author_email) that WordPress uses for sanitizing emails that are submitted to its comment component. Plus, it’s passing the resulting string to a custom strip_tags() method that does the following:
It passes the string to wp_kses() with an empty array as its second parameter, which prevents us from injecting any HTML tags in the email address. Keeping these constraints in mind, we went forward and tried to insert dummy characters, until we got this result when visiting the Feedback section of the administration panel:
We were able to insert an unsanitized single quote in a single quoted href attribute. In a regular pentesting scenario, this generally means we have a XSS attack vector, as we could insert a payload like the following to inject malicious script:
However, we got different results when trying this payload:
It appears the module verified the format of our email address both client-side and server-side. So we dove deeper into how things were being handled on the server. We found that the script would validate our email address using the WordPress is_email() function before saving it in the database. We knew from past investigations that this function allowed characters that can be dangerous in certain contexts, notably in the local part of the address (before the ‘@’ symbol):
That provides us with a good list of characters to use, but we still had another challenge: ‘(‘, ‘)’, ‘:’,’;’ and the space character are blocked by this regular expression, and they’re somewhat needed in order to craft a useful malicious payload. The first issue came when trying to separate our closing href from the onmouseover event we’re trying to inject, something we easily managed to do by inserting slashes (‘/’) between the attributes, which acts like a separator in most browsers. So the beginning of our payload would look like:
The last issue we had to get rid of was the fact we couldn’t insert any parenthesis and semicolons. The first idea we had was to insert HTML entities inside the onmouseover attribute, but that led to another issue: HTML entities ends with semicolons.
We had to have a look at Mozilla’s HTML5 Tokenizer to find something that would help us overcome this hurdle, using decimal HTML entities (A for example).
Simplified, the above snippet does the following:
- Knowing that we look at a decimal HTML entitiy, check if the current byte we’re analyzing has a value between ‘0′ and ‘9’ (essentially, a decimal number) and if it is, add its value to the ‘value‘ variable.
- Else, if we’re staring at a semicolon, do one of the following:
- If we stored decimals before, go to NS_HTML5TOKENIZER_HANDLE_NCR_VALUE, where we will convert the ‘value’ variable to its corresponding character.
- Else, if this is not an entity, we’re just ignoring it.
- Otherwise, if we didn’t meet a semicolon and we didn’t parse a decimal number, do one of the following
- If we didn’t store decimals in the ‘value’ variable before, do the same thing as step 2.2 and ignore this corrupted entity.
- If we did, then do as in step 2.1, go to NS_HTML5TOKENIZER_HANDLE_NCR_VALUE and throw a debugging error message notifying the developer he messed up.
This means that if instead of writing a semicolon, for example, we wrote it’s decimal entity counterpart ; and switched the ending semicolon with something else like ;/**/, it would work too, and we would successfully circumvent the constraints is_email() gives us, as we use only allowed characters.
From this point, the attacker has a full blown stored XSS attack vector to compromise the security of this site’s users even further, something you definitely don’t want to see happen.
Update as Soon as Possible
There are a couple of things to bear in mind when thinking about this vulnerability. Cross-Site Scripting (XSS) vulnerabilities come in various flavors, some more severe than others. In most instances, the vulnerabilities you hear attributed to XSS are what you’d call reflective XSS vulnerabilities. This is where the user can cause something to function on the browser via a carefully crafted URL string, but it’s happening on the pre-verbal surface of the site.
In this case however, we’re talking a Stored XSS vulnerability; meaning a visitor to your website is able to pass arbitrary code to your web server and wait for someone to log into your WordPress panel. This is fundamentally different. The users logging in could be subscribers, authors or even administrators. The attackers simply has to wait for the right user to log in, without introducing any additional indicators that might alert the administrator (think a phishing campaign). This is compounded by the popularity of a plugin like JetPack, deployed by default in many installations via their hosts or the install packages.
This also presents readers with an example that illustrates how with enough time and energy, bugs can be identified and exploited for malicious intent. In the attackers mind, the goal is to own one, own them all. Attacking extensions to CMS applications like Jetpack (which has an install base of over 1 million active installs) is a gold mine for any attacker. It’s important you take the time to update. If for whatever reason you can’t, we encourage you to to leverage a Security Stack that provides you proactive defenses against not only known issues, but unknown as well. We encourage you to look at protection platforms like a Website Firewall or equivalent technology that provides some form of virtual patching and hardening to help stay of these emerging threats.