파일 다운로드 (경로 탐색) | 위험도: 5 (매우 높음) | 통제구분: 5.8.4 서비스 보호
인터넷뱅킹 - 거래내역 다운로드 서비스
계좌별 거래내역을 엑셀 파일로 다운로드합니다
이용 안내
거래내역 파일은 /user_data/documents/ 폴더에 저장됩니다
// ❌ 취약한 코드 - 경로 검증 없음
@GetMapping("/download")
public ResponseEntity<Resource> downloadFile(
@RequestParam String fileName) {
// 사용자 입력을 그대로 경로에 결합!
String filePath = "/user_data/documents/" + fileName;
// 경로 검증 없이 파일 읽기
File file = new File(filePath);
Resource resource = new FileSystemResource(file);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + fileName + "\"")
.body(resource);
}
// ✅ 안전한 코드 - 경로 검증 및 정규화
@GetMapping("/download")
public ResponseEntity<Resource> downloadFile(
@RequestParam String fileName) throws Exception {
// 1. 경로 탐색 문자 제거
fileName = fileName.replaceAll("\\.\\.", "")
.replaceAll("/", "")
.replaceAll("\\\\", "");
// 2. 파일명만 추출 (경로 제거)
Path path = Paths.get(fileName);
fileName = path.getFileName().toString();
// 3. 허용된 확장자만 허용
String extension = fileName.substring(
fileName.lastIndexOf(".") + 1
).toLowerCase();
Set<String> allowedExtensions = Set.of(
"xlsx", "pdf", "csv"
);
if (!allowedExtensions.contains(extension)) {
throw new SecurityException(
"허용되지 않은 파일 형식"
);
}
// 4. 베이스 디렉토리 설정
String baseDir = "/user_data/documents/";
Path basePath = Paths.get(baseDir).normalize();
// 5. 전체 경로 생성 및 정규화
Path fullPath = basePath.resolve(fileName).normalize();
// 6. 경로 벗어남 체크
if (!fullPath.startsWith(basePath)) {
throw new SecurityException(
"허용된 경로 외부 접근 시도"
);
}
// 7. 파일 존재 여부 확인
File file = fullPath.toFile();
if (!file.exists() || !file.isFile()) {
throw new FileNotFoundException(
"파일을 찾을 수 없습니다"
);
}
// 8. 안전하게 다운로드
Resource resource = new FileSystemResource(file);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" +
URLEncoder.encode(fileName, "UTF-8") + "\"")
.body(resource);
}
// 추가: 화이트리스트 기반 파일 검증
private boolean isAllowedFile(String fileName) {
// DB에 저장된 허용 파일 목록 확인
List<String> allowedFiles =
fileRepository.findAllowedFiles();
return allowedFiles.contains(fileName);
}