[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, ...);
}
Patch Details
새로운 passesSameOriginCheck helper는 호출하는 JSGlobalObject의 scriptExecutionContext security origin을, handler가 속한 LocalFrame의 현재 document origin과 비교합니다. postMessage와 postLegacySynchronousMessage 모두 descriptor로 전달하기 전에 이 helper를 먼저 호출합니다. 반환 타입도 변경되었습니다. postMessage는 이제 void를 반환하며 promise를 통해 reject하고, postLegacySynchronousMessage는 ExceptionOr<JSC::JSValue>를 반환합니다. 새로 추가된 테스트는 cross-origin iframe에서 iframe1.contentWindow.window.webkit.messageHandlers.testhandler1을 가져와, 해당 post가 실패하는지 검증합니다.
Capability 획득 시점에만 same-origin 조건을 검사하고 사용 시점에는 재검증하지 않아, frame origin이 변경된 이후에도 캡처된 handler reference를 계속 사용할 수 있는 패턴.
Background
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 객체 참조는 자동으로 무효화되지 않습니다.
Analysis
기존 postMessage는 m_descriptor가 연결되어 있는지만 확인한 뒤, descriptor->didPostMessage(*this, globalObject, ...)로 직접 전달했습니다. handler는 생성 시점에 특정 frame에 바인딩되며, host app은 메시지 수신 시 handler의 frame을 기준으로 메시지를 식별합니다. 그러나 postMessage를 호출하는 JSGlobalObject의 security origin이 handler가 바인딩된 frame의 origin과 동일한지 검증하는 로직은 호출 경로 어디에도 없었습니다.
Aa Aaa Aaa Aaaaa Aaaaaa Aa Aa Aaaa Aaaaaaa Aaaaaaaaaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaaaaaaaa Aaaaaa Aa Aaaaaaa Aaaaaa Aa Aaaaaaaaaaaaa Aaaaa Aaa Aaaaaaaaaa Aa Aaaa Aaa Aaaaa Aa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaa Aaa Aaaaaaaaaaaaaaaaaaaaa Aaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aa Aaa Aaaaaaa Aaaaa Aaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaa Aaaaa Aaa Aaa Aaa Aaa Aaaa Aaaa Aaaa
a Aaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaa Aaa Aaaaaaa Aaaa Aaaa Aaaaaaaaaaaaaaaaaaa Aaaa Aa Aaa Aaaa Aaa Aaaaa Aa Aa Aaaaaaa Aaaa Aaa Aaaaaaaa Aaaaaaaaaa Aaaaaaaa Aaaa Aaa Aaaaaaa Aa Aaaa Aa Aa Aaa Aaaaaa Aaa Aaaa Aaaaaaaa Aaa a Aaaaa Aaaa Aaaa Aa Aaa Aaaa Aaaaaaaaaaaaaaaaaa Aaaaaaa Aaaaaaa Aa Aaa Aaaa Aaaaa Aaaaaaa Aaaa Aaaaaa
🔒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.
더 확인하려면 구독해 주세요
Audit directions
a Aaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaaaaa Aaa Aa a Aaaaaaa Aaaaaa Aaaaaaa Aaaaa Aa Aa Aa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaa Aaa Aaa Aaaaaaaaaaaaaaaaaaaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaa Aa Aaaa Aaaaaaaaa Aaaaaa a Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaa Aaaaaa Aa Aaaaaaa Aaaaa Aaaa Aaaa
a Aaaa Aaaa Aaaa Aaaaaaaaaa Aaa Aaaaaaaaaaaa Aaaaaaaaaaaaaaaaa Aa Aa Aaaaaa Aaaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaa Aaa Aaa Aaaaaaaaa Aa Aaaaaa
a Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaa Aa Aaa Aaa Aaaaaaaaaaa Aaaaaaa Aaaa Aaaa Aaaaa Aa Aaa Aaaa Aaa Aaaaaa Aaa Aaaa Aaaa Aaaaaa
a Aaaaaaaaaaaaaaaaaaaa Aa Aaa Aa Aa Aaaaa Aaaaaaaaaaaa Aa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaa Aa Aaaaaaaa Aaaaaaaaaa Aa Aaaa Aaaaaaaaaaaaa Aaa Aaaa Aaaaaaaaa Aaaaaa
🔒Several reusable audit patterns identified for capability-style JS bridges across WebCore, with concrete starting points for variant discovery.
더 확인하려면 구독해 주세요