Cross-Site Content and Status Types Leakage

When doing my usual Bug Bounty research routine, I found an interesting behavior that occurred on a popular website, let’s say censored.com. Depending on whether the user was authorized to display the website two completely different pages were being shown. One, with content-type:text/html;charset=utf-8 HTTP header, and the second, without Content-Type header at all, which in that case becomes text/plain by default. So I’ve asked myself: Is there a clever way to differentiate between these two responses? If so, could this be generalized to all websites? What threats does it pose?

Content-Type: text/html

Let’s start with the threats that differentiating between responses mentioned earlier poses. It may not be obvious but it’s a serious information leak that could be performed across different websites. In this specific example, the malicious third party could obtain information on whether the visitor had been authorized to display a given set of resources on another website, e.g. Facebook.com, and hence, based on the results — expose their identity.

In some cases, the impact could be more severe than user deanonymization, so giving the idea a shot seemed worth the effort. At the time, I had a hunch that it was possible to achieve, so I began researching the MDN Web Documentation in the hope of finding any interesting attributes that could help. It didn’t take too long because I quickly discovered the worthy one.

The HTMLObjectElement.typeMustMatch property is a Boolean that reflects the typemustmatch attribute of the <object> element. It indicates if the resource linked by it must match the MIME type given by HTMLObjectElement.type in order for this resource to be used. [1]

Armed with this property, I began experimenting with it in Chrome. However, after a few tries, I was nowhere close in making it work. It seemed that the attribute was getting completely ignored by the browser. Indeed, I overlooked the browser compatibility section. Luckily, Eduardo enlightened me that it should work in Firefox. Indeed it did, like a charm.

Going into the details, if the content-type header doesn’t match the type attribute of the HTML <object> the resource will not be loaded. Unfortunately, the <object> element does not trigger the onload nor the onerror event, so detecting whether the object has loaded is yet another nut to crack.

Eduardo came up with an interesting idea of detecting that state by using an inline element inside the <object>.

<object type= data= typemustmatch> not_loaded </object>

Basically, the idea was that if the not_loaded text was rendered, either the type attribute didn’t match the content-type header or the server responded with the status different than HTTP 200 OK. It doesn’t look like something very easy to pull off, so I kept digging around.

After a closer look at the <object> attributes, I noticed that <object>.clientHeight and <object>.clientWidth varies in some scenarios. These attributes are nothing less than the width and the height of the rendered object. If the object was not yet rendered its dimensions are 0, otherwise, likely greater than that. Like in the previous paragraph, same here, the object will only be rendered if the server responded with HTTP 200 OK status. I am unsure whether it’s an obstruction to the attack or a bridge to another, very efficient, technique of error pages detection? [2]

The last missing piece to the attack is detecting when the <object> has loaded. Because, how do we know when we should read its height property if we don’t know when the <object> has loaded in the first place? A naive solution would be the use of timings after which the object.clientHeight is being read. But no one likes timings, these are pretty unstable and sensitive to connection drops.

Well, maybe the <object> itself doesn’t trigger the onload event, but the window object surely does. That event in the window will only be triggered when all components, including iframes, are loaded. Hence, by creating a new isolated document (e.g. iframe) it’s easy to listen to its onload event and then read the height of the <object> placed in there.

As for the result of combining the simulated <object>.onload event with detection of the <object> rendering, we’ve got a complete brand new technique for Cross-Origin Content and Status Types detection. And with that, it’s safe to say that the method proudly joins the XS-Leaks family.

I embedded a fancy Proof of Concept that successfully leaks the content-type of the resource. Enjoy the ride!