General: Better validation for iframe event origins (#6856)

This commit is contained in:
zubiden 2026-04-17 13:38:10 +02:00 committed by Alexander Zinchuk
parent 889d07823d
commit 7e789e6e77
4 changed files with 23 additions and 9 deletions

View File

@ -1,12 +1,13 @@
import type { FC } from '../../lib/teact/teact';
import type React from '../../lib/teact/teact';
import { memo, useCallback, useEffect } from '../../lib/teact/teact';
import { memo, useCallback, useEffect, useRef } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import type { TabState } from '../../global/types';
import { getCanPostInChat } from '../../global/helpers';
import { selectChat, selectChatFullInfo } from '../../global/selectors';
import { isMessageFromIframe } from '../../util/browser/iframe';
import useInterval from '../../hooks/schedulers/useInterval';
import useOldLang from '../../hooks/useOldLang';
@ -34,6 +35,7 @@ const GameModal: FC<OwnProps & StateProps> = ({ openedGame, gameTitle, canPost }
const lang = useOldLang();
const { url, chatId, messageId } = openedGame || {};
const isOpen = Boolean(url);
const frameRef = useRef<HTMLIFrameElement>();
const sendMessageAction = useSendMessageAction(chatId);
useInterval(() => {
@ -41,7 +43,10 @@ const GameModal: FC<OwnProps & StateProps> = ({ openedGame, gameTitle, canPost }
}, isOpen && canPost ? PLAY_GAME_ACTION_INTERVAL : undefined);
const handleMessage = useCallback((event: MessageEvent<string>) => {
if (!chatId || !messageId) return;
if (!chatId || !messageId || !isMessageFromIframe(event, frameRef.current)) {
return;
}
try {
const data = JSON.parse(event.data) as GameEvents;
if (data.eventType === 'share_score') {
@ -77,6 +82,7 @@ const GameModal: FC<OwnProps & StateProps> = ({ openedGame, gameTitle, canPost }
>
{isOpen && (
<iframe
ref={frameRef}
className="game-frame"
onLoad={handleLoad}
src={url}

View File

@ -6,6 +6,7 @@ import type { WebApp, WebAppInboundEvent, WebAppOutboundEvent } from '../../../.
import { VERIFY_AGE_MIN_DEFAULT } from '../../../../config';
import { getWebAppKey } from '../../../../global/helpers';
import { isMessageFromIframe } from '../../../../util/browser/iframe';
import { extractCurrentThemeParams } from '../../../../util/themeStyle';
import { REM } from '../../../common/helpers/mediaDimensions';
@ -172,10 +173,8 @@ const useWebAppFrame = (
if (ignoreEventsRef.current) {
return;
}
const contentWindow = ref.current?.contentWindow;
const sourceWindow = event.source as Window;
if (contentWindow !== sourceWindow) {
if (!isMessageFromIframe(event, ref.current)) {
return;
}

View File

@ -1,8 +1,8 @@
import type { FC } from '../../lib/teact/teact';
import { memo, useCallback, useEffect } from '../../lib/teact/teact';
import { memo, useCallback, useEffect, useRef } from '../../lib/teact/teact';
import { getActions } from '../../global';
import { TME_LINK_PREFIX } from '../../config';
import { isMessageFromIframe } from '../../util/browser/iframe';
import useOldLang from '../../hooks/useOldLang';
@ -35,14 +35,19 @@ interface WebAppOpenTgLinkEvent {
type IframeCallbackEvent = PaymentFormSubmitEvent | WebAppOpenTgLinkEvent;
const ConfirmPayment: FC<OwnProps> = ({
const ConfirmPayment = ({
url, noRedirect, onClose, onPaymentFormSubmit,
}) => {
}: OwnProps) => {
const { openTelegramLink } = getActions();
const lang = useOldLang();
const frameRef = useRef<HTMLIFrameElement>();
const handleMessage = useCallback((event: MessageEvent<string>) => {
if (!isMessageFromIframe(event, frameRef.current)) {
return;
}
try {
const data = JSON.parse(event.data) as IframeCallbackEvent;
const { eventType, eventData } = data;
@ -76,6 +81,7 @@ const ConfirmPayment: FC<OwnProps> = ({
return (
<div className="ConfirmPayment">
<iframe
ref={frameRef}
src={url}
title={lang('Checkout.WebConfirmation.Title')}
allow="payment"

View File

@ -0,0 +1,3 @@
export function isMessageFromIframe(event: MessageEvent, iframe?: HTMLIFrameElement) {
return Boolean(iframe?.contentWindow && event.source === iframe.contentWindow);
}