{"record":{"id":"20fd85ef03f16905","repo":"jeecgboot/JeecgBoot","slug":"storepath","errorCode":null,"errorMessage":"非法存储路径，路径包含遍历字符: {storePath}","messagePattern":"非法存储路径，路径包含遍历字符: (.+?)","errorType":"validation","errorClass":"JeecgBootException","httpStatus":null,"severity":"error","filePath":"jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/FileDownloadUtils.java","lineNumber":141,"sourceCode":"    }\n\n    /**\n     * 下载网络资源到磁盘\n     *\n     * @param fileUrl\n     * @param storePath\n     * @author chenrui\n     * @date 2024/1/19 10:09\n     */\n    public static String download2DiskFromNet(String fileUrl, String storePath) {\n        //update-begin---author:liusq ---date:2026-03-30  for：【issues/9437】修复download2DiskFromNet storePath路径遍历漏洞(CWE-22)-----------\n        // 路径遍历校验：拦截 ../ 等遍历字符，并确保规范化路径与原始路径一致\n        SsrfFileTypeFilter.checkPathTraversal(storePath);\n        try {\n            String canonicalPath = new File(storePath).getCanonicalPath();\n            String absolutePath = new File(storePath).getAbsolutePath();\n            if (!canonicalPath.equals(absolutePath)) {\n                throw new JeecgBootException(\"非法存储路径，路径包含遍历字符: \" + storePath);\n            }\n        } catch (IOException e) {\n            throw new JeecgBootException(\"存储路径校验失败: \" + storePath, e);\n        }\n        //update-end---author:liusq ---date:2026-03-30  for：【issues/9437】修复download2DiskFromNet storePath路径遍历漏洞(CWE-22)-----------\n        //update-begin---author:zhangdaihao ---date:2026-04-15  for：【issues/9553】下载网络资源前增加SSRF校验-----------\n        SsrfFileTypeFilter.checkSsrfHttpUrl(fileUrl);\n        //update-end---author:zhangdaihao ---date:2026-04-15  for：【issues/9553】下载网络资源前增加SSRF校验-----------\n        try {\n            URL url = new URL(fileUrl);\n            URLConnection conn = url.openConnection();\n            // 设置超时间为3秒\n            conn.setConnectTimeout(3 * 1000);\n            // 防止屏蔽程序\n            conn.setRequestProperty(\"User-Agent\", \"Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)\");\n            // 确保目录存在\n            File file = ensureDestFileDir(storePath);\n            try (InputStream inStream = conn.getInputStream();","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/jeecgboot/JeecgBoot/blob/96fb33f5ec68516da0b0147da06b2eb0419e063a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/FileDownloadUtils.java#L123-L159","documentation":"Thrown by FileDownloadUtils.download2DiskFromNet when the computed canonical path of storePath does not equal its absolute path. This is a CWE-22 path-traversal guard added for issue 9437: it runs after SsrfFileTypeFilter.checkPathTraversal and confirms the path contains no '../' traversal or symlink resolution that escapes the intended directory. The JeecgBootException signals the caller passed an unsafe storage path.","triggerScenarios":"Calling FileDownloadUtils.download2DiskFromNet(fileUrl, storePath) where storePath contains '../', '.' segments, or resolves through symlinks so that new File(storePath).getCanonicalPath() differs from getAbsolutePath(). Any storePath sourced from user input or request parameters without sanitization.","commonSituations":"Upload/download directory configured with a relative path containing '..'; storage base directory is a symlink; storePath is built by concatenating untrusted user input (filename from upload) onto a base dir without normalization; path constructed on Windows with case differences that make canonical != absolute.","solutions":["Pass a clean, pre-normalized absolute path as storePath — compute it with Paths.get(baseDir, fileName).normalize().toAbsolutePath() and confirm it startsWith(baseDir) before calling download2DiskFromNet.","Strip any '../' or '.\\' sequences from the storePath and re-derive it from a trusted base directory constant.","If the mismatch is caused by a symlinked storage volume, point the storage config at the real (canonical) path so absolute == canonical.","At the calling controller, validate the storePath/fileName with a whitelist regex (e.g. no path separators in the filename portion) before it reaches this method."],"exampleFix":"// before\nString storePath = uploadDir + \"/\" + request.getParameter(\"fileName\");\nFileDownloadUtils.download2DiskFromNet(fileUrl, storePath);\n\n// after\nPath base = Paths.get(uploadDir).toAbsolutePath().normalize();\nPath resolved = base.resolve(request.getParameter(\"fileName\")).normalize();\nif (!resolved.startsWith(base)) {\n    throw new IllegalArgumentException(\"Invalid file path\");\n}\nFileDownloadUtils.download2DiskFromNet(fileUrl, resolved.toString());","handlingStrategy":"validation","validationCode":"Path base = Paths.get(uploadDir).toAbsolutePath().normalize();\nPath resolved = base.resolve(fileName).normalize();\nif (!resolved.startsWith(base)) {\n    throw new IllegalArgumentException(\"storePath escapes base directory: \" + resolved);\n}\nFileDownloadUtils.download2DiskFromNet(fileUrl, resolved.toString());","typeGuard":"null","tryCatchPattern":"try {\n    FileDownloadUtils.download2DiskFromNet(fileUrl, resolved.toString());\n} catch (JeecgBootException e) {\n    log.warn(\"下载失败，路径校验未通过: {}\", e.getMessage());\n    return Result.error(\"文件路径不合法\");\n}","preventionTips":["Never build storePath by concatenating raw request parameters onto a base directory.","Always normalize and confine paths under a known base directory before file operations.","Configure storage directories as absolute paths to avoid symlink/canonical divergence."],"tags":["security","path-traversal","cwe-22","file-download","validation"],"backgroundTag":null,"analyzedSha":"96fb33f5ec68516da0b0147da06b2eb0419e063a","analyzedAt":"2026-08-14T00:04:16.786Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}