diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java index d5611b7a06..c763414f78 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java @@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.*; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.validator.ValidDirectoryParam; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; import cn.iocoder.yudao.module.infra.service.file.FileService; import io.swagger.v3.oas.annotations.Operation; @@ -43,7 +44,7 @@ public class FileController { @PostMapping("/upload") @Operation(summary = "上传文件", description = "模式一:后端上传文件") - public CommonResult uploadFile(FileUploadReqVO uploadReqVO) throws Exception { + public CommonResult uploadFile(@Valid FileUploadReqVO uploadReqVO) throws Exception { MultipartFile file = uploadReqVO.getFile(); byte[] content = IoUtil.readBytes(file.getInputStream()); return success(fileService.createFile(content, file.getOriginalFilename(), @@ -58,7 +59,7 @@ public class FileController { }) public CommonResult getFilePresignedUrl( @RequestParam("name") String name, - @RequestParam(value = "directory", required = false) String directory) { + @ValidDirectoryParam @RequestParam(value = "directory", required = false) String directory) { return success(fileService.getFilePresignedUrl(name, directory)); } diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileUploadReqVO.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileUploadReqVO.java index 4096f477e3..56aae23a91 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileUploadReqVO.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileUploadReqVO.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.validator.ValidDirectory; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -14,6 +15,7 @@ public class FileUploadReqVO { private MultipartFile file; @Schema(description = "文件目录", example = "XXX/YYY") + @ValidDirectory(message = "目录路径无效,包含非法字符") private String directory; } diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/validator/DirectoryParamValidator.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/validator/DirectoryParamValidator.java new file mode 100644 index 0000000000..5356a4e5d6 --- /dev/null +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/validator/DirectoryParamValidator.java @@ -0,0 +1,65 @@ +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.validator; + +import cn.hutool.core.util.StrUtil; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +/** + * 目录路径参数验证器实现 + * + * @author 芋道源码 + */ +public class DirectoryParamValidator implements ConstraintValidator { + + @Override + public void initialize(ValidDirectoryParam constraintAnnotation) { + // 初始化方法,如果需要可以在这里进行一些初始化操作 + } + + @Override + public boolean isValid(String directory, ConstraintValidatorContext context) { + // 如果为空,则认为是有效的(可选字段) + if (StrUtil.isEmpty(directory)) { + return true; + } + + // 检查是否包含路径遍历攻击 + return !containsPathTraversal(directory); + } + + /** + * 检查路径是否包含路径遍历攻击 + * + * @param path 路径 + * @return 是否包含路径遍历攻击 + */ + private boolean containsPathTraversal(String path) { + if (StrUtil.isEmpty(path)) { + return false; + } + + // 统一使用正斜杠 + String normalizedPath = path.replace('\\', '/'); + + // 检查常见的路径遍历模式 + String[] dangerousPatterns = { + "..", "..\\", "../", "..%2f", "..%5c", "..%2F", "..%5C", + "%2e%2e", "%2E%2E", "%2e%2e%2f", "%2E%2E%2F", + "....//", "....\\\\", "....%2f", "....%5c" + }; + + String lowerPath = normalizedPath.toLowerCase(); + for (String pattern : dangerousPatterns) { + if (lowerPath.contains(pattern)) { + return true; + } + } + + // 检查绝对路径 + if (normalizedPath.startsWith("/") || normalizedPath.matches("^[A-Za-z]:.*")) { + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/validator/DirectoryValidator.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/validator/DirectoryValidator.java new file mode 100644 index 0000000000..9745c71521 --- /dev/null +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/validator/DirectoryValidator.java @@ -0,0 +1,65 @@ +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.validator; + +import cn.hutool.core.util.StrUtil; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +/** + * 目录路径验证器实现 + * + * @author 芋道源码 + */ +public class DirectoryValidator implements ConstraintValidator { + + @Override + public void initialize(ValidDirectory constraintAnnotation) { + // 初始化方法,如果需要可以在这里进行一些初始化操作 + } + + @Override + public boolean isValid(String directory, ConstraintValidatorContext context) { + // 如果为空,则认为是有效的(可选字段) + if (StrUtil.isEmpty(directory)) { + return true; + } + + // 检查是否包含路径遍历攻击 + return !containsPathTraversal(directory); + } + + /** + * 检查路径是否包含路径遍历攻击 + * + * @param path 路径 + * @return 是否包含路径遍历攻击 + */ + private boolean containsPathTraversal(String path) { + if (StrUtil.isEmpty(path)) { + return false; + } + + // 统一使用正斜杠 + String normalizedPath = path.replace('\\', '/'); + + // 检查常见的路径遍历模式 + String[] dangerousPatterns = { + "..", "..\\", "../", "..%2f", "..%5c", "..%2F", "..%5C", + "%2e%2e", "%2E%2E", "%2e%2e%2f", "%2E%2E%2F", + "....//", "....\\\\", "....%2f", "....%5c" + }; + + String lowerPath = normalizedPath.toLowerCase(); + for (String pattern : dangerousPatterns) { + if (lowerPath.contains(pattern)) { + return true; + } + } + + // 检查绝对路径 + if (normalizedPath.startsWith("/") || normalizedPath.matches("^[A-Za-z]:.*")) { + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/validator/ValidDirectory.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/validator/ValidDirectory.java new file mode 100644 index 0000000000..2ba12e9b8d --- /dev/null +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/validator/ValidDirectory.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.validator; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.*; + +/** + * 目录路径验证注解 + * + * @author 芋道源码 + */ +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Constraint(validatedBy = DirectoryValidator.class) +public @interface ValidDirectory { + + String message() default "目录路径无效,包含非法字符"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} \ No newline at end of file diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/validator/ValidDirectoryParam.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/validator/ValidDirectoryParam.java new file mode 100644 index 0000000000..3dfe00cd9c --- /dev/null +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/validator/ValidDirectoryParam.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.validator; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.*; + +/** + * 目录路径参数验证注解 + * + * @author 芋道源码 + */ +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Constraint(validatedBy = DirectoryParamValidator.class) +public @interface ValidDirectoryParam { + + String message() default "目录路径无效,包含非法字符"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} \ No newline at end of file diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java index 7f85e996d7..495aad9d7a 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java @@ -1,10 +1,11 @@ -package cn.iocoder.yudao.module.infra.controller.app.file; +THIS SHOULD BE A LINTER ERRORpackage cn.iocoder.yudao.module.infra.controller.app.file; import cn.hutool.core.io.IoUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO; import cn.iocoder.yudao.module.infra.controller.app.file.vo.AppFileUploadReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.validator.ValidDirectoryParam; import cn.iocoder.yudao.module.infra.service.file.FileService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -33,7 +34,7 @@ public class AppFileController { @PostMapping("/upload") @Operation(summary = "上传文件") @PermitAll - public CommonResult uploadFile(AppFileUploadReqVO uploadReqVO) throws Exception { + public CommonResult uploadFile(@Valid AppFileUploadReqVO uploadReqVO) throws Exception { MultipartFile file = uploadReqVO.getFile(); byte[] content = IoUtil.readBytes(file.getInputStream()); return success(fileService.createFile(content, file.getOriginalFilename(), diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/vo/AppFileUploadReqVO.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/vo/AppFileUploadReqVO.java index fde120a067..3364607cbe 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/vo/AppFileUploadReqVO.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/vo/AppFileUploadReqVO.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.infra.controller.app.file.vo; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.validator.ValidDirectory; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -14,6 +15,7 @@ public class AppFileUploadReqVO { private MultipartFile file; @Schema(description = "文件目录", example = "XXX/YYY") + @ValidDirectory(message = "目录路径无效,包含非法字符") private String directory; }