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

View File

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

View File

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

View File

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