← All issues

[9] UserMessageHandler.postMessage should fail if called from another frame

Severity: Medium | Component: WebCore page/UserMessageHandler | 795ef8a

Observable effect가 캡처된 cross-origin reference를 통해 host-app WKScriptMessageHandler에 origin을 속인 메시지를 전달하는 것이므로 Medium으로 평가합니다. 영향 범위는 host app이 frameInfo/origin에 부여하는 신뢰 수준에 달려 있으며, memory primitive는 존재하지 않습니다.

Source/WebCore/page/UserMessageHandler.cpp

+static bool passesSameOriginCheck(JSC::JSGlobalObject& globalObject, RefPtr<LocalFrame> frame)
+{
+ if (!frame) return false;
+ RefPtr document = frame->document();
+ if (!document) return false;
+ Ref frameSecurityOrigin = document->securityOrigin();
+ if (!globalObject.inherits<JSDOMGlobalObject>()) return false;
+ RefPtr scriptExecutionContext = uncheckedDowncast<JSDOMGlobalObject>(globalObject).scriptExecutionContext();
+ if (!scriptExecutionContext) return false;
+ RefPtr securityOrigin = scriptExecutionContext->securityOrigin();
+ if (!securityOrigin) return false;
+ return securityOrigin->isSameOriginAs(frameSecurityOrigin);
+}
+
void UserMessageHandler::postMessage(JSC::JSGlobalObject& globalObject, JSC::JSValue value, Ref<DeferredPromise>&& promise)
{
RefPtr descriptor = m_descriptor;
- if (!descriptor) {
- promise->reject(Exception { ExceptionCode::InvalidAccessError });
- return Exception { ExceptionCode::InvalidAccessError };
- }
+ if (!descriptor)
+ return promise->reject(Exception { ExceptionCode::InvalidAccessError });
+ if (!passesSameOriginCheck(globalObject, m_frame.get()))
+ return promise->reject(Exception { ExceptionCode::InvalidAccessError, "Failed same-origin check."_s });
descriptor->didPostMessage(*this, globalObject, value, ...);
}

새로운 passesSameOriginCheck helper는 호출하는 JSGlobalObjectscriptExecutionContext security origin을, handler가 속한 LocalFrame의 현재 document origin과 비교합니다. postMessagepostLegacySynchronousMessage 모두 descriptor로 전달하기 전에 이 helper를 먼저 호출합니다. 반환 타입도 변경되었습니다. postMessage는 이제 void를 반환하며 promise를 통해 reject하고, postLegacySynchronousMessageExceptionOr<JSC::JSValue>를 반환합니다. 새로 추가된 테스트는 cross-origin iframe에서 iframe1.contentWindow.window.webkit.messageHandlers.testhandler1을 가져와, 해당 post가 실패하는지 검증합니다.

Capability 획득 시점에만 same-origin 조건을 검사하고 사용 시점에는 재검증하지 않아, frame origin이 변경된 이후에도 캡처된 handler reference를 계속 사용할 수 있는 패턴.

WKUserContentController를 통해 host app은 이름을 붙인 JS 호출 가능한 bridge를 등록할 수 있습니다. 스크립트에서 window.webkit.messageHandlers.<name>.postMessage(value)를 호출하면 value가 app의 WKScriptMessageHandler로 전달됩니다. 각 이름에 대응하는 C++ 객체인 UserMessageHandler는 frame마다 생성되며, FrameDestructionObserver를 통해 해당 LocalFrame에 바인딩됩니다. 두 frame이 same-origin인 경우, 한 쪽의 스크립트에서 상대방의 global JS 객체에 직접 접근할 수 있습니다. 다만 frame을 다른 페이지로 navigation하면 Document와 origin은 교체되지만, 다른 스크립트가 이미 캡처한 JS 객체 참조는 자동으로 무효화되지 않습니다.

기존 postMessagem_descriptor가 연결되어 있는지만 확인한 뒤, descriptor->didPostMessage(*this, globalObject, ...)로 직접 전달했습니다. handler는 생성 시점에 특정 frame에 바인딩되며, host app은 메시지 수신 시 handler의 frame을 기준으로 메시지를 식별합니다. 그러나 postMessage를 호출하는 JSGlobalObject의 security origin이 handler가 바인딩된 frame의 origin과 동일한지 검증하는 로직은 호출 경로 어디에도 없었습니다.

🔒

Where the same-origin policy is enforced for this capability — and what changes for an attacker who captured a reference at the right moment — is examined in detail.

더 확인하려면 구독해 주세요

🔒

Several reusable audit patterns identified for capability-style JS bridges across WebCore, with concrete starting points for variant discovery.

더 확인하려면 구독해 주세요