文件目录扫描漏洞

This commit is contained in:
JEECG 2026-01-26 15:06:33 +08:00
parent 1936f503df
commit 360f5d779a
4 changed files with 46 additions and 2 deletions

View File

@ -286,5 +286,38 @@ public class SsrfFileTypeFilter {
}
}
/**
* 校验文件路径安全性防止路径遍历攻击
* @param filePath 文件路径
*/
public static void checkPathTraversal(String filePath) {
if (StringUtils.isBlank(filePath)) {
return;
}
// 1. 防止路径遍历不允许 ..
if (filePath.contains("..")) {
throw new JeecgBootException("文件路径包含非法字符");
}
// 2. 防止URL编码绕过%2e = .
String fileLower = filePath.toLowerCase();
if (fileLower.contains("%2e")) {
throw new JeecgBootException("文件路径包含非法字符");
}
}
/**
* 批量校验文件路径安全性逗号分隔的多个文件路径
* @param files 逗号分隔的文件路径
*/
public static void checkPathTraversalBatch(String files) {
if (StringUtils.isBlank(files)) {
return;
}
for (String file : files.split(",")) {
if (StringUtils.isNotBlank(file)) {
checkPathTraversal(file.trim());
}
}
}
}

View File

@ -11,6 +11,7 @@ import lombok.extern.slf4j.Slf4j;
import org.jeecg.ai.handler.LLMHandler;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.util.AssertUtils;
import org.jeecg.common.util.filter.SsrfFileTypeFilter;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.airag.common.consts.AiragConsts;
import org.jeecg.modules.airag.common.handler.AIChatParams;
@ -401,6 +402,7 @@ public class AIChatHandler implements IAIChatHandler {
String filePath = uploadpath + File.separator + imageUrl;
// 读取文件并转换为 base64 编码字符串
try {
SsrfFileTypeFilter.checkPathTraversal(filePath);
Path path = Paths.get(filePath);
byte[] fileContent = Files.readAllBytes(path);
String base64Data = Base64.getEncoder().encodeToString(fileContent);
@ -409,7 +411,7 @@ public class AIChatHandler implements IAIChatHandler {
// 构建 ImageContent 对象
imageContents.add(ImageContent.from(base64Data, mimeType));
} catch (IOException e) {
log.error("读取文件失败: " + filePath, e);
log.error("读取文件失败: {}", imageUrl, e);
throw new RuntimeException("发送消息失败,读取文件异常:" + e.getMessage(), e);
}
}
@ -529,12 +531,13 @@ public class AIChatHandler implements IAIChatHandler {
} else {
// 本地文件
String filePath = uploadpath + File.separator + imageUrl;
SsrfFileTypeFilter.checkPathTraversal(filePath);
Path path = Paths.get(filePath);
fileContent = Files.readAllBytes(path);
}
originalImageBase64List.add(Base64.getEncoder().encodeToString(fileContent));
} catch (Exception e) {
log.error("图片读取失败: " + imageUrl, e);
log.error("图片读取失败: {}", imageUrl, e);
throw new JeecgBootException("图片读取失败: " + imageUrl);
}
}

View File

@ -20,6 +20,7 @@ import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.*;
import org.jeecg.common.util.filter.SsrfFileTypeFilter;
import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
import org.jeecg.modules.message.enums.RangeDateEnum;
import org.jeecg.modules.message.websocket.WebSocket;
@ -142,6 +143,8 @@ public class SysAnnouncementController {
// 代码逻辑说明: 标题处理xss攻击的问题
String title = XssUtils.scriptXss(sysAnnouncement.getTitile());
sysAnnouncement.setTitile(title);
// 安全校验校验附件文件名防止路径遍历攻击
SsrfFileTypeFilter.checkPathTraversalBatch(sysAnnouncement.getFiles());
sysAnnouncement.setDelFlag(CommonConstant.DEL_FLAG_0.toString());
//未发布
sysAnnouncement.setSendStatus(CommonSendStatus.UNPUBLISHED_STATUS_0);
@ -173,6 +176,8 @@ public class SysAnnouncementController {
// 代码逻辑说明: 标题处理xss攻击的问题
String title = XssUtils.scriptXss(sysAnnouncement.getTitile());
sysAnnouncement.setTitile(title);
// 安全校验校验附件文件名防止路径遍历攻击
SsrfFileTypeFilter.checkPathTraversalBatch(sysAnnouncement.getFiles());
sysAnnouncement.setNoticeType(NoticeTypeEnum.NOTICE_TYPE_SYSTEM.getValue());
boolean ok = sysAnnouncementService.upDateAnnouncement(sysAnnouncement);
//TODO 返回false说明什么

View File

@ -12,6 +12,7 @@ import org.apache.shiro.SecurityUtils;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.FileDownloadUtils;
import org.jeecg.common.util.filter.SsrfFileTypeFilter;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.config.JeecgBaseConfig;
import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
@ -303,6 +304,8 @@ public class SysAnnouncementServiceImpl extends ServiceImpl<SysAnnouncementMappe
if (oConvertUtils.isEmpty(fileUrl)) {
continue;
}
// 安全校验防止路径遍历攻击
SsrfFileTypeFilter.checkPathTraversal(fileUrl);
// 生成ZIP内文件名避免重名添加序号
String fileName = FileDownloadUtils.generateFileName(fileUrl, i, fileUrls.length);
String uploadUrl = jeecgBaseConfig.getPath().getUpload();