第54节:安全防护 - 3D场景中的XSS防御

概述

随着Three.js应用的复杂化和网络化,安全威胁变得越来越重要。特别是在加载外部模型、处理用户输入和实现多人协作时,XSS(跨站脚本攻击)和其他安全漏洞可能造成严重危害。本节将深入探讨3D场景中的安全风险及防护策略。

3D应用安全威胁图谱:
3D应用安全威胁 代码注入攻击 数据篡改攻击 资源滥用攻击 隐私泄露攻击 XSS注入 恶意脚本执行 Shader代码注入 模型数据篡改 场景配置篡改 用户数据篡改 GPU资源耗尽 内存溢出攻击 网络带宽滥用 位置信息泄露 行为模式分析 设备指纹收集 数据窃取 服务中断 性能降级 隐私侵犯

核心安全威胁分析

XSS攻击向量分析

攻击类型 攻击路径 潜在危害 防护难度
模型文件注入 恶意GLTF/OBJ文件 脚本执行、数据窃取
纹理脚本注入 图片EXIF数据、SVG脚本 权限提升、会话劫持
JSON配置注入 场景配置数据 代码执行、重定向
URL参数注入 分享链接参数 钓鱼攻击、恶意跳转
用户输入注入 聊天框、文件名 XSS攻击、社会工程

3D特定安全挑战

  1. 着色器代码安全

    • 动态着色器编译风险
    • WebGL扩展滥用
    • GPU内存耗尽攻击
  2. 模型加载安全

    • 二进制文件格式漏洞
    • 内存缓冲区溢出
    • 资源引用劫持
  3. 网络通信安全

    • WebSocket消息注入
    • CORS配置错误
    • 数据传输窃听

完整安全防护实现

1. 安全模型加载器

javascript 复制代码
// SecureModelLoader.js
class SecureModelLoader {
    constructor(options = {}) {
        this.options = Object.assign({
            maxFileSize: 50 * 1024 * 1024, // 50MB
            allowedFormats: ['gltf', 'glb', 'obj', 'fbx'],
            sanitizeGeometry: true,
            validateTextures: true,
            timeout: 30000,
            corsProxy: null
        }, options);

        this.validator = new ModelValidator(this.options);
        this.sanitizer = new ModelSanitizer(this.options);
    }

    // 安全加载模型
    async loadModel(url, onProgress = null) {
        try {
            // 1. URL验证
            this.validateURL(url);
            
            // 2. 安全获取文件
            const fileData = await this.fetchSecurely(url, onProgress);
            
            // 3. 文件格式验证
            const fileInfo = this.validator.validateFileFormat(fileData, url);
            
            // 4. 内容安全检查
            await this.validator.validateContent(fileData, fileInfo);
            
            // 5. 数据清理
            const sanitizedData = await this.sanitizer.sanitize(fileData, fileInfo);
            
            // 6. 使用Three.js加载器加载
            return await this.loadWithThreeJS(sanitizedData, fileInfo);
            
        } catch (error) {
            console.error('安全模型加载失败:', error);
            throw new Error(`模型加载失败: ${error.message}`);
        }
    }

    // URL验证
    validateURL(url) {
        try {
            const urlObj = new URL(url);
            
            // 协议检查
            if (!['http:', 'https:'].includes(urlObj.protocol)) {
                throw new Error('不支持的协议');
            }
            
            // 域名白名单
            const allowedDomains = this.options.allowedDomains;
            if (allowedDomains && !allowedDomains.includes(urlObj.hostname)) {
                throw new Error('域名不在白名单中');
            }
            
            // 路径遍历检查
            if (urlObj.pathname.includes('..') || urlObj.pathname.includes('//')) {
                throw new Error('检测到路径遍历攻击');
            }
            
        } catch (error) {
            throw new Error(`URL验证失败: ${error.message}`);
        }
    }

    // 安全文件获取
    async fetchSecurely(url, onProgress) {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), this.options.timeout);
        
        try {
            const response = await fetch(url, {
                signal: controller.signal,
                credentials: 'omit', // 不发送cookie
                referrerPolicy: 'no-referrer'
            });
            
            clearTimeout(timeoutId);
            
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}: ${response.statusText}`);
            }
            
            // 检查Content-Type
            const contentType = response.headers.get('content-type');
            if (!this.validator.isValidContentType(contentType)) {
                throw new Error(`不支持的内容类型: ${contentType}`);
            }
            
            // 检查文件大小
            const contentLength = response.headers.get('content-length');
            if (contentLength && parseInt(contentLength) > this.options.maxFileSize) {
                throw new Error('文件大小超过限制');
            }
            
            // 读取数据
            const reader = response.body.getReader();
            const chunks = [];
            let receivedLength = 0;
            
            while (true) {
                const { done, value } = await reader.read();
                
                if (done) break;
                
                // 检查大小限制
                receivedLength += value.length;
                if (receivedLength > this.options.maxFileSize) {
                    throw new Error('文件大小超过限制');
                }
                
                chunks.push(value);
                
                // 进度回调
                if (onProgress) {
                    onProgress(receivedLength);
                }
            }
            
            // 合并数据
            const arrayBuffer = new Uint8Array(receivedLength);
            let position = 0;
            for (const chunk of chunks) {
                arrayBuffer.set(chunk, position);
                position += chunk.length;
            }
            
            return arrayBuffer;
            
        } catch (error) {
            clearTimeout(timeoutId);
            throw new Error(`文件获取失败: ${error.message}`);
        }
    }

    // 使用Three.js加载器加载
    async loadWithThreeJS(data, fileInfo) {
        return new Promise((resolve, reject) => {
            let loader;
            
            switch (fileInfo.format) {
                case 'gltf':
                case 'glb':
                    loader = new THREE.GLTFLoader();
                    break;
                case 'obj':
                    loader = new THREE.OBJLoader();
                    break;
                case 'fbx':
                    loader = new THREE.FBXLoader();
                    break;
                default:
                    reject(new Error(`不支持的格式: ${fileInfo.format}`));
                    return;
            }
            
            try {
                const result = loader.parse(data, fileInfo.basePath);
                resolve(result);
            } catch (error) {
                reject(new Error(`Three.js解析失败: ${error.message}`));
            }
        });
    }
}

// 模型验证器
class ModelValidator {
    constructor(options) {
        this.options = options;
    }

    // 验证文件格式
    validateFileFormat(data, url) {
        const fileInfo = {
            url: url,
            size: data.byteLength,
            format: this.detectFormat(data, url),
            basePath: this.getBasePath(url)
        };
        
        // 格式白名单检查
        if (!this.options.allowedFormats.includes(fileInfo.format)) {
            throw new Error(`不支持的格式: ${fileInfo.format}`);
        }
        
        // 大小检查
        if (fileInfo.size > this.options.maxFileSize) {
            throw new Error('文件大小超过限制');
        }
        
        return fileInfo;
    }

    // 检测文件格式
    detectFormat(data, url) {
        const extension = url.split('.').pop().toLowerCase();
        
        // 基于魔数检测
        const header = new Uint8Array(data.slice(0, 4));
        
        // GLB格式检测 (前4字节为 'glTF')
        if (header[0] === 0x67 && header[1] === 0x6C && header[2] === 0x54 && header[3] === 0x46) {
            return 'glb';
        }
        
        // 基于扩展名回退
        return extension;
    }

    // 验证内容安全
    async validateContent(data, fileInfo) {
        switch (fileInfo.format) {
            case 'gltf':
            case 'glb':
                await this.validateGLTFContent(data, fileInfo);
                break;
            case 'obj':
                await this.validateOBJContent(data, fileInfo);
                break;
            default:
                // 基础二进制检查
                this.validateBinaryContent(data);
        }
    }

    // 验证GLTF内容
    async validateGLTFContent(data, fileInfo) {
        let gltfData;
        
        try {
            if (fileInfo.format === 'glb') {
                gltfData = this.parseGLB(data);
            } else {
                gltfData = JSON.parse(new TextDecoder().decode(data));
            }
        } catch (error) {
            throw new Error('GLTF解析失败');
        }
        
        // 检查必需字段
        if (!gltfData.asset || !gltfData.asset.version) {
            throw new Error('无效的GLTF文件');
        }
        
        // 检查版本兼容性
        if (!gltfData.asset.version.startsWith('2.')) {
            throw new Error('不支持的GLTF版本');
        }
        
        // 检查场景复杂度
        if (gltfData.scenes && gltfData.scenes.length > 10) {
            throw new Error('场景数量超过限制');
        }
        
        // 检查网格数量
        if (gltfData.meshes && gltfData.meshes.length > 100) {
            throw new Error('网格数量超过限制');
        }
        
        // 检查纹理引用
        if (gltfData.images) {
            for (const image of gltfData.images) {
                await this.validateImageReference(image, fileInfo);
            }
        }
        
        // 检查扩展和额外字段
        this.validateExtensions(gltfData);
    }

    // 解析GLB文件
    parseGLB(data) {
        const dataView = new DataView(data);
        
        // 检查魔数
        if (dataView.getUint32(0, true) !== 0x46546C67) {
            throw new Error('无效的GLB文件');
        }
        
        // 检查版本
        const version = dataView.getUint32(4, true);
        if (version !== 2) {
            throw new Error('不支持的GLB版本');
        }
        
        // 解析JSON块
        const chunkLength = dataView.getUint32(12, true);
        const chunkType = dataView.getUint32(16, true);
        
        if (chunkType !== 0x4E4F534A) { // JSON
            throw new Error('GLB格式错误');
        }
        
        const jsonChunk = new Uint8Array(data, 20, chunkLength);
        const jsonText = new TextDecoder().decode(jsonChunk);
        
        return JSON.parse(jsonText);
    }

    // 验证图片引用
    async validateImageReference(image, fileInfo) {
        if (image.uri) {
            // 检查数据URI
            if (image.uri.startsWith('data:')) {
                this.validateDataURI(image.uri);
            } else {
                // 检查外部URL
                this.validateExternalURL(image.uri, fileInfo.basePath);
            }
        }
    }

    // 验证数据URI
    validateDataURI(dataURI) {
        // 检查数据URI格式
        const dataURIPattern = /^data:([a-z]+\/[a-z0-9-+.]+);base64,[a-z0-9+/]+=*$/i;
        if (!dataURIPattern.test(dataURI)) {
            throw new Error('无效的数据URI格式');
        }
        
        // 检查MIME类型
        const mimeType = dataURI.split(':')[1].split(';')[0];
        const allowedMimeTypes = ['image/jpeg', 'image/png', 'image/webp'];
        if (!allowedMimeTypes.includes(mimeType)) {
            throw new Error(`不支持的图片格式: ${mimeType}`);
        }
        
        // 检查数据大小
        const base64Data = dataURI.split(',')[1];
        const dataSize = (base64Data.length * 3) / 4; // 近似大小
        if (dataSize > 5 * 1024 * 1024) { // 5MB限制
            throw new Error('数据URI图片大小超过限制');
        }
    }

    // 验证外部URL
    validateExternalURL(url, basePath) {
        try {
            const resolvedURL = new URL(url, basePath);
            this.validateURL(resolvedURL.href);
        } catch (error) {
            throw new Error(`无效的图片URL: ${error.message}`);
        }
    }

    // 验证扩展
    validateExtensions(gltfData) {
        const allowedExtensions = [
            'KHR_materials_pbrSpecularGlossiness',
            'KHR_lights_punctual',
            'KHR_materials_unlit'
        ];
        
        if (gltfData.extensionsUsed) {
            for (const extension of gltfData.extensionsUsed) {
                if (!allowedExtensions.includes(extension)) {
                    throw new Error(`不支持的扩展: ${extension}`);
                }
            }
        }
    }

    // 内容类型验证
    isValidContentType(contentType) {
        const allowedTypes = [
            'application/octet-stream',
            'model/gltf-binary',
            'model/gltf+json',
            'application/json',
            'text/plain'
        ];
        
        return !contentType || allowedTypes.some(type => contentType.includes(type));
    }

    getBasePath(url) {
        return url.substring(0, url.lastIndexOf('/') + 1);
    }
}

// 模型清理器
class ModelSanitizer {
    constructor(options) {
        this.options = options;
    }

    // 清理模型数据
    async sanitize(data, fileInfo) {
        switch (fileInfo.format) {
            case 'gltf':
                return await this.sanitizeGLTF(data);
            case 'obj':
                return await this.sanitizeOBJ(data);
            default:
                return data; // 二进制格式直接返回
        }
    }

    // 清理GLTF数据
    async sanitizeGLTF(data) {
        const gltfData = JSON.parse(new TextDecoder().decode(data));
        
        // 移除脚本相关字段
        this.removeScriptFields(gltfData);
        
        // 清理额外字段
        this.cleanExtraFields(gltfData);
        
        // 限制几何体复杂度
        if (this.options.sanitizeGeometry) {
            this.limitGeometryComplexity(gltfData);
        }
        
        return new TextEncoder().encode(JSON.stringify(gltfData));
    }

    // 移除脚本字段
    removeScriptFields(obj) {
        if (typeof obj !== 'object' || obj === null) return;
        
        // 删除可疑字段
        const dangerousFields = ['script', 'onload', 'onerror', 'onclick', 'javascript', 'eval'];
        
        for (const key in obj) {
            if (dangerousFields.some(field => key.toLowerCase().includes(field))) {
                delete obj[key];
            } else if (typeof obj[key] === 'object') {
                this.removeScriptFields(obj[key]);
            } else if (typeof obj[key] === 'string') {
                // 检查字符串中的脚本
                if (this.containsScript(obj[key])) {
                    obj[key] = this.sanitizeString(obj[key]);
                }
            }
        }
    }

    // 检查是否包含脚本
    containsScript(str) {
        const scriptPatterns = [
            /javascript:/i,
            /vbscript:/i,
            /onload\s*=/i,
            /onerror\s*=/i,
            /<script/i,
            /eval\s*\(/i
        ];
        
        return scriptPatterns.some(pattern => pattern.test(str));
    }

    // 清理字符串
    sanitizeString(str) {
        return str
            .replace(/javascript:/gi, '')
            .replace(/vbscript:/gi, '')
            .replace(/onload\s*=/gi, '')
            .replace(/onerror\s*=/gi, '')
            .replace(/<script/gi, '')
            .replace(/eval\s*\(/gi, '');
    }

    // 限制几何体复杂度
    limitGeometryComplexity(gltfData) {
        if (!gltfData.meshes) return;
        
        const maxVertices = 50000;
        const maxTriangles = 100000;
        
        for (const mesh of gltfData.meshes) {
            for (const primitive of mesh.primitives) {
                // 这里可以添加顶点和三角形数量检查
                // 实际实现需要访问几何体数据
            }
        }
    }
}

2. 安全着色器管理器

javascript 复制代码
// SecureShaderManager.js
class SecureShaderManager {
    constructor() {
        this.allowedFunctions = new Set([
            'main', 'sin', 'cos', 'tan', 'asin', 'acos', 'atan',
            'pow', 'exp', 'log', 'exp2', 'log2', 'sqrt', 'inversesqrt',
            'abs', 'sign', 'floor', 'ceil', 'fract', 'mod', 'min', 'max',
            'clamp', 'mix', 'step', 'smoothstep', 'length', 'distance',
            'dot', 'cross', 'normalize', 'reflect', 'refract',
            'matrixCompMult', 'transpose', 'determinant', 'inverse'
        ]);
        
        this.bannedPatterns = [
            /discard\s*;/,
            /while\s*\(/,
            /for\s*\(/,
            /do\s*\{/,
            /goto\s+/,
            /asm\s*\{/,
            /texture2D\s*\(/,
            /textureCube\s*\(/,
            /dFdx\s*\(/,
            /dFdy\s*\(/
        ];
    }

    // 验证着色器代码
    validateShader(code, type) {
        const errors = [];
        
        // 基础语法检查
        if (!this.isValidGLSL(code)) {
            errors.push('无效的GLSL语法');
        }
        
        // 函数白名单检查
        const usedFunctions = this.extractFunctions(code);
        for (const func of usedFunctions) {
            if (!this.allowedFunctions.has(func)) {
                errors.push(`不允许的函数: ${func}`);
            }
        }
        
        // 禁止模式检查
        for (const pattern of this.bannedPatterns) {
            if (pattern.test(code)) {
                errors.push(`检测到禁止的模式: ${pattern}`);
            }
        }
        
        // 复杂度检查
        if (this.calculateComplexity(code) > 100) {
            errors.push('着色器过于复杂');
        }
        
        // 长度检查
        if (code.length > 10000) {
            errors.push('着色器代码过长');
        }
        
        return {
            isValid: errors.length === 0,
            errors: errors
        };
    }

    // 检查GLSL语法
    isValidGLSL(code) {
        // 基础语法检查
        const basicChecks = [
            code.includes('void main'),
            !code.includes('#include'), // 禁止include
            !code.includes('#pragma')   // 禁止pragma
        ];
        
        return basicChecks.every(check => check);
    }

    // 提取使用的函数
    extractFunctions(code) {
        const functionPattern = /[a-zA-Z_][a-zA-Z0-9_]*\s*\(/g;
        const functions = new Set();
        let match;
        
        while ((match = functionPattern.exec(code)) !== null) {
            const funcName = match[0].split('(')[0].trim();
            if (funcName && !this.isGLSLKeyword(funcName)) {
                functions.add(funcName);
            }
        }
        
        return functions;
    }

    // 检查是否是GLSL关键字
    isGLSLKeyword(word) {
        const keywords = [
            'attribute', 'uniform', 'varying', 'layout', 'in', 'out',
            'const', 'float', 'int', 'uint', 'bool', 'true', 'false',
            'if', 'else', 'switch', 'case', 'default', 'break', 'continue',
            'return', 'struct', 'void', 'while', 'for', 'do', 'discard'
        ];
        
        return keywords.includes(word);
    }

    // 计算复杂度
    calculateComplexity(code) {
        let complexity = 0;
        
        // 循环复杂度
        complexity += (code.match(/for\s*\(/g) || []).length;
        complexity += (code.match(/while\s*\(/g) || []).length;
        
        // 条件复杂度
        complexity += (code.match(/if\s*\(/g) || []).length;
        complexity += (code.match(/else\s*if/g) || []).length;
        
        // 函数调用复杂度
        complexity += (code.match(/[a-zA-Z_][a-zA-Z0-9_]*\s*\(/g) || []).length;
        
        return complexity;
    }

    // 创建安全着色器材质
    createSecureShaderMaterial(vertexShader, fragmentShader, uniforms) {
        const vertexValidation = this.validateShader(vertexShader, 'vertex');
        const fragmentValidation = this.validateShader(fragmentShader, 'fragment');
        
        if (!vertexValidation.isValid || !fragmentValidation.isValid) {
            const errors = [
                ...vertexValidation.errors,
                ...fragmentValidation.errors
            ];
            throw new Error(`着色器验证失败: ${errors.join(', ')}`);
        }
        
        return new THREE.ShaderMaterial({
            vertexShader: vertexShader,
            fragmentShader: fragmentShader,
            uniforms: uniforms
        });
    }
}

3. 安全用户输入处理

javascript 复制代码
// SecureInputHandler.js
class SecureInputHandler {
    constructor() {
        this.xssFilter = new XSSFilter();
        this.rateLimiter = new RateLimiter();
    }

    // 处理文本输入
    sanitizeTextInput(input, options = {}) {
        const defaults = {
            maxLength: 1000,
            allowHTML: false,
            allowScript: false,
            allowedTags: [],
            allowedAttributes: {}
        };
        
        const config = { ...defaults, ...options };
        
        // 长度检查
        if (input.length > config.maxLength) {
            throw new Error(`输入长度超过限制: ${config.maxLength}`);
        }
        
        // XSS过滤
        let sanitized = this.xssFilter.filter(input, config);
        
        // 额外清理
        sanitized = this.extraSanitization(sanitized);
        
        return sanitized;
    }

    // 处理文件上传
    validateFileUpload(file, options = {}) {
        const defaults = {
            maxSize: 10 * 1024 * 1024, // 10MB
            allowedTypes: ['image/jpeg', 'image/png', 'image/gif'],
            allowedExtensions: ['.jpg', '.jpeg', '.png', '.gif']
        };
        
        const config = { ...defaults, ...options };
        
        // 大小检查
        if (file.size > config.maxSize) {
            throw new Error('文件大小超过限制');
        }
        
        // 类型检查
        if (!config.allowedTypes.includes(file.type)) {
            throw new Error('不支持的文件类型');
        }
        
        // 扩展名检查
        const extension = '.' + file.name.split('.').pop().toLowerCase();
        if (!config.allowedExtensions.includes(extension)) {
            throw new Error('不支持的文件扩展名');
        }
        
        // 文件名安全检查
        if (!this.isSafeFileName(file.name)) {
            throw new Error('无效的文件名');
        }
        
        return true;
    }

    // 检查文件名安全
    isSafeFileName(fileName) {
        const dangerousPatterns = [
            /\.\.\//,   // 路径遍历
            /\/\//,     // 双斜杠
            /\\/,       // 反斜杠
            /^\//,      // 绝对路径
            /[<>:"|?*]/ // 非法字符
        ];
        
        return !dangerousPatterns.some(pattern => pattern.test(fileName));
    }

    // 额外清理
    extraSanitization(input) {
        return input
            .replace(/\0/g, '') // 移除空字符
            .replace(/\u2028/g, '') // 移除行分隔符
            .replace(/\u2029/g, '') // 移除段落分隔符
            .trim();
    }
}

// XSS过滤器
class XSSFilter {
    filter(input, config) {
        if (!config.allowHTML) {
            // 基本文本过滤
            return this.escapeHTML(input);
        }
        
        // 使用DOMParser进行安全HTML解析
        return this.sanitizeHTML(input, config);
    }

    // HTML转义
    escapeHTML(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }

    // 安全HTML清理
    sanitizeHTML(html, config) {
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');
        
        this.walkDOM(doc.body, (node) => {
            if (node.nodeType === Node.ELEMENT_NODE) {
                this.sanitizeElement(node, config);
            }
        });
        
        return doc.body.innerHTML;
    }

    // 遍历DOM
    walkDOM(node, callback) {
        callback(node);
        node = node.firstChild;
        while (node) {
            this.walkDOM(node, callback);
            node = node.nextSibling;
        }
    }

    // 清理元素
    sanitizeElement(element, config) {
        // 检查标签白名单
        if (!config.allowedTags.includes(element.tagName.toLowerCase())) {
            element.parentNode.removeChild(element);
            return;
        }
        
        // 清理属性
        const attributes = Array.from(element.attributes);
        for (const attr of attributes) {
            if (!this.isAllowedAttribute(attr.name, config.allowedAttributes)) {
                element.removeAttribute(attr.name);
            } else if (this.isDangerousAttribute(attr.name, attr.value)) {
                element.removeAttribute(attr.name);
            }
        }
        
        // 检查事件处理器
        this.removeEventHandlers(element);
    }

    // 检查属性是否允许
    isAllowedAttribute(attrName, allowedAttributes) {
        const safeAttributes = ['class', 'id', 'style', 'src', 'href', 'alt', 'title'];
        return safeAttributes.includes(attrName) || 
               allowedAttributes[attrName] === true;
    }

    // 检查危险属性
    isDangerousAttribute(attrName, attrValue) {
        const dangerousPatterns = [
            /javascript:/i,
            /vbscript:/i,
            /data:/i,
            /on\w+/i
        ];
        
        return dangerousPatterns.some(pattern => 
            pattern.test(attrName) || pattern.test(attrValue)
        );
    }

    // 移除事件处理器
    removeEventHandlers(element) {
        const eventAttributes = element.attributes;
        for (let i = eventAttributes.length - 1; i >= 0; i--) {
            const attr = eventAttributes[i];
            if (attr.name.startsWith('on')) {
                element.removeAttribute(attr.name);
            }
        }
    }
}

// 速率限制器
class RateLimiter {
    constructor() {
        this.requests = new Map();
        this.config = {
            windowMs: 15 * 60 * 1000, // 15分钟
            maxRequests: 100,
            delayMs: 0
        };
    }

    // 检查是否超过限制
    checkLimit(identifier) {
        const now = Date.now();
        const windowStart = now - this.config.windowMs;
        
        if (!this.requests.has(identifier)) {
            this.requests.set(identifier, []);
        }
        
        const userRequests = this.requests.get(identifier);
        
        // 清理过期请求
        while (userRequests.length > 0 && userRequests[0] < windowStart) {
            userRequests.shift();
        }
        
        // 检查请求数量
        if (userRequests.length >= this.config.maxRequests) {
            return {
                allowed: false,
                remaining: 0,
                resetTime: userRequests[0] + this.config.windowMs
            };
        }
        
        // 添加新请求
        userRequests.push(now);
        
        return {
            allowed: true,
            remaining: this.config.maxRequests - userRequests.length,
            resetTime: now + this.config.windowMs
        };
    }
}

4. 内容安全策略(CSP)配置

html 复制代码
<!-- CSP配置示例 -->
<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self';
               script-src 'self' 'unsafe-eval' https://cdnjs.cloudflare.com;
               style-src 'self' 'unsafe-inline';
               img-src 'self' data: https:;
               media-src 'self';
               connect-src 'self' wss:;
               font-src 'self';
               object-src 'none';
               base-uri 'self';
               form-action 'self';
               frame-ancestors 'none';
               block-all-mixed-content;">

安全监控与审计

安全事件日志系统

javascript 复制代码
// SecurityLogger.js
class SecurityLogger {
    constructor() {
        this.endpoint = '/api/security/log';
        this.queue = [];
        this.flushInterval = 30000; // 30秒
        this.maxQueueSize = 100;
        
        this.startFlushTimer();
    }

    // 记录安全事件
    logEvent(event) {
        const logEntry = {
            timestamp: new Date().toISOString(),
            type: event.type,
            severity: event.severity || 'info',
            message: event.message,
            userAgent: navigator.userAgent,
            url: window.location.href,
            data: event.data || {}
        };
        
        this.queue.push(logEntry);
        
        // 队列满时立即刷新
        if (this.queue.length >= this.maxQueueSize) {
            this.flush();
        }
        
        // 控制台输出(开发环境)
        if (process.env.NODE_ENV === 'development') {
            console.log(`[Security] ${event.type}: ${event.message}`, event.data);
        }
    }

    // 记录XSS尝试
    logXSSAttempt(details) {
        this.logEvent({
            type: 'xss_attempt',
            severity: 'warning',
            message: '检测到XSS攻击尝试',
            data: details
        });
    }

    // 记录模型加载失败
    logModelLoadFailure(url, reason) {
        this.logEvent({
            type: 'model_load_failure',
            severity: 'error',
            message: `模型加载失败: ${url}`,
            data: { url, reason }
        });
    }

    // 记录速率限制触发
    logRateLimit(identifier) {
        this.logEvent({
            type: 'rate_limit_triggered',
            severity: 'warning',
            message: `速率限制触发: ${identifier}`,
            data: { identifier }
        });
    }

    // 刷新日志到服务器
    async flush() {
        if (this.queue.length === 0) return;
        
        const logsToSend = [...this.queue];
        this.queue = [];
        
        try {
            await fetch(this.endpoint, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ logs: logsToSend })
            });
        } catch (error) {
            // 发送失败,重新加入队列
            this.queue.unshift(...logsToSend);
            console.error('安全日志发送失败:', error);
        }
    }

    startFlushTimer() {
        setInterval(() => this.flush(), this.flushInterval);
    }
}

安全测试与验证

自动化安全测试

javascript 复制代码
// SecurityTestSuite.js
class SecurityTestSuite {
    constructor() {
        this.tests = [];
    }

    // 添加测试用例
    addTest(name, testFunction) {
        this.tests.push({ name, testFunction });
    }

    // 运行所有测试
    async runAllTests() {
        const results = [];
        
        for (const test of this.tests) {
            try {
                await test.testFunction();
                results.push({ name: test.name, passed: true });
            } catch (error) {
                results.push({ 
                    name: test.name, 
                    passed: false, 
                    error: error.message 
                });
            }
        }
        
        return results;
    }

    // 创建标准测试套件
    createStandardSuite() {
        // XSS注入测试
        this.addTest('XSS Script Injection', async () => {
            const inputHandler = new SecureInputHandler();
            const maliciousInput = '<script>alert("XSS")</script>';
            const sanitized = inputHandler.sanitizeTextInput(maliciousInput, { allowHTML: false });
            
            if (sanitized.includes('<script>')) {
                throw new Error('XSS脚本未正确过滤');
            }
        });

        // 模型文件安全测试
        this.addTest('Malicious Model Detection', async () => {
            const loader = new SecureModelLoader();
            const maliciousData = new TextEncoder().encode('恶意内容');
            
            try {
                await loader.loadModel('data:application/octet-stream;base64,' + 
                    btoa(String.fromCharCode(...maliciousData)));
                throw new Error('恶意模型未被正确拒绝');
            } catch (error) {
                // 期望抛出错误
            }
        });

        // 着色器安全测试
        this.addTest('Shader Code Injection', async () => {
            const shaderManager = new SecureShaderManager();
            const maliciousShader = `
                void main() {
                    discard;
                    for(;;) {}
                }
            `;
            
            const result = shaderManager.validateShader(maliciousShader, 'fragment');
            if (result.isValid) {
                throw new Error('恶意着色器未被正确检测');
            }
        });

        // 速率限制测试
        this.addTest('Rate Limiting', async () => {
            const rateLimiter = new RateLimiter();
            const identifier = 'test-user';
            
            // 发送大量请求
            for (let i = 0; i < 150; i++) {
                const result = rateLimiter.checkLimit(identifier);
                if (i >= 100 && result.allowed) {
                    throw new Error('速率限制未正确工作');
                }
            }
        });
    }
}

通过实施这些安全防护措施,可以显著降低Three.js应用面临的安全风险,保护用户数据和系统完整性。安全是一个持续的过程,需要定期审查和更新防护策略以应对新的威胁。

相关推荐
网安小白的进阶之路5 小时前
B模块 安全通信网络 第一门课 园区网实现与安全-1
网络·安全
快起来搬砖了5 小时前
Vue 实现阿里云 OSS 视频分片上传:安全实战与完整方案
vue.js·安全·阿里云
爱隐身的官人5 小时前
beef-xss hook.js访问失败500错误
javascript·xss
changlianzhifu17 小时前
AI反欺诈与生物识别:数字支付的安全双刃剑
人工智能·安全
独行soc7 小时前
2025年渗透测试面试题总结-254(题目+回答)
网络·python·安全·web安全·adb·渗透测试·安全狮
newxtc7 小时前
【中国石油和化工网-注册安全分析及升级报告】
人工智能·selenium·测试工具·安全·短信盗刷·石油和化工
龙亘川7 小时前
应急管理数字化转型深度解析:从政策到落地,AI + 大数据如何重塑安全防线(附白皮书 5 页核心干货)
大数据·人工智能·安全·智慧城市·ai + 应急·应急管理数字化·解读技术白皮书
武汉唯众智创8 小时前
网络安全教学升级!基于深度强化学习的动态对抗网络安全防护教学方案全解析
网络·人工智能·安全·web安全·生成对抗网络·网络安全
reddingtons9 小时前
Firefly Text-to-Texture:一键生成PBR武器材质的游戏美术效率革命
人工智能·3d·prompt·材质·技术美术·游戏策划·游戏美术