During a security assessment of a web app for which I was commissioned to carry out a black- box penetration test, I discovered a non-persistent cross-site scripting that allowed me to obtain the CVE-2023-1119. But let’s take a few steps back and go back to when it all began.
Initial assessment
On January 26th, I started the audit by gathering information about the web app, including the implemented technologies, its purpose, and the features available to users. From the initial analysis of the index page source, I noticed some WordPress folders, including “wp-includes” and “wp-uploads”. This led me to realize that the web app was likely built using WordPress.
To confirm this hypothesis, I used Wpscan and obtained confirmation that the web app was powered by WordPress, specifically the latest version (6.1.1).
Wpscan also provided me with the list of identified plugins and their version. While some of these were outdated versions, none of them had critical vulnerabilities. The only security issue reported by the tool was present in the updated version of WordPress, for which a patch had not yet been released. This vulnerability was categorized as “Unauthenticated Blind SSRF via DNS Rebinding” (CVE-2022-3590) and allowed the enumeration of internal services present on the same server or network segment as the web app through the “pingback” function (more information available at the following link).
Few failed attempts
Despite the exploit being publicly available, we were unable to leverage the vulnerability and obtain the Proof of Concept (PoC). This was likely due to the documented difficulty of exploitation, which is high and only feasible under certain circumstances. None of the plugins listed by Wpscan appeared to have security features specifically implemented to protect the web app and its users from common attacks. However, during the security testing of some application features, we detected the presence of the Wordfence Security Plugin, which effectively blocked all our XSS attempts.
Wordfence Security is one of the most popular and widely used security plugins in the WordPress community (estimated active installs: 4+ million). It includes a Web Application Firewall (WAF) that monitors incoming traffic to detect and block potential attacks by comparing received data against a database of known attack patterns and employing behavioral analysis to identify anomalous and suspicious activities. This presented us with the ambitious challenge of attempting to bypass Wordfence by crafting a payload that wouldn’t match any signatures in its database. To understand how to construct our payload, it was important to first identify the HTML context and the location where the desired string is reflected.
In our case, if we wanted to inject our payload into an <input>
tag, we would need to use double quotes to close the “value” attribute and insert an HTML event that would execute our malicious JavaScript code. The next step was to probe the search function using Burp Intruder to determine which HTML events were allowed. We sent a request with an attack attempt to Burp Intruder, selected ‘Sniper’ as the attack type, positioned the payload by adding the §
character at the start and end of the HTML event, and then inserted a list of HTML events into the ‘Payloads’ table before starting the attack.
From the results obtained, we observed that Wordfence blocked all requests with an HTML event followed by the =
character. Before resorting to the use of obfuscated and anti-WAF payloads, we decided to try injecting some unusual payloads that didn’t include the =
character prepended to an HTML event.
However, as proposed, the attempt turns out to be inconclusive. Now that we’ve gathered some background information on how to craft our payload, let’s create an obfuscated exploits list that mirrors the affected HTML context. To do this, I consulted some lists of anti-WAF XSS payloads online and modified them based on the collected information. Finally, I configured Burp Intruder to automatically test the created payload list. However, at the end of the attack, none of the attempts made by the Intruder were successful.
Finally XSS!
But let’s not lose heart; we certainly didn’t expect it to be a walk in the park! Let’s organize the information gathered, refine our techniques, and prepare for a new attempt. So, I decided to manually test some payloads by encoding the special characters that trigger Wordfence. To do this, I used the Burp “Hackvertor” extension, which allowed me to convert the special characters using various encodings available in the tool. As a result, Wordfence was able to detect and block any attack attempt by decoding our payloads. However, against all odds, we finally achieved success by using HTML encoding and obtaining our coveted XSS-reflected payload.
Woah! We succeeded!
A further analysis to understand what triggered the XSS
Initially, I thought that Wordfence might not be updated to the latest version or might not have the “Real-time firewall” feature, which includes updating signatures through the “Threat Defense Feed.”
However, the following day, while speaking with one of the web app developers, he assured me that Wordfence was indeed updated to the latest version, along with WordPress. So things started to get interesting, but the surprises didn’t end there.
The story became even more intriguing when comparing notes with my colleague Luca Famà. He pointed out that the payload injected into the search function was also reflected within the HTML context of the WordPress “Calendar Events” plugin. This led us to suspect an XSS mutation caused by a function integrated into that plugin, which failed to properly sanitize user input.
To investigate this hypothesis, we examined the source code but found no functions that operated on the site’s Document Object Model (DOM) or improperly sanitized user input. To eliminate doubts and identify the cause of the vulnerability, we decided to perform injections by disabling one plugin at a time, with the assistance of the development team member Fabio Passarella. Eventually, we unexpectedly identified the plugin responsible for the vulnerability: WP-Optimize (Active installations: 1+ million). This plugin is designed to optimize and clean the WordPress database and, seemingly unrelatedly, makes the site vulnerable to XSS-reflected attacks. But the twists didn’t end there.
In our final analysis we found that it was precisely an optional feature of WP-Optimize that triggered the vulnerability, namely “WebP Conversion”, which is used for image optimization. It seemed perplexing to find a correlation between an image optimization function and XSS-reflected attacks. We were equally surprised when analyzing the source code and uncovering the hidden truth.
The function alter_html
is defined in webp/class-wpo-webp-alter-html.php
and looks like this:
This function loads the external library simple_html_dom
, which through the str_get_html
function (line 52) takes the HTML document as an input string and returns $dom->save
.
When the “WebP Conversion” option is enabled, the str_get_html
function parses any HTML page requested by the user (that’s because it needs to look for some specific tags in order to convert images in WebP format) and returns the original document. It turned out that this process has the side effect of converting all the HTML entities to their corresponding reserved HTML characters.
This is clearly an issue: if an attacker injects a malicious input encoded using HTML entities, the str_get_html
function will convert back the HTML entities to actual HTML tags, which will be rendered/executed by the browser.
This is really dangerous, considering that allows an attacker to easily bypass the Wordfence filtering (because the Wordfence filtering happens before the str_get_html
is executed and Wordfence doesn’t consider HTML entities dangerous).
In order to fix the issue the maintaners decided to add an additional argument to the str_get_html
function that control whether or not enable the HTML entities conversion (link here).
The following image shows how WP-Optimize fixed the bug, by updating the simple_html_dom
library and setting to false
the HTML entities conversion process (the last argument).
Conclusion
This activity gave me the opportunity to find an interesting vulnerability in WP-Optimize, a well-known and popular WordPress plugin with more than 1 million active installations. This allowed me to get a CVE assigned (CVE-2023-1119) and an official advisory has been published by WPScan, the famous platform which includes an extended database of WordPress (and plugins) vulnerabilities.