diff --git a/src/main/java/com/madeu/config/WebConfig.java b/src/main/java/com/madeu/config/WebConfig.java index 6a422a8..3d63e51 100644 --- a/src/main/java/com/madeu/config/WebConfig.java +++ b/src/main/java/com/madeu/config/WebConfig.java @@ -1,17 +1,14 @@ package com.madeu.config; -import java.io.IOException; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; - import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; -import org.springframework.util.StringUtils; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.resource.PathResourceResolver; +import java.io.IOException; + @Configuration public class WebConfig implements WebMvcConfigurer { @@ -22,72 +19,32 @@ public class WebConfig implements WebMvcConfigurer { public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/cdn/**") .addResourceLocations("file:" + uploadPath + "/") - .setCachePeriod(3600) + .setCachePeriod(3600) // 1시간 캐싱 .resourceChain(true) .addResolver(new PathResourceResolver() { @Override protected Resource getResource(String resourcePath, Resource location) throws IOException { - // 1. URL 디코딩 - String decodedPath = decodeResourcePath(resourcePath); + Resource requestedResource = location.createRelative(resourcePath); - // 2. 경로 정규화 (보안 검증) - if (!isValidPath(decodedPath)) { - return null; - } - - // 3. 리소스 찾기 - Resource requestedResource = location.createRelative(decodedPath); - - // 4. 존재 여부 및 허용된 파일 타입 검증 - if (requestedResource.exists() && - requestedResource.isReadable() && - isAllowedResource(requestedResource)) { + // 보안 검증: 허용된 파일 타입만 + if (requestedResource.exists() && requestedResource.isReadable() + && isAllowedResource(requestedResource)) { return requestedResource; } - return null; } - private String decodeResourcePath(String path) { - try { - // UTF-8로 URL 디코딩 - return URLDecoder.decode(path, StandardCharsets.UTF_8.name()); - } catch (Exception e) { - // 디코딩 실패 시 원본 반환 - return path; - } - } - - private boolean isValidPath(String path) { - // 경로 탐색 공격 방지 (../ 등) - if (path.contains("..") || path.contains("./")) { - return false; - } - - // 절대 경로 방지 - if (path.startsWith("/") || path.contains(":")) { - return false; - } - - return true; - } - private boolean isAllowedResource(Resource resource) { try { String filename = resource.getFilename(); - if (!StringUtils.hasText(filename)) { - return false; - } - - // 허용된 확장자 체크 - String lowerFilename = filename.toLowerCase(); - return lowerFilename.endsWith(".jpg") || - lowerFilename.endsWith(".jpeg") || - lowerFilename.endsWith(".png") || - lowerFilename.endsWith(".gif") || - lowerFilename.endsWith(".webp") || - lowerFilename.endsWith(".bmp") || - lowerFilename.endsWith(".svg"); + return filename != null && + (filename.toLowerCase().endsWith(".jpg") || + filename.toLowerCase().endsWith(".jpeg") || + filename.toLowerCase().endsWith(".png") || + filename.toLowerCase().endsWith(".gif") || + filename.toLowerCase().endsWith(".webp") || + filename.toLowerCase().endsWith(".bmp") || + filename.toLowerCase().endsWith(".svg")); } catch (Exception e) { return false; }