07 Jan 2019Iframe Security

Iframe Security

You have no control over sites that provide their functionality via embedded frames. 3rd party providers who offer their functionality for free usually gather user data to be sold to ad providers as a form of payment.

Cross-origin Frames

Modern browsers are non susceptible to cross-origin iframes tampering with embedding website.

This cross-origin frame doesn't have access to top frame which is the current website:

Cross-origin frames may stop working at some point. Source used can be found at the bottom of this page.

Opening this frame with provided button will show that it does have access to opener which is the iframe itself but it still can't access opener.top. Trying to change location of opener.top in newly opened window will produce an error. This fact prevents changing location of original site in the background which could be used in a phishing attack.

Same-origin Frames

For comparison same-origin iframe below (identical to the one above) has access to top. Widow opened with provided button has access to opener.top and can change location of top frame of its opener. Clicking the button in newly opened window will change location of current site to example.com.

Sandboxing

The sandbox attribute allows for extra restrictions. Empty attribute will apply all restrictions.

Same-origin Sandboxing

In this case same-origin sandboxed frame without allow-same-origin always fails SOP checks. This will also disable to opener in window opened in new tab.

Removing allow-popups will disable opening new windows and tabs.

Removing allow-scripts disables all JavaScript but not popups. target="_blank" anchor will work, opened window will inherit script restriction. In this case frame will be mostly empty.

Adding allow-popups-to-escape-sandbox to the last case will allow script execution and opener.top access.

Cross-origin Sandboxing

Omitting allow-same-origin on cross-origin iframe blocks opener in popups.

Cross origin frame source

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<a href="" target="_blank">open this frame in blank target</a>
<ul id="log"></ul>
<script type="text/javascript">

const logEl = document.getElementById('log')
const log = (...messages) => {
const messageEl = document.createElement('li')
messageEl.style.display = 'flex'
messageEl.style.borderTop = '1px solid black'
messageEl.style.padding = '1rem 0'
const spans = messages.map(message => {
const span = document.createElement('span')
span.style.flex = `${100 / messages.length}%`
span.textContent = message
messageEl.append(span)
})
logEl.append(messageEl)
}
logEl.style.padding = 0
logEl.style.margin = 0

const tryToReadWidowProp = str => {
try {
const path = str.split('.')
result = path.reduce((a, c) => a[c], window)
} catch (error) {
result = error.message
} finally {
return result
}
}

const locationHref = tryToReadWidowProp('location.href')
const topLocationHref = tryToReadWidowProp('top.location.href')

log('window location:', locationHref)
log('top location:', topLocationHref)
log('opener location:', tryToReadWidowProp('opener.location.href'))
log('opener top location:', tryToReadWidowProp('opener.top.location.href'))

if (!(locationHref === topLocationHref)) {
const button = document.createElement('button')
button.textContent = 'open this frame in a blank target'
button.addEventListener('click', () => window.open(window.location.href, '_blank'))
document.body.append(button)
}

if (window.opener) {
const button = document.createElement('button')
button.textContent = 'navigate top frame of opener to example.com'
button.addEventListener('click', () => opener.top.location.href = '//example.com')
document.body.append(button)
}

</script>
</body>
</html>