During the recent Google CTF competition I solved a curious web security challenge called safehtmlpaste that required to bypass a certain HTML sanitizer.
The vulnerable application allows users to create Pastes with HTML-formatted text in them. We can try to add a script and see what happens:
When we open the created paste, we can see that as expected, the malicious script has been removed:
The reason for this is the call to safepaste.sanitize in the source code of the view page:
As it became evident later, the application uses the sanitizer from the Google Closure library.
From looking at the code above, it is obvious that we could defeat the sanitizing just by triggering any exception in the sanitize function. The exception will be caught, and the value of html variable will remain intact.
Another possible solution might involve mutation-based XSS (mXSS) technique which is often used to defeat HTML sanitizers. The mutations are caused by the direct write to innerHTML property.
I’ve decided to look through all the exceptions thrown in the library to trace the one we can trigger. After some poking around, I’ve found an interesting function module$contents$goog$html$sanitizer$noclobber_assertHTMLElement. It is called from numerous locations including the module$contents$goog$html$sanitizer$noclobber_getElementStyle function which processes the style attribute of any element
By setting a breakpoint and executing
safepaste.sanitize("<a style=b>") we can confirm that the function is accessible:
As we can see, the exception will be thrown if the input variable (with the style attribute) is not an HTMLElement object. I consulted the MDN web docs to check which DOM elements could technically have HTML attributes and are not inherited from the HTMLElement interface.
One of the obvious choices might be SVGElement but we can’t use it thanks to the deny list:
However, another similar element is <math>: the MathMLElement interface is not inherited from HTMLElement.
Note that the scripts contained in HTML directly written to the innerHTML property won’t be executed, so we need to put our payload into the event handler.
The final PoC is
<math style=a></math><img src=x onerror=alert(1337)>:
P.S. After the CTF ended, it turned out that this issue was already mentioned in the blogpost about copy&paste vulnerabilities in WYSIWIG editors. Not sure why the CTF organizers decided to put a known weakness in the CTF task.
Such bugs are of particular interest to me because they exploit insecure exception handling to change the execution flow of the application by effectively causing Denial of Service of a number of components. There’re some other interesting examples of similar attacks, such as DoS-based WAF bypass.
To verify that your application is not prone to subtle business logic errors as well as classical web application vulnerabilities, please reach out to us for a web application penetration testing inquiry.