代码之家  ›  专栏  ›  技术社区  ›  Dónal

Spring Boot应用程序中的伪造证明文件类型验证

  •  0
  • Dónal  · 技术社区  · 4 月前

    我创建了一个自定义验证器,用于检查上传文件类型的Spring Boot应用程序。这个 @ValidFileType 注释定义为

    @Documented
    @Constraint(validatedBy = MultiPartFileValidator.class)
    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ValidFileType {
    
        String[] allowed() default {
            "image/webp",
            "image/svg+xml",
            "application/zip",
        };
    
        String message() default "{com.example.invalidFileType}";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    }
    

    验证器本身是

    class MultiPartFileValidator implements ConstraintValidator<ValidFileType, MultipartFile> {
    
        private Set<String> allowed;
    
        @Override
        public void initialize(ValidFileType constraintAnnotation) {
            allowed = Set.of(constraintAnnotation.allowed());
        }
    
        @Override
        public boolean isValid(MultipartFile file, ConstraintValidatorContext context) {
            return file == null || allowed.contains(file.getContentType());
        }
    }
    

    这通常用于控制器方法,如下所示:

    @PostMapping
    public void uploadFile(@Valid @ValidFileType @RequestPart MultipartFile uploadedFile) {
        // if we get this far, the file that has passed validation
    }
    

    然而,有人指出,这个验证器完全依赖于 content-type 标题可以被欺骗。如果我不能依赖这个标头(或文件名中的扩展名),还有什么可以用来验证更具弹性的文件类型吗?

    1 回复  |  直到 4 月前
        1
  •  1
  •   Roar S.    4 月前

    你可以试试蒂卡。需要此依赖关系: org.apache.tika:tika-core:3.0.0

    类似验证器中的示例用法

    import jakarta.validation.ConstraintValidator;
    import jakarta.validation.ConstraintValidatorContext;
    import org.apache.tika.Tika;
    import org.springframework.http.MediaType;
    import org.springframework.web.multipart.MultipartFile;
    
    import java.io.IOException;
    import java.util.Set;
    
    public class MultiPartFileValidator implements ConstraintValidator<ValidFileType, MultipartFile> {
    
        private static final Set<String> ALLOWED_FILETYPES =
                Set.of(MediaType.IMAGE_PNG_VALUE, MediaType.IMAGE_JPEG_VALUE);
    
        private static final Tika TIKA = new Tika();
    
        private long maxFileSize;
        private String invalidFileTypeMessage;
        private String invalidSizeMessage;
    
        @Override
        public void initialize(ValidFileType constraintAnnotation) {
            this.maxFileSize = constraintAnnotation.maxFileSize();
            this.invalidFileTypeMessage = constraintAnnotation.invalidFileTypeMessage();
            this.invalidSizeMessage = constraintAnnotation.invalidSizeMessage();
        }
    
        @Override
        public boolean isValid(MultipartFile file, ConstraintValidatorContext context) {
            // Null or empty check upfront
            if (file == null) {
                return true;
            }
    
            context.disableDefaultConstraintViolation();
    
            // Size check: Ensure the file is within the allowed size limit
            if (file.getSize() > maxFileSize) {
                return setValidationError(invalidSizeMessage, context);
            }
    
            // Validate file type
            try {
                // other overloads of detect exist
                String mimeType = TIKA.detect(file.getBytes());
                if (!ALLOWED_FILETYPES.contains(mimeType)) {
                    return setValidationError(invalidFileTypeMessage, context);
                }
            } catch (IOException e) {
                // Handle error if file type detection fails
                return setValidationError("Unable to detect file type", context);
            }
    
            // If both size and type are valid
            return true;
        }
    
        private static boolean setValidationError(String message, ConstraintValidatorContext context) {
            context.buildConstraintViolationWithTemplate(message).addConstraintViolation();
            return false;
        }
    }