// lib/file-storage.ts - 보안이 강화된 파일 저장 유틸리티 import { promises as fs, createWriteStream } from "fs"; import path from "path"; import crypto from "crypto"; import { createHash } from "crypto"; import { Readable } from 'stream' interface FileStorageConfig { baseDir: string; publicUrl: string; isProduction: boolean; } // 보안 설정 const SECURITY_CONFIG = { // 허용된 파일 확장자 ALLOWED_EXTENSIONS: new Set([ 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'csv', 'png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', // SVG 제거 - XSS 위험으로 인해 // 'svg', 'dwg', 'dxf', 'zip', 'rar', '7z' ]), // 금지된 파일 확장자 (실행 파일 등) FORBIDDEN_EXTENSIONS: new Set([ 'exe', 'bat', 'cmd', 'scr', 'vbs', 'js', 'jar', 'com', 'pif', 'msi', 'reg', 'ps1', 'sh', 'php', 'asp', 'jsp', 'py', 'pl', // XSS 방지를 위한 추가 확장자 'html', 'htm', 'xhtml', 'xml', 'xsl', 'xslt','svg', // 돌체 블랙리스트 추가 'dll', 'vbs', 'js', 'aspx', 'cmd' ]), // 허용된 MIME 타입 ALLOWED_MIME_TYPES: new Set([ 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp', // SVG 제거 - XSS 위험으로 인해 // 'image/svg+xml', 'text/plain', 'text/csv', 'application/zip', 'application/x-rar-compressed', 'application/x-7z-compressed' ]), // 최대 파일 크기 (1GB) MAX_FILE_SIZE: 1024 * 1024 * 1024, // 파일명 최대 길이 MAX_FILENAME_LENGTH: 255, }; // 보안 검증 클래스 class FileSecurityValidator { // 파일 확장자 검증 static validateExtension(fileName: string): { valid: boolean; error?: string } { const extension = path.extname(fileName).toLowerCase().substring(1); if (!extension) { return { valid: false, error: "파일 확장자가 없습니다" }; } if (SECURITY_CONFIG.FORBIDDEN_EXTENSIONS.has(extension)) { return { valid: false, error: `금지된 파일 형식입니다: .${extension}` }; } // if (!SECURITY_CONFIG.ALLOWED_EXTENSIONS.has(extension)) { // return { valid: false, error: `허용되지 않은 파일 형식입니다: .${extension}` }; // } return { valid: true }; } // 파일명 안전성 검증 static validateFileName(fileName: string): { valid: boolean; error?: string } { // 길이 체크 if (fileName.length > SECURITY_CONFIG.MAX_FILENAME_LENGTH) { return { valid: false, error: "파일명이 너무 깁니다" }; } // 위험한 문자 체크 (XSS 방지 강화) const dangerousPatterns = [ /[<>:"|?*]/, // HTML 태그 및 특수문자 /[\x00-\x1f]/, // 제어문자 /^\./, // 숨김 파일 /\.\./, // 상위 디렉토리 접근 /\/|\\$/, // 경로 구분자 /javascript:/i, // JavaScript 프로토콜 /data:/i, // Data URI /vbscript:/i, // VBScript 프로토콜 /on\w+=/i, // 이벤트 핸들러 (onclick=, onload= 등) /