mirror of
http://git.pig4cloud.com/pig/pigx.git
synced 2026-11-08 04:39:00 +08:00
fix(upms): 本地文件存储增加路径穿越防护
上传/读取/删除前校验路径位于存储根目录内,统一错误文案,并修复无扩展名时多余的点号。
This commit is contained in:
1 parent
6248d60e62
commit
d1876914d2
4 files changed
+77
-2
No files matched your search
+5
@@ -74,6 +74,11 @@ public interface UpmsErrorCodes {
|
||||
|
||||
String SYS_DICT_ITEM_SORT_SCOPE_INVALID = "sys.dict.item.sort.scopeInvalid";
|
||||
|
||||
// === 文件相关 ===
|
||||
String SYS_FILE_PATH_INVALID = "sys.file.path.invalid";
|
||||
|
||||
String SYS_FILE_UPLOAD_FAILED = "sys.file.upload.failed";
|
||||
|
||||
// === 参数相关 ===
|
||||
String SYS_PARAM_CONFIG_ERROR = "sys.param.config.error";
|
||||
|
||||
|
||||
@@ -42,6 +42,10 @@ sys.dict.notFound=Dictionary does not exist
|
||||
sys.dict.item.sort.duplicate=Dictionary item ID list cannot contain duplicates
|
||||
sys.dict.item.sort.scopeInvalid=Dictionary item sort data does not belong to the current dictionary
|
||||
|
||||
# File related
|
||||
sys.file.path.invalid=Invalid file path
|
||||
sys.file.upload.failed=File upload failed
|
||||
|
||||
# Parameter related
|
||||
sys.param.config.error=System parameter configuration error: {0}
|
||||
sys.param.delete.system=Built-in system parameters cannot be deleted
|
||||
|
||||
@@ -42,6 +42,10 @@ sys.dict.notFound=\u5B57\u5178\u4E0D\u5B58\u5728
|
||||
sys.dict.item.sort.duplicate=\u5B57\u5178\u9879ID\u5217\u8868\u4E0D\u80FD\u91CD\u590D
|
||||
sys.dict.item.sort.scopeInvalid=\u5B57\u5178\u9879\u6392\u5E8F\u6570\u636E\u4E0D\u5C5E\u4E8E\u5F53\u524D\u5B57\u5178
|
||||
|
||||
# 文件相关
|
||||
sys.file.path.invalid=文件路径不合法
|
||||
sys.file.upload.failed=文件上传失败
|
||||
|
||||
# 参数相关
|
||||
sys.param.config.error={0} \u7CFB\u7EDF\u53C2\u6570\u914D\u7F6E\u9519\u8BEF
|
||||
sys.param.delete.system=\u7CFB\u7EDF\u5185\u7F6E\u53C2\u6570\u4E0D\u80FD\u5220\u9664
|
||||
|
||||
+64
-2
@@ -30,6 +30,7 @@ import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.pig4cloud.pigx.admin.api.constant.UpmsErrorCodes;
|
||||
import com.pig4cloud.pigx.admin.api.dto.SysFileGroupDTO;
|
||||
import com.pig4cloud.pigx.admin.api.entity.SysFile;
|
||||
import com.pig4cloud.pigx.admin.api.entity.SysFileGroup;
|
||||
@@ -37,11 +38,13 @@ import com.pig4cloud.pigx.admin.mapper.SysFileGroupMapper;
|
||||
import com.pig4cloud.pigx.admin.mapper.SysFileMapper;
|
||||
import com.pig4cloud.pigx.admin.service.SysFileService;
|
||||
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
|
||||
import com.pig4cloud.pigx.common.core.util.MsgUtils;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder;
|
||||
import com.pig4cloud.pigx.common.file.core.FileObject;
|
||||
import com.pig4cloud.pigx.common.file.core.FileProperties;
|
||||
import com.pig4cloud.pigx.common.file.core.FileTemplate;
|
||||
import com.pig4cloud.pigx.common.file.core.FileType;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
@@ -51,6 +54,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.*;
|
||||
@@ -86,8 +90,17 @@ public class SysFileServiceImpl extends ServiceImpl<SysFileMapper, SysFile> impl
|
||||
public R uploadFile(MultipartFile file, String fileName, String dir, Long groupId, String type) {
|
||||
// 如果客户端没有传入文件名,则使用UUID生成一个唯一的文件名
|
||||
if (StrUtil.isBlank(fileName)) {
|
||||
fileName = IdUtil.simpleUUID() + StrUtil.DOT + FileUtil.extName(file.getOriginalFilename());
|
||||
String extension = FileUtil.extName(file.getOriginalFilename());
|
||||
fileName = IdUtil.simpleUUID() + (StrUtil.isBlank(extension) ? StrUtil.EMPTY : StrUtil.DOT + extension);
|
||||
}
|
||||
|
||||
try {
|
||||
validateStoragePath(properties.getBucketName(), dir, fileName);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
log.warn("拒绝包含非法路径的文件上传请求");
|
||||
return R.failed(MsgUtils.getMessage(UpmsErrorCodes.SYS_FILE_PATH_INVALID));
|
||||
}
|
||||
|
||||
Map<String, String> resultMap = new HashMap<>(4);
|
||||
resultMap.put("bucketName", properties.getBucketName());
|
||||
resultMap.put("fileName", fileName);
|
||||
@@ -100,7 +113,7 @@ public class SysFileServiceImpl extends ServiceImpl<SysFileMapper, SysFile> impl
|
||||
fileLog(file, dir, fileName, groupId, type);
|
||||
} catch (Exception e) {
|
||||
log.error("上传失败", e);
|
||||
return R.failed(e.getLocalizedMessage());
|
||||
return R.failed(MsgUtils.getMessage(UpmsErrorCodes.SYS_FILE_UPLOAD_FAILED));
|
||||
}
|
||||
return R.ok(resultMap);
|
||||
}
|
||||
@@ -121,6 +134,14 @@ public class SysFileServiceImpl extends ServiceImpl<SysFileMapper, SysFile> impl
|
||||
fileName = urlFileName;
|
||||
}
|
||||
|
||||
try {
|
||||
validateStoragePath(properties.getBucketName(), null, fileName);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
log.warn("拒绝包含非法路径的文件读取请求");
|
||||
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
return;
|
||||
}
|
||||
|
||||
SysFile sysFile = baseMapper.selectOne(Wrappers.<SysFile>lambdaQuery().eq(SysFile::getFileName, fileName),
|
||||
false);
|
||||
if (Objects.isNull(sysFile)) {
|
||||
@@ -128,6 +149,14 @@ public class SysFileServiceImpl extends ServiceImpl<SysFileMapper, SysFile> impl
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
validateStoragePath(sysFile.getBucketName(), sysFile.getDir(), sysFile.getFileName());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
log.warn("拒绝读取包含非法存储路径的文件记录");
|
||||
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
return;
|
||||
}
|
||||
|
||||
try (FileObject fileObject = fileTemplate.getObject(sysFile.getBucketName(), sysFile.getDir(), fileName)) {
|
||||
response.setContentType("application/octet-stream; charset=UTF-8");
|
||||
// 文件唯一hash
|
||||
@@ -154,6 +183,14 @@ public class SysFileServiceImpl extends ServiceImpl<SysFileMapper, SysFile> impl
|
||||
if (Objects.isNull(file)) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
|
||||
try {
|
||||
validateStoragePath(properties.getBucketName(), null, file.getFileName());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
log.warn("拒绝删除包含非法存储路径的文件记录");
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
|
||||
fileTemplate.removeObject(properties.getBucketName(), file.getFileName());
|
||||
return this.removeById(id);
|
||||
}
|
||||
@@ -258,4 +295,29 @@ public class SysFileServiceImpl extends ServiceImpl<SysFileMapper, SysFile> impl
|
||||
this.save(sysFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验文件存储路径,确保文件始终位于配置的存储桶目录内。
|
||||
*
|
||||
* @param bucketName 存储桶名称
|
||||
* @param dir 相对目录
|
||||
* @param fileName 文件名
|
||||
*/
|
||||
private void validateStoragePath(String bucketName, String dir, String fileName) {
|
||||
if (properties.getType() != FileType.LOCAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
String basePath = properties.getLocal().getBasePath();
|
||||
if (StrUtil.isBlank(basePath)) {
|
||||
throw new IllegalArgumentException(MsgUtils.getMessage(UpmsErrorCodes.SYS_FILE_PATH_INVALID));
|
||||
}
|
||||
|
||||
File storageRoot = new File(basePath);
|
||||
File bucketRoot = new File(storageRoot, bucketName);
|
||||
File targetFile = StrUtil.isBlank(dir) ? new File(bucketRoot, fileName)
|
||||
: new File(new File(bucketRoot, dir), fileName);
|
||||
FileUtil.checkSlip(storageRoot, bucketRoot);
|
||||
FileUtil.checkSlip(bucketRoot, targetFile);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user