mirror of
https://github.com/jeecgboot/jeecg-boot.git
synced 2025-10-13 14:29:18 +00:00
Path Traversal Vulnerability /sys/comment/addFile /sys/upload/uploadMinio endpoint (notice the uploadlocal function is different from the /sys/common/upload ) #8827
This commit is contained in:
@@ -153,9 +153,9 @@ public class CommonUtils {
|
||||
*/
|
||||
public static String uploadLocal(MultipartFile mf,String bizPath,String uploadpath){
|
||||
try {
|
||||
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
SsrfFileTypeFilter.checkUploadFileType(mf);
|
||||
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
// 文件安全校验,防止上传漏洞文件
|
||||
SsrfFileTypeFilter.checkUploadFileType(mf, bizPath);
|
||||
|
||||
String fileName = null;
|
||||
File file = new File(uploadpath + File.separator + bizPath + File.separator );
|
||||
if (!file.exists()) {
|
||||
|
@@ -55,13 +55,11 @@ public class MinioUtil {
|
||||
*/
|
||||
public static String upload(MultipartFile file, String bizPath, String customBucket) throws Exception {
|
||||
String fileUrl = "";
|
||||
//update-begin-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
|
||||
// 业务路径过滤,防止攻击
|
||||
bizPath = StrAttackFilter.filter(bizPath);
|
||||
//update-end-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
|
||||
|
||||
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
SsrfFileTypeFilter.checkUploadFileType(file);
|
||||
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
// 文件安全校验,防止上传漏洞文件
|
||||
SsrfFileTypeFilter.checkUploadFileType(file, bizPath);
|
||||
|
||||
String newBucket = bucketName;
|
||||
if(oConvertUtils.isNotEmpty(customBucket)){
|
||||
|
@@ -2,6 +2,7 @@ package org.jeecg.common.util.filter;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -149,29 +150,38 @@ public class SsrfFileTypeFilter {
|
||||
public static void checkDownloadFileType(String filePath) throws IOException {
|
||||
//文件后缀
|
||||
String suffix = getFileTypeBySuffix(filePath);
|
||||
log.info("suffix:{}", suffix);
|
||||
log.debug(" 【文件下载校验】文件后缀 suffix: {}", suffix);
|
||||
boolean isAllowExtension = FILE_TYPE_WHITE_LIST.contains(suffix.toLowerCase());
|
||||
//是否允许下载的文件
|
||||
if (!isAllowExtension) {
|
||||
throw new IOException("下载失败,存在非法文件类型:" + suffix);
|
||||
throw new JeecgBootException("下载失败,存在非法文件类型:" + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 上传文件类型过滤
|
||||
*
|
||||
* @param file
|
||||
*/
|
||||
public static void checkUploadFileType(MultipartFile file) throws Exception {
|
||||
//获取文件真是后缀
|
||||
String suffix = getFileType(file);
|
||||
|
||||
log.info("suffix:{}", suffix);
|
||||
checkUploadFileType(file, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件类型过滤
|
||||
*
|
||||
* @param file
|
||||
*/
|
||||
public static void checkUploadFileType(MultipartFile file, String customPath) throws Exception {
|
||||
//1. 路径安全校验
|
||||
validatePathSecurity(customPath);
|
||||
//2. 校验文件后缀和头
|
||||
String suffix = getFileType(file, customPath);
|
||||
log.info("【文件上传校验】文件后缀 suffix: {},customPath:{}", suffix, customPath);
|
||||
boolean isAllowExtension = FILE_TYPE_WHITE_LIST.contains(suffix.toLowerCase());
|
||||
//是否允许下载的文件
|
||||
if (!isAllowExtension) {
|
||||
throw new Exception("上传失败,存在非法文件类型:" + suffix);
|
||||
throw new JeecgBootException("上传失败,存在非法文件类型:" + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +193,7 @@ public class SsrfFileTypeFilter {
|
||||
* @throws Exception
|
||||
*/
|
||||
|
||||
private static String getFileType(MultipartFile file) throws Exception {
|
||||
private static String getFileType(MultipartFile file, String customPath) throws Exception {
|
||||
//update-begin-author:liusq date:20230404 for: [issue/4672]方法造成的文件被占用,注释掉此方法tomcat就能自动清理掉临时文件
|
||||
String fileExtendName = null;
|
||||
InputStream is = null;
|
||||
@@ -203,7 +213,7 @@ public class SsrfFileTypeFilter {
|
||||
break;
|
||||
}
|
||||
}
|
||||
log.info("-----获取到的指定文件类型------"+fileExtendName);
|
||||
log.debug("-----获取到的指定文件类型------"+fileExtendName);
|
||||
// 如果不是上述类型,则判断扩展名
|
||||
if (StringUtils.isBlank(fileExtendName)) {
|
||||
String fileName = file.getOriginalFilename();
|
||||
@@ -214,7 +224,6 @@ public class SsrfFileTypeFilter {
|
||||
// 如果有扩展名,则返回扩展名
|
||||
return getFileTypeBySuffix(fileName);
|
||||
}
|
||||
log.info("-----最終的文件类型------"+fileExtendName);
|
||||
is.close();
|
||||
return fileExtendName;
|
||||
} catch (Exception e) {
|
||||
@@ -249,4 +258,34 @@ public class SsrfFileTypeFilter {
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径安全校验
|
||||
*/
|
||||
private static void validatePathSecurity(String customPath) throws JeecgBootException {
|
||||
if (customPath == null || customPath.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 统一分隔符为 /
|
||||
String normalized = customPath.replace("\\", "/");
|
||||
|
||||
// 1. 防止路径遍历攻击
|
||||
if (normalized.contains("..") || normalized.contains("~")) {
|
||||
throw new JeecgBootException("上传业务路径包含非法字符!");
|
||||
}
|
||||
|
||||
// 2. 限制路径深度
|
||||
int depth = normalized.split("/").length;
|
||||
if (depth > 5) {
|
||||
throw new JeecgBootException("上传业务路径深度超出限制!");
|
||||
}
|
||||
|
||||
// 3. 限制字符集(只允许字母、数字、下划线、横线、斜杠)
|
||||
if (!normalized.matches("^[a-zA-Z0-9/_-]+$")) {
|
||||
throw new JeecgBootException("上传业务路径包含非法字符!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -97,9 +97,8 @@ public class OssBootUtil {
|
||||
* @return oss 中的相对文件路径
|
||||
*/
|
||||
public static String upload(MultipartFile file, String fileDir,String customBucket) throws Exception {
|
||||
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
// 文件安全校验,防止上传漏洞文件
|
||||
SsrfFileTypeFilter.checkUploadFileType(file);
|
||||
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
|
||||
String filePath = null;
|
||||
initOss(endPoint, accessKeyId, accessKeySecret);
|
||||
|
@@ -68,50 +68,19 @@ public class CommonController {
|
||||
Result<?> result = new Result<>();
|
||||
String savePath = "";
|
||||
String bizPath = request.getParameter("biz");
|
||||
|
||||
//LOWCOD-2580 sys/common/upload接口存在任意文件上传漏洞
|
||||
if (oConvertUtils.isNotEmpty(bizPath)) {
|
||||
if(bizPath.contains(SymbolConstant.SPOT_SINGLE_SLASH) || bizPath.contains(SymbolConstant.SPOT_DOUBLE_BACKSLASH)){
|
||||
throw new JeecgBootException("上传目录bizPath,格式非法!");
|
||||
}
|
||||
}
|
||||
|
||||
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
|
||||
// 获取上传文件对象
|
||||
MultipartFile file = multipartRequest.getFile("file");
|
||||
if(oConvertUtils.isEmpty(bizPath)){
|
||||
if(CommonConstant.UPLOAD_TYPE_OSS.equals(uploadType)){
|
||||
//未指定目录,则用阿里云默认目录 upload
|
||||
bizPath = "upload";
|
||||
//result.setMessage("使用阿里云文件上传时,必须添加目录!");
|
||||
//result.setSuccess(false);
|
||||
//return result;
|
||||
}else{
|
||||
bizPath = "";
|
||||
}
|
||||
|
||||
// 文件安全校验,防止上传漏洞文件
|
||||
SsrfFileTypeFilter.checkUploadFileType(file, bizPath);
|
||||
|
||||
if (oConvertUtils.isEmpty(bizPath)) {
|
||||
bizPath = CommonConstant.UPLOAD_TYPE_OSS.equals(uploadType) ? "upload" : "";
|
||||
}
|
||||
if(CommonConstant.UPLOAD_TYPE_LOCAL.equals(uploadType)){
|
||||
//update-begin-author:liusq date:20221102 for: 过滤上传文件类型
|
||||
SsrfFileTypeFilter.checkUploadFileType(file);
|
||||
//update-end-author:liusq date:20221102 for: 过滤上传文件类型
|
||||
//update-begin-author:lvdandan date:20200928 for:修改JEditor编辑器本地上传
|
||||
savePath = this.uploadLocal(file,bizPath);
|
||||
//update-begin-author:lvdandan date:20200928 for:修改JEditor编辑器本地上传
|
||||
/** 富文本编辑器及markdown本地上传时,采用返回链接方式
|
||||
//针对jeditor编辑器如何使 lcaol模式,采用 base64格式存储
|
||||
String jeditor = request.getParameter("jeditor");
|
||||
if(oConvertUtils.isNotEmpty(jeditor)){
|
||||
result.setMessage(CommonConstant.UPLOAD_TYPE_LOCAL);
|
||||
result.setSuccess(true);
|
||||
return result;
|
||||
}else{
|
||||
savePath = this.uploadLocal(file,bizPath);
|
||||
}
|
||||
*/
|
||||
}else{
|
||||
//update-begin-author:taoyan date:20200814 for:文件上传改造
|
||||
savePath = CommonUtils.upload(file, bizPath, uploadType);
|
||||
//update-end-author:taoyan date:20200814 for:文件上传改造
|
||||
}
|
||||
if(oConvertUtils.isNotEmpty(savePath)){
|
||||
result.setMessage(savePath);
|
||||
|
@@ -2,9 +2,9 @@ package org.jeecg.modules.system.controller;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.util.CommonUtils;
|
||||
import org.jeecg.common.util.MinioUtil;
|
||||
import org.jeecg.common.util.filter.SsrfFileTypeFilter;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.oss.entity.OssFile;
|
||||
import org.jeecg.modules.oss.service.IOssFileService;
|
||||
@@ -35,20 +35,18 @@ public class SysUploadController {
|
||||
@PostMapping(value = "/uploadMinio")
|
||||
public Result<?> uploadMinio(HttpServletRequest request) throws Exception {
|
||||
Result<?> result = new Result<>();
|
||||
// 获取业务路径
|
||||
String bizPath = request.getParameter("biz");
|
||||
|
||||
//LOWCOD-2580 sys/common/upload接口存在任意文件上传漏洞
|
||||
boolean flag = oConvertUtils.isNotEmpty(bizPath) && (bizPath.contains("../") || bizPath.contains("..\\"));
|
||||
if (flag) {
|
||||
throw new JeecgBootException("上传目录bizPath,格式非法!");
|
||||
}
|
||||
// 获取上传文件对象
|
||||
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
|
||||
MultipartFile file = multipartRequest.getFile("file");
|
||||
|
||||
// 文件安全校验,防止上传漏洞文件
|
||||
SsrfFileTypeFilter.checkUploadFileType(file, bizPath);
|
||||
|
||||
if(oConvertUtils.isEmpty(bizPath)){
|
||||
bizPath = "";
|
||||
}
|
||||
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
|
||||
// 获取上传文件对象
|
||||
MultipartFile file = multipartRequest.getFile("file");
|
||||
// 获取文件名
|
||||
String orgName = file.getOriginalFilename();
|
||||
orgName = CommonUtils.getFileName(orgName);
|
||||
|
@@ -15,6 +15,7 @@ import org.jeecg.common.system.api.ISysBaseAPI;
|
||||
import org.jeecg.common.system.vo.SysFilesModel;
|
||||
import org.jeecg.common.util.CommonUtils;
|
||||
import org.jeecg.common.util.RedisUtil;
|
||||
import org.jeecg.common.util.filter.SsrfFileTypeFilter;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.system.entity.SysComment;
|
||||
import org.jeecg.modules.system.entity.SysFormFile;
|
||||
@@ -119,28 +120,24 @@ public class SysCommentServiceImpl extends ServiceImpl<SysCommentMapper, SysComm
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public void saveOneFileComment(HttpServletRequest request) {
|
||||
|
||||
//update-begin-author:taoyan date:2023-6-12 for: QQYUN-4310【文件】从文件库选择文件功能未做
|
||||
String existFileId = request.getParameter("fileId");
|
||||
if(oConvertUtils.isEmpty(existFileId)){
|
||||
String savePath = "";
|
||||
// 获取业务路径
|
||||
String bizPath = request.getParameter("biz");
|
||||
//LOWCOD-2580 sys/common/upload接口存在任意文件上传漏洞
|
||||
if (oConvertUtils.isNotEmpty(bizPath)) {
|
||||
if (bizPath.contains(SymbolConstant.SPOT_SINGLE_SLASH) || bizPath.contains(SymbolConstant.SPOT_DOUBLE_BACKSLASH)) {
|
||||
throw new JeecgBootException("上传目录bizPath,格式非法!");
|
||||
}
|
||||
}
|
||||
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
|
||||
// 获取上传文件对象
|
||||
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
|
||||
MultipartFile file = multipartRequest.getFile("file");
|
||||
|
||||
// 文件安全校验,防止上传漏洞文件
|
||||
try {
|
||||
SsrfFileTypeFilter.checkUploadFileType(file, bizPath);
|
||||
} catch (Exception e) {
|
||||
throw new JeecgBootException(e);
|
||||
}
|
||||
|
||||
if (oConvertUtils.isEmpty(bizPath)) {
|
||||
if (CommonConstant.UPLOAD_TYPE_OSS.equals(uploadType)) {
|
||||
//未指定目录,则用阿里云默认目录 upload
|
||||
bizPath = "upload";
|
||||
} else {
|
||||
bizPath = "";
|
||||
}
|
||||
bizPath = CommonConstant.UPLOAD_TYPE_OSS.equals(uploadType) ? "upload" : "";
|
||||
}
|
||||
if (CommonConstant.UPLOAD_TYPE_LOCAL.equals(uploadType)) {
|
||||
savePath = this.uploadLocal(file, bizPath);
|
||||
@@ -348,10 +345,13 @@ public class SysCommentServiceImpl extends ServiceImpl<SysCommentMapper, SysComm
|
||||
* @return
|
||||
*/
|
||||
private String uploadLocal(MultipartFile mf, String bizPath) {
|
||||
//LOWCOD-2580 sys/common/upload接口存在任意文件上传漏洞
|
||||
if (oConvertUtils.isNotEmpty(bizPath) && (bizPath.contains("../") || bizPath.contains("..\\"))) {
|
||||
throw new JeecgBootException("上传目录bizPath,格式非法!");
|
||||
try {
|
||||
// 文件安全校验,防止上传漏洞文件
|
||||
SsrfFileTypeFilter.checkUploadFileType(mf, bizPath);
|
||||
} catch (Exception e) {
|
||||
throw new JeecgBootException(e);
|
||||
}
|
||||
|
||||
try {
|
||||
String ctxPath = uploadpath;
|
||||
String fileName = null;
|
||||
|
Reference in New Issue
Block a user