You've already forked Nitro_Render_V3
mirror of
https://github.com/duckietm/Nitro_Render_V3.git
synced 2026-06-19 15:06:20 +00:00
4f2c2f904c
* Fix PixiJS v8 deprecation warning (PixiJS v8 only allows Container objects to have children) * Changed from whitelist to blocklist approach in LegacyExternalInterface: - Allows legitimate callbacks like 'openroom', 'opennavigator', etc. - Blocks only dangerous globals (eval, Function, window, document, etc.) - Blocks prototype pollution vectors (__proto__, constructor, etc.) - Blocks network/storage APIs from being overwritten.
307 lines
9.7 KiB
TypeScript
307 lines
9.7 KiB
TypeScript
declare global
|
|
{
|
|
interface Window
|
|
{
|
|
FlashExternalInterface?:
|
|
{
|
|
legacyTrack?: (
|
|
category: string,
|
|
action: string,
|
|
data: unknown[]
|
|
) => void;
|
|
logDebug?: (...params: string[]) => void;
|
|
disconnect?: (reasonCode: number, reasonString: string) => void;
|
|
logout?: () => void;
|
|
openWebPageAndMinimizeClient?: (pageUrl: string) => void;
|
|
heartBeat?: () => void;
|
|
logEventLog?: (log: string) => void;
|
|
openPage?: (pageUrl: string) => void;
|
|
closeWebPageAndRestoreClient?: () => void;
|
|
openHabblet?: (name: string, param: string) => void;
|
|
closeHabblet?: (name: string, param: string) => void;
|
|
openExternalLink?: (link: string) => void;
|
|
roomVisited?: (roomId: number) => void;
|
|
openMinimail?: (target: string) => void;
|
|
openNews?: () => void;
|
|
closeNews?: () => void;
|
|
openAvatars?: () => void;
|
|
openRoomEnterAd?: () => void;
|
|
updateFigure?: (figure: string) => void;
|
|
};
|
|
|
|
FlashExternalGameInterface?:
|
|
{
|
|
showGame?: (url: string) => void;
|
|
hideGame?: () => void;
|
|
};
|
|
}
|
|
}
|
|
|
|
export class LegacyExternalInterface
|
|
{
|
|
private static readonly MESSAGE_KEY = 'Nitro_LegacyExternalInterface';
|
|
private static readonly GAME_MESSAGE_KEY = 'Nitro_LegacyExternalGameInterface';
|
|
private static _isListeningForPostMessages = false;
|
|
private static _messageListener: (ev: MessageEvent) => void = null;
|
|
|
|
// Whitelist of allowed methods that can be called via postMessage
|
|
// This prevents arbitrary code execution from malicious postMessage calls
|
|
private static readonly ALLOWED_EXTERNAL_METHODS: ReadonlySet<string> = new Set([
|
|
'cycleWallpaper',
|
|
'cycleFloor',
|
|
'cycleLandscape',
|
|
'cycleBackgroundColor',
|
|
'cycleAvatarLightLevel',
|
|
'cycleAvatarName',
|
|
'cycleAvatarTyping',
|
|
'cycleAvatarEffect',
|
|
'cycleAvatarIdle',
|
|
'cycleAvatarDance',
|
|
'cycleAvatarExpression',
|
|
'cycleAvatarPosture',
|
|
'cycleAvatarSign',
|
|
'cycleAvatarSleep',
|
|
'cycleAvatarTalk',
|
|
'cycleAvatarWave',
|
|
'cycleZoom',
|
|
'cycleRoomBackgroundColor'
|
|
]);
|
|
|
|
// Blocklist of dangerous global function/property names that cannot be overwritten
|
|
// This prevents security vulnerabilities while allowing legitimate callbacks
|
|
private static readonly BLOCKED_CALLBACK_NAMES: ReadonlySet<string> = new Set([
|
|
// JavaScript execution functions
|
|
'eval',
|
|
'Function',
|
|
'constructor',
|
|
// Prototype pollution vectors
|
|
'prototype',
|
|
'__proto__',
|
|
'__defineGetter__',
|
|
'__defineSetter__',
|
|
'__lookupGetter__',
|
|
'__lookupSetter__',
|
|
// Global objects
|
|
'window',
|
|
'document',
|
|
'globalThis',
|
|
'self',
|
|
'top',
|
|
'parent',
|
|
'frames',
|
|
// Module/import system
|
|
'require',
|
|
'import',
|
|
'module',
|
|
'exports',
|
|
// Network/storage APIs
|
|
'fetch',
|
|
'XMLHttpRequest',
|
|
'WebSocket',
|
|
'Worker',
|
|
'SharedWorker',
|
|
'ServiceWorker',
|
|
'localStorage',
|
|
'sessionStorage',
|
|
'indexedDB',
|
|
'caches',
|
|
// Object prototype methods
|
|
'toString',
|
|
'valueOf',
|
|
'hasOwnProperty',
|
|
'isPrototypeOf',
|
|
'propertyIsEnumerable',
|
|
'toLocaleString',
|
|
// Potentially dangerous DOM methods
|
|
'postMessage',
|
|
'addEventListener',
|
|
'removeEventListener',
|
|
'dispatchEvent',
|
|
'setTimeout',
|
|
'setInterval',
|
|
'setImmediate',
|
|
'requestAnimationFrame',
|
|
'queueMicrotask'
|
|
]);
|
|
|
|
// Allowed origins for postMessage - empty means same-origin only
|
|
// Add trusted origins here if cross-origin communication is needed
|
|
private static _allowedOrigins: Set<string> = new Set();
|
|
|
|
public static setAllowedOrigins(origins: string[]): void
|
|
{
|
|
this._allowedOrigins = new Set(origins);
|
|
}
|
|
|
|
public static get available(): boolean
|
|
{
|
|
if(!this._isListeningForPostMessages)
|
|
{
|
|
this._isListeningForPostMessages = true;
|
|
|
|
this._messageListener = (ev: MessageEvent) =>
|
|
{
|
|
// Validate origin - only accept from same origin or explicitly allowed origins
|
|
if(ev.origin !== window.location.origin && !this._allowedOrigins.has(ev.origin))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(typeof ev.data !== 'string') return;
|
|
|
|
if(ev.data.startsWith(LegacyExternalInterface.MESSAGE_KEY))
|
|
{
|
|
try
|
|
{
|
|
const { method, params } = JSON.parse(
|
|
ev.data.substring(LegacyExternalInterface.MESSAGE_KEY.length)
|
|
);
|
|
|
|
// Validate method is in whitelist before executing
|
|
if(!this.ALLOWED_EXTERNAL_METHODS.has(method))
|
|
{
|
|
console.warn(`[LegacyExternalInterface] Blocked unauthorized method call: ${method}`);
|
|
return;
|
|
}
|
|
|
|
// Validate params is an array
|
|
if(!Array.isArray(params))
|
|
{
|
|
console.warn(`[LegacyExternalInterface] Invalid params for method: ${method}`);
|
|
return;
|
|
}
|
|
|
|
const fn = (window as any)[method];
|
|
if(typeof fn !== 'function') return;
|
|
|
|
fn(...params);
|
|
}
|
|
catch(e)
|
|
{
|
|
console.error('[LegacyExternalInterface] Error processing message:', e);
|
|
}
|
|
return;
|
|
}
|
|
};
|
|
|
|
window.addEventListener('message', this._messageListener);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static dispose(): void
|
|
{
|
|
if(this._messageListener)
|
|
{
|
|
window.removeEventListener('message', this._messageListener);
|
|
this._messageListener = null;
|
|
this._isListeningForPostMessages = false;
|
|
}
|
|
}
|
|
|
|
public static call<K extends keyof typeof window.FlashExternalInterface>(
|
|
method: K,
|
|
...params: Parameters<typeof window.FlashExternalInterface[K]>
|
|
): ReturnType<typeof window.FlashExternalInterface[K]> | undefined
|
|
{
|
|
if(window.top !== window)
|
|
{
|
|
// Use parent origin if known, otherwise use '*' with caution
|
|
// Note: Using '*' is necessary when the parent origin is unknown
|
|
// The receiving end should validate the message content
|
|
const targetOrigin = window.location.ancestorOrigins?.length > 0
|
|
? window.location.ancestorOrigins[0]
|
|
: '*';
|
|
|
|
window.top.postMessage(LegacyExternalInterface.MESSAGE_KEY + JSON.stringify({
|
|
method,
|
|
params
|
|
}), targetOrigin);
|
|
}
|
|
|
|
if(!('FlashExternalInterface' in window)) return undefined;
|
|
|
|
const fn = window.FlashExternalInterface[method] as Function;
|
|
|
|
return typeof fn !== 'undefined' ? fn(...params) : undefined;
|
|
}
|
|
|
|
public static callGame<K extends keyof typeof window.FlashExternalGameInterface>(
|
|
method: K,
|
|
...params: Parameters<typeof window.FlashExternalGameInterface[K]>
|
|
): ReturnType<typeof window.FlashExternalGameInterface[K]> | undefined
|
|
{
|
|
if(window.top !== window)
|
|
{
|
|
// Use parent origin if known, otherwise use '*' with caution
|
|
const targetOrigin = window.location.ancestorOrigins?.length > 0
|
|
? window.location.ancestorOrigins[0]
|
|
: '*';
|
|
|
|
window.top.postMessage(LegacyExternalInterface.GAME_MESSAGE_KEY + JSON.stringify({
|
|
method,
|
|
params
|
|
}), targetOrigin);
|
|
}
|
|
|
|
if(!('FlashExternalGameInterface' in window)) return undefined;
|
|
|
|
const fn = window.FlashExternalGameInterface[method] as Function;
|
|
|
|
return typeof fn !== 'undefined' ? fn(...params) : undefined;
|
|
}
|
|
|
|
public static addCallback(name: string, func: Function): boolean
|
|
{
|
|
// Validate callback name is not empty
|
|
if(!name || typeof name !== 'string' || name.trim().length === 0)
|
|
{
|
|
console.warn('[LegacyExternalInterface] Invalid callback name');
|
|
return false;
|
|
}
|
|
|
|
// Check against blocklist of dangerous global function names
|
|
// This prevents overwriting critical globals like 'eval', 'Function', etc.
|
|
if(this.BLOCKED_CALLBACK_NAMES.has(name))
|
|
{
|
|
console.warn(`[LegacyExternalInterface] Blocked registration of dangerous callback: ${name}`);
|
|
return false;
|
|
}
|
|
|
|
// Additional safety: prevent overwriting existing non-function properties
|
|
const existing = (window as any)[name];
|
|
if(existing !== undefined && typeof existing !== 'function')
|
|
{
|
|
console.warn(`[LegacyExternalInterface] Cannot overwrite non-function property: ${name}`);
|
|
return false;
|
|
}
|
|
|
|
(window as any)[name] = func;
|
|
return true;
|
|
}
|
|
|
|
public static removeCallback(name: string): boolean
|
|
{
|
|
// Validate callback name
|
|
if(!name || typeof name !== 'string' || name.trim().length === 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Don't allow removal of blocked/dangerous globals
|
|
if(this.BLOCKED_CALLBACK_NAMES.has(name))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if((window as any)[name] !== undefined)
|
|
{
|
|
delete (window as any)[name];
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|