You've already forked Nitro_Render_V3
mirror of
https://github.com/duckietm/Nitro_Render_V3.git
synced 2026-06-20 15:36:18 +00:00
🆙 Updates
* 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.
This commit is contained in:
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
import { IGraphicAsset } from '@nitrots/api';
|
import { IGraphicAsset } from '@nitrots/api';
|
||||||
import { GetRenderer, TextureUtils } from '@nitrots/utils';
|
import { GetRenderer, TextureUtils } from '@nitrots/utils';
|
||||||
import { Matrix, Sprite, Texture, RenderTexture } from 'pixi.js';
|
import { Container, Matrix, Sprite, Texture, RenderTexture } from 'pixi.js';
|
||||||
import { FurnitureAnimatedVisualization } from './FurnitureAnimatedVisualization';
|
import { FurnitureAnimatedVisualization } from './FurnitureAnimatedVisualization';
|
||||||
|
|
||||||
export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualization {
|
export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualization {
|
||||||
@@ -138,7 +138,7 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza
|
|||||||
const width = 64;
|
const width = 64;
|
||||||
const height = 64;
|
const height = 64;
|
||||||
|
|
||||||
const container = new Sprite();
|
const container = new Container();
|
||||||
sprite.position.set((width - sprite.width) / 2, (height - sprite.height) / 2);
|
sprite.position.set((width - sprite.width) / 2, (height - sprite.height) / 2);
|
||||||
container.addChild(sprite);
|
container.addChild(sprite);
|
||||||
|
|
||||||
|
|||||||
@@ -40,37 +40,166 @@ declare global
|
|||||||
export class LegacyExternalInterface
|
export class LegacyExternalInterface
|
||||||
{
|
{
|
||||||
private static readonly MESSAGE_KEY = 'Nitro_LegacyExternalInterface';
|
private static readonly MESSAGE_KEY = 'Nitro_LegacyExternalInterface';
|
||||||
|
private static readonly GAME_MESSAGE_KEY = 'Nitro_LegacyExternalGameInterface';
|
||||||
private static _isListeningForPostMessages = false;
|
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
|
public static get available(): boolean
|
||||||
{
|
{
|
||||||
if(!this._isListeningForPostMessages)
|
if(!this._isListeningForPostMessages)
|
||||||
{
|
{
|
||||||
this._isListeningForPostMessages = true;
|
this._isListeningForPostMessages = true;
|
||||||
window.addEventListener('message', (ev) =>
|
|
||||||
|
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(typeof ev.data !== 'string') return;
|
||||||
|
|
||||||
if(ev.data.startsWith(LegacyExternalInterface.MESSAGE_KEY))
|
if(ev.data.startsWith(LegacyExternalInterface.MESSAGE_KEY))
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
const { method, params } = JSON.parse(
|
const { method, params } = JSON.parse(
|
||||||
ev.data.substr(LegacyExternalInterface.MESSAGE_KEY.length)
|
ev.data.substring(LegacyExternalInterface.MESSAGE_KEY.length)
|
||||||
);
|
);
|
||||||
|
|
||||||
const fn = (window as any)[method];
|
// Validate method is in whitelist before executing
|
||||||
if(!fn) return;
|
if(!this.ALLOWED_EXTERNAL_METHODS.has(method))
|
||||||
|
{
|
||||||
fn(...params);
|
console.warn(`[LegacyExternalInterface] Blocked unauthorized method call: ${method}`);
|
||||||
return;
|
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;
|
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>(
|
public static call<K extends keyof typeof window.FlashExternalInterface>(
|
||||||
method: K,
|
method: K,
|
||||||
...params: Parameters<typeof window.FlashExternalInterface[K]>
|
...params: Parameters<typeof window.FlashExternalInterface[K]>
|
||||||
@@ -78,10 +207,17 @@ export class LegacyExternalInterface
|
|||||||
{
|
{
|
||||||
if(window.top !== window)
|
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({
|
window.top.postMessage(LegacyExternalInterface.MESSAGE_KEY + JSON.stringify({
|
||||||
method,
|
method,
|
||||||
params
|
params
|
||||||
}), '*');
|
}), targetOrigin);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!('FlashExternalInterface' in window)) return undefined;
|
if(!('FlashExternalInterface' in window)) return undefined;
|
||||||
@@ -98,10 +234,15 @@ export class LegacyExternalInterface
|
|||||||
{
|
{
|
||||||
if(window.top !== window)
|
if(window.top !== window)
|
||||||
{
|
{
|
||||||
window.top.postMessage('Nitro_LegacyExternalGameInterface' + JSON.stringify({
|
// 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,
|
method,
|
||||||
params
|
params
|
||||||
}), '*');
|
}), targetOrigin);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!('FlashExternalGameInterface' in window)) return undefined;
|
if(!('FlashExternalGameInterface' in window)) return undefined;
|
||||||
@@ -111,8 +252,55 @@ export class LegacyExternalInterface
|
|||||||
return typeof fn !== 'undefined' ? fn(...params) : undefined;
|
return typeof fn !== 'undefined' ? fn(...params) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static addCallback(name: string, func: Function)
|
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;
|
(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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user