CVE-2026-59280:SpringTemplateLoader 路径穿越漏洞浅析
Spring官方近期披露了CVE-2026-59280,在受影响版本中,若应用将 Controller 返回的视图名直接拼入 FreeMarker 视图解析流程时,攻击者可在视图名中注入包含反斜杠的路径序列(例如 ..\..\etc\passwd),借助底层文件 ResourceLoader 的反斜杠语义突破模板根目录,读到模板目录之外本不应被访问的本地文件。
0x00 CVE-2026-59280 =================== Spring Framework 中的 spring-context-support 模块提供了FreeMarker 集成,通过 SpringTemplateLoader 让 FreeMarker 模板引擎经由 Spring ResourceLoader 解析模板路径。 在受影响版本中,若应用将 Controller 返回的视图名直接拼入 FreeMarker 视图解析流程时,攻击者可在视图名中注入包含反斜杠的路径序列(例如 ..\\..\\etc\\passwd),借助底层文件 ResourceLoader 的反斜杠语义突破模板根目录,读到模板目录之外本不应被访问的本地文件。  **影响范围:** Spring Framework: - 7.0.0 - 7.0.8 - 6.2.0 - 6.2.19 - 6.1.0 - 6.1.28 - 6.0.0 - 6.0.30 - 5.3.0 - 5.3.49 - 5.2.25 and earlier 0x01 漏洞分析与复现 ============ 1.1 模版解析过程与漏洞原理 --------------- 通告里提到了漏洞与Freemarker有关,默认情况下Spring并不会直接使用Freemarker进行解析,需要引入相关的依赖:  下面看看具体的解析过程, 首先是FreeMarkerViewResolver#loadView,其在 Spring 加载完 FreeMarker 视图对象后,会调用父类逻辑,完成视图的标准加载:  在loadView方法中,首先会调用buildView进行处理:  这里主要是视图构建的地方,包括预定义的前缀后缀的拼接等:  然后会调用checkResource方法进行处理,这里会判断对应的模版文件是否存在,主要是通过 getTemplate方法处理:  `TemplateCache` 会先对模板名做规范化/检查:  主要是在normalizeRootBasedName实现的,主要是通过按 `/` 切分并处理 `../`,然后以`../`开头会直接抛异常:  随即将解析后的文件名字符,透传给 TemplateLoader,然后通过lookupTemplate方法处理:  在处理过程中,会调用SpringTemplateLoader#findTemplateSource方法:  这里会进行一系列的解析,然后创建ClassPathResource:  在ClassPathResource的创建时,会调用cleanPath方法对路径进一步处理:  在cleanPath中,有这么一段逻辑,首先会把 `\`(Windows 分隔符)统一替换成 `/`:  然后对类似`../`的内容进行处理回溯:  到这里其实漏洞原因就理顺了,虽然FreeMarker在处理时会检查是否包含类似`../`的情况,但是因为没有考虑`\`的情况下,类似`\..`在原样透传给SpringTemplateLoader时在StringUtils#cleanPath方法得到了还原,从而造成了目录穿越的可能。 结合修复commit <https://github.com/spring-projects/spring-framework/commit/bb65d819a4ecbfd297b46165fa5fe929a384de47> ,确实也是对`\`进行了处理,如果透传过来的name包含`\`,则直接返回内容为null:  1.2 漏洞复现 -------- 根据上面的分析,这里直接定义一个Controller进行验证: ```TypeScript @GetMapping("/preview") public String preview(@RequestParam String page, Model model) { model.addAttribute("theme", "preview"); return page; } ``` 因为没有做任何的过滤,根据前面的分析以及当前项目的目录接口,尝试读取application.properties文件:  根据前面的分析,直接采用`../`的方式尝试读取会返回500 status:  使用`\`成功目录穿越读取到properties里的内容:  此外,默认情况下下`classpath:`加载走的是 ClassLoader 资源查找而不是文件系统读取,是没办法读取classpath 之外的 /etc/passwd的。除非模板路径配成 file协议形式(如 spring.freemarker.template-loader-path=file:/opt/app/templates/),这时会通过FileSystemResource按真实文件系统解析,在特定情况下,..%5c..%5c..%5c..%5cetc%5cpasswd 可以逐级回溯读取到对应的敏感文件了。 1.3 RCE/SSTI ------------ 触发时需要应用先把不可信输入拼进视图名,条件还是比较苛刻的,但是一些主题切换、多语言、动态页面这类需求在真实业务里并不少见,很多时候研发为了方便,直接就进行视图名拼接了。 若攻击者同时还能控制被读文件的部分内容(如日志、上传文件), 若文件被当作模板解析意味着 FreeMarker 指令会被执行,还有可能造成SSTI/RCE的风险。 下面看一个具体的例子: 一般市场活动、节日皮肤经常会有切换主题的功能,上述接口主要传入对应的参数,即可完成对应的切换,十分方便。 ```TypeScript @GetMapping("/portal") public String portal(@RequestParam(defaultValue = "default") String theme, Model model) { model.addAttribute("theme", theme); return "themes/" + theme + "/home"; } ``` 相关的模版目录如下: ```Java src/main/resources/ ├── templates/themes/default/home.ftlh 默认主题(正常页面) ├── templates/themes/dark/home.ftlh 深色主题(正常页面) ``` 默认情况下,访问portal接口,返回的是默认主题:  传入theme=dart后,即可切换为深色主题,使用起来十分方便:  这里肯定是存在风险的,但是一般都会在application.properties 中 spring.freemarker.suffix=进行配置, 限制了home结尾只能是ftlh,这样也只能读取到任意目录下的 home.ftlh,危害有限。 但是一般系统还可能会带一个草稿箱的功能, 提交的页面草稿**按模板格式原样存为对应的模版文件**,审核发布后才会上线。 但是一般系统对草稿内容不做任何过滤。 攻击者提交含 FreeMarker 指令的草稿,再通过相关的接口强制渲染对应的模版文件, 存储的"数据"立刻变成被执行的"恶意模板"。 模拟下具体的利用过程,继续沿用漏洞复现时主题切换的接口: ```TypeScript @GetMapping("/portal") public String portal(@RequestParam(defaultValue = "default") String theme, Model model) { model.addAttribute("theme", theme) return "themes/" + theme + "/home"; } ``` 草稿模版相关功能: ```Java @PostMapping("/draft") public Map<String, Object> save(@RequestParam String author, @RequestParam String content) throws IOException { String body = "【草稿】提交人: " + author + "\n================================\n" + content + "\n"; Files.writeString(draftsDir().resolve("home.ftlh"), body, StandardCharsets.UTF_8); return Map.of("saved", "drafts/home.ftlh", "note", "草稿仅存储待审,发布前不会被渲染"); } /** 草稿目录:classpath 根下的 drafts/(需要展开运行才可在运行期写入) */ private Path draftsDir() throws IOException { try { URL root = DemoApplication.class.getResource("/"); Path dir = Path.of(root.toURI()).resolve("drafts"); Files.createDirectories(dir); return dir; } catch (IllegalStateException ex) { throw ex; } catch (Exception ex) { throw new IOException("无法定位草稿目录", ex); } } ``` 首先调用draft接口保存草稿,在内容里通过freemarker.template.utility.Execute尝试执行id命令:  结合之前主题切换的风险接口,FreeMarker 会把草稿内容当模板解析,Execute 指令被成功执行:  0x02 修复方式 ========= - 不要把请求输入拼进视图名,动态视图可以通过白名单的方式进行枚举 - 升级到修复版本 - 对参与视图名拼接的参数拒绝包含类似 `\`、`..`、`:` 的输入
发表于 2026-10-12 10:02:55
阅读 ( 6705 )
分类:
漏洞分析
0 推荐
收藏
0 条评论
tkswifty
81 篇文章
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!