<%@ page import="java.net.URLEncoder" %> <%@ page import="java.io.FileInputStream" %> <%@ page import="org.slf4j.Logger" %> <%@ page import="org.slf4j.LoggerFactory" %> <%@ page import="java.nio.file.Paths" %> <%@ page import="java.nio.file.Path" %> <% // 日志初始化 Logger logger = LoggerFactory.getLogger("download.jsp"); // 重置响应,避免乱码或缓存问题 response.reset(); response.setContentType("application/x-download"); // 1. 定义允许的根目录(限制为应用部署目录下的指定子目录,如downloads) String appRoot = application.getRealPath("/"); // 安全增强:强制文件只能从应用内的downloads目录下载(根据实际业务调整) String allowedRoot = appRoot + "downloads" + java.io.File.separator; // 2. 从session获取参数(需校验,即使是session存储也可能被篡改) String basepath = (String) session.getAttribute("basepath"); String filename = (String) session.getAttribute("filename"); // 3. 输入验证:校验basepath和filename是否合法 if (basepath == null || basepath.trim().isEmpty() || filename == null || filename.trim().isEmpty()) { logger.error("文件路径或名称为空,拒绝下载"); response.sendError(400, "无效的请求参数"); return; } // 4. 净化参数:移除危险字符,禁止路径跳转序列 String safeBasepath = basepath.trim().replace("..", "").replace("/", "").replace("\\", ""); String safeFilename = filename.trim().replace("..", "").replace("/", "").replace("\\", ""); // 5. 构建完整路径并规范化(解析相对路径符号) String filedownload = allowedRoot + safeBasepath + java.io.File.separator + safeFilename; Path resolvedPath; Path rootPath; try { resolvedPath = Paths.get(filedownload).toRealPath(); // 规范化路径,消除../等 rootPath = Paths.get(allowedRoot).toRealPath(); // 规范化根目录 } catch (Exception e) { logger.error("路径解析失败,可能存在非法路径: " + filedownload, e); response.sendError(403, "文件访问被拒绝"); return; } // 6. 校验最终路径是否在允许的根目录内(核心防护) if (!resolvedPath.startsWith(rootPath)) { logger.error("检测到路径遍历攻击,尝试访问未授权文件: " + resolvedPath); response.sendError(403, "文件访问被拒绝"); return; } // 7. 设置下载响应头(处理文件名编码) try { response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(safeFilename, "UTF-8")); } catch (Exception e) { logger.error("文件名编码失败", e); response.sendError(500, "服务器处理错误"); return; } // 8. 安全输出文件流 java.io.OutputStream outp = null; java.io.FileInputStream in = null; try { outp = response.getOutputStream(); in = new FileInputStream(resolvedPath.toFile()); // 使用校验后的安全路径 byte[] b = new byte[1024 * 1024]; int i; while ((i = in.read(b)) > 0) { outp.write(b, 0, i); } outp.flush(); // 清理缓冲区,避免 IllegalStateException out.clear(); out = pageContext.pushBody(); } catch (Exception e) { logger.error("文件下载失败", e); response.sendError(500, "文件下载失败"); } finally { if (in != null) { try { in.close(); } catch (Exception e) { logger.error("输入流关闭失败", e); } } // 不关闭输出流,由容器处理 } %>