Spring Boot 파일 업로드 보안 취약점과 방어법
Spring Boot 파일 업로드에서 발생하는 주요 취약점과 공격 경로를 설명하고, 검증·저장·설정 중심의 실무적 방어 기법을 정리한 정리
목차
개요
웹 애플리케이션에서 파일 업로드는 필수 기능이다. 그러나 검증이 부족하면 경로 조작, 악성 코드 업로드, 서비스 거부 등 치명적 보안 사고로 이어진다. 이 글은 spring boot file upload 보안 관련 취약점과 이를 막기 위한 실무적 방어 기법을 정리한다.
대표적 취약점과 위험
1) 경로 조작(Path Traversal)
공격자가 파일명에 "../" 같은 문자열을 삽입해 서버의 임의 파일을 덮어쓰거나 민감한 파일에 접근하는 취약점이다. 업로드된 파일을 그대로 파일시스템 경로로 사용하면 위험이 커진다.
2) 허용되지 않은 콘텐츠 업로드
확장자나 Content-Type만 검사할 경우, 실제 파일 내용은 다른 형식일 수 있다. 예를 들어 이미지로 위장한 스크립트나 바이너리를 업로드해 서버에서 실행될 여지가 있다.
3) 대용량·다중 업로드로 인한 자원 고갈
multipart 취약점 spring boot 상황에서 요청 크기 제한을 두지 않으면 메모리·디스크 자원이 소모되어 서비스 거부(DoS)로 이어진다.
4) 임의 파일 실행(RCE) 및 라이브러리 취약점
업로드된 파일을 서버 내부에서 처리하는 과정에서 취약한 라이브러리를 이용하면 원격 코드 실행으로 이어질 수 있다. 특히 압축 해제나 이미지 처리 라이브러리 사용 시 주의가 필요하다.
방어 원칙
- 입력 검증(Validation): 파일명, 크기, 타입, 콘텐츠 모두 검증
- 출력 분리(Storage): 실행 가능한 경로와 분리된 안전한 저장소 사용
- 최소 권한(Least Privilege): 업로드 디렉토리 권한 제한
- 리소스 제한: 업로드 크기와 동시 업로드 수 제한
- 악성 코드 검사: AV 스캐너 또는 샌드박스 연계
Spring Boot 적용 방어 기법
1) multipart 설정으로 기본 제한하기
application.properties 또는 yml에서 업로드 크기와 임계값을 설정한다. 메모리 기반 처리 대신 스트리밍 처리를 권장한다.
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=20MB
spring.servlet.multipart.file-size-threshold=0
2) 파일명 검증 및 정규화
사용자가 제공한 파일명은 신뢰하지 않는다. 경로 구분자 제거, 허용되는 문자만 허용, UUID로 재명명하는 방식으로 처리한다.
String original = file.getOriginalFilename();
String clean = original.replaceAll("[\\\\/\\\\\\\u0000-\\\\u001F]", "_");
String storedName = UUID.randomUUID().toString() + "." + getExtension(clean);
3) 콘텐츠 기반 타입 검사
Content-Type 헤더만 의존하지 않고 파일 바이트의 매직 넘버 또는 라이브러리(detector)를 사용해 실제 타입을 판별한다. 이미지인 경우 이미지 라이브러리로 로드해 확인한다.
4) 안전한 저장소와 권한 설정
웹 루트와 분리된 전용 디렉토리에 저장한다. 실행 권한 제거(chmod 600/640 등)와 소유자 제한으로 서버 측 명령 실행 위험을 최소화한다. 가능하면 오브젝트 스토리지(S3 등)에 저장하고, 웹에서 직접 파일을 제공하지 않도록 프록시 또는 서명된 URL을 사용한다.
5) 스트리밍 처리로 메모리 보호
대용량 파일은 메모리에 올리지 말고 스트리밍으로 처리한다. Spring의 MultipartFile.transferTo 대신 InputStream을 통한 블록 단위 저장을 사용하면 메모리 과다 사용을 예방할 수 있다.
6) 악성 코드 스캔 및 샌드박스
업로드 후 AV 엔진으로 검사하거나, 파일을 별도 샌드박스 환경에서 실행·분석 후 정상으로 판별되면 서비스에 반영한다. 자동화 파이프라인에 통합하면 운영 부담을 낮출 수 있다.
실전 예제: 간단한 컨트롤러 (스트리밍 방식)
@RestController
public class FileController {
@PostMapping("/upload")
public ResponseEntity<String> upload(@RequestParam("file") MultipartFile file) throws IOException {
if (file.isEmpty()) return ResponseEntity.badRequest().body("empty");
if (file.getSize() > 10L * 1024 * 1024) return ResponseEntity.status(413).body("too large");
String original = file.getOriginalFilename();
String clean = original.replaceAll("[\\\\/\\\\\\\u0000-\\\\u001F]", "_");
String stored = UUID.randomUUID().toString() + "." + getExtension(clean);
Path target = Paths.get("/data/uploads").resolve(stored);
try (InputStream in = file.getInputStream();
OutputStream out = Files.newOutputStream(target, StandardOpenOption.CREATE_NEW)) {
byte[] buf = new byte[8192];
int r;
while ((r = in.read(buf)) != -1) out.write(buf, 0, r);
}
// 추가: 파일 시그니처 검사, AV 스캔 등
return ResponseEntity.ok(stored);
}
}
운영 시 체크리스트
- 최대 파일 크기와 요청 크기 제한 설정
- 파일명 정규화 및 재명명 정책 적용
- 확장자뿐 아니라 콘텐츠 검사 도입
- 임시 파일 저장소 권한 최소화
- 업로드 계층에 별도 인증·인가 적용
- 로그와 모니터링으로 이상 패턴 탐지
- 외부 스토리지 사용 시 서명된 URL 사용
맺음말
file upload validation spring boot와 관련된 보안은 다층 방어가 핵심이다. 한 가지 검사로 충분하다고 믿지 말고, 입력 검증·저장 분리·리소스 제한·악성 검사 등 여러 기법을 조합하면 실무에서 발생하는 대부분의 공격을 차단할 수 있다. 보안 정책은 주기적으로 검토하고, 라이브러리와 설정은 최신 상태로 유지한다.