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

3D应用安全威胁图谱:
3D应用安全威胁 代码注入攻击 数据篡改攻击 资源滥用攻击 隐私泄露攻击 XSS注入 恶意脚本执行 Shader代码注入 模型数据篡改 场景配置篡改 用户数据篡改 GPU资源耗尽 内存溢出攻击 网络带宽滥用 位置信息泄露 行为模式分析 设备指纹收集 数据窃取 服务中断 性能降级 隐私侵犯
核心安全威胁分析
XSS攻击向量分析
| 攻击类型 | 攻击路径 | 潜在危害 | 防护难度 |
|---|---|---|---|
| 模型文件注入 | 恶意GLTF/OBJ文件 | 脚本执行、数据窃取 | 高 |
| 纹理脚本注入 | 图片EXIF数据、SVG脚本 | 权限提升、会话劫持 | 中 |
| JSON配置注入 | 场景配置数据 | 代码执行、重定向 | 中 |
| URL参数注入 | 分享链接参数 | 钓鱼攻击、恶意跳转 | 低 |
| 用户输入注入 | 聊天框、文件名 | XSS攻击、社会工程 | 低 |
3D特定安全挑战
-
着色器代码安全
- 动态着色器编译风险
- WebGL扩展滥用
- GPU内存耗尽攻击
-
模型加载安全
- 二进制文件格式漏洞
- 内存缓冲区溢出
- 资源引用劫持
-
网络通信安全
- 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应用面临的安全风险,保护用户数据和系统完整性。安全是一个持续的过程,需要定期审查和更新防护策略以应对新的威胁。