十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Jekyll 1.5.1 安全修复解析:sanitized_path 路径净化如何阻断 `..c:` 形式的路径穿越攻击

Jekyll 1.5.1 安全修复解析:sanitized_path 路径净化如何阻断 `..c:` 形式的路径穿越攻击 Jekyll 1.5.1 安全修复解析sanitized_path 路径净化如何阻断..c:形式的路径穿越攻击【免费下载链接】jekyll:globe_with_meridians: Jekyll is a blog-aware static site generator in Ruby项目地址: https://gitcode.com/gh_mirrors/je/jekyllJekyll 1.5.12014 年 3 月 27 日发布是一次规模极小却意义重大的安全修复版本其唯一修复点位于Jekyll.sanitized_path——Jekyll 内部用于把任意路径关进站点源目录这一安全边界内的核心净化函数。本篇以官方发布说明 docs/_posts/2014-03-27-jekyll-1-5-1-released.markdown 为主干结合当前仓库的源码实现、单元测试与版本历史完整还原该 Bug 的产生原因、修复原理以及它在现代 Jekyll 中的实现形态与调用场景帮助读者理解路径净化这一安全机制的设计思路与实战写法。一、背景sanitized_path 为什么存在Jekyll 是一个博客感知的静态站点生成器工作方式是把站点源目录source中的 Markdown、HTML、数据文件等内容渲染后输出到目标目录。在此过程中用户可控的输入如 front matter 里的 permalink、include 标签的文件名、--config命令行参数等会被拼接到文件系统路径上。如果这些拼接不做约束攻击者完全可以用../../etc/passwd这类路径穿越path traversal手段让 Jekyll 读取或写出源目录之外的文件。Jekyll.sanitized_path(base_directory, questionable_path)正是为此设计的防线它把可疑路径questionable path强制以前缀方式拼接到基础目录base directory通常是站点 source 目录之下确保任何结果路径都无法逃逸出该目录。这个函数在 lib/jekyll.rb 中作为模块级公开方法暴露# Public: Ensures the questionable path is prefixed with the base directory # and prepends the questionable path with the base directory if false. # # base_directory - the directory with which to prefix the questionable path # questionable_path - the path were unsure about, and want prefixed # # Returns the sanitized path. def sanitized_path(base_directory, questionable_path) return base_directory if base_directory.eql?(questionable_path) return base_directory if questionable_path.nil? Jekyll::PathManager.sanitized_path(base_directory, questionable_path) end从源码可以看到两个边界约定当questionable_path为空nil或与基础目录完全相同时直接返回基础目录本身其余情况则交由 lib/jekyll/path_manager.rb 的PathManager.sanitized_path做真正的净化与拼接。二、Bug 全貌1.5.0 引入的盘符剥离回归发布说明中社区成员 gregose 报告了一个触目惊心的输入输出对 sanitized_path(/tmp/foobar/jail, ..c:/..c:/..c:/etc/passwd) /tmp/foobar/jail/../../../etc/passwd也就是说一个本应被囚禁在/tmp/foobar/jail目录内的净化函数竟然产出了带有../../../的结果路径成功逃逸到了/etc/passwd附近。Jekyll 1.5.1 修复后同样的输入得到的是安全结果 sanitized_path(/tmp/foobar/jail, ..c:/..c:/..c:/etc/passwd) /tmp/foobar/jail/..c:/..c:/..c:/etc/passwd根因把\w:盘符剥离逻辑用在了错误位置要理解这个 Bug必须回顾 1.5.0 的变更。在 History.markdown 中1.5.0 的 Bug Fixes 部分写着Fix issue where filesystem traversal restriction broke Windows (#2167)——为修复 Windows 下路径净化限制失效的问题1.5.0 引入了剥离盘符drive name的逻辑而 1.5.1 的修复条目则是Only strip the drive name if it begins the string (#2176)见 History.markdown。可以推断1.5.0 中剥离盘符的实现是对路径中所有形如\w:的子串进行全局替换Windows 盘符如C:里的C恰好是单个字母数字字符于是C:会命中\w:模式。问题在于攻击者构造的..c:/..c:/..c:/etc/passwd中每一段..c:内部的c:同样命中\w:模式——全局替换会把三段c:全部剥掉剩下../../../../etc/passwd经File.expand_path归一化后再与基础目录拼接就得到了逃逸出 jail 的结果。这就是发布说明中那句我们可不能让这种事发生Well, we cant have that!背后的具体原理。修复只剥离字符串开头的盘符1.5.1 的修复思路非常干脆盘符只有出现在路径字符串的最开头才具有驱动器标识语义路径中部的c:只是普通字符序列不该被当作盘符处理。于是净化逻辑从全局剥离改为仅剥离开头匹配。在当代代码中这一约定保留在 lib/jekyll/path_manager.rb 的正则中clean_path.sub!(%r!\A\w:/!, /)\A锚定字符串开头\w:匹配C:这类单字符盘符。..c:/..c:/..c:/etc/passwd的开头是..而不是c:因此完全不会命中路径被原样保留在基础目录之下。修复后的结果由 test/test_path_sanitization.rb 固化为了回归测试至今仍在守护这条防线should strip just the initial drive name do assert_equal /tmp/foobar/jail/..c:/..c:/..c:/etc/passwd, Jekyll.sanitized_path(/tmp/foobar/jail, ..c:/..c:/..c:/etc/passwd) end发布说明中提到幸运的是不影响 1.4.xLuckily not affecting 1.4.x与 History 的记录完全吻合该回归由 1.5.0 引入#2167仅存在于 1.5.0 这一个版本中1.5.1 随即修复。三、现代实现PathManager 的完整净化流水线时至今日仓库当前版本为 4.4.1见 lib/jekyll/version.rbJekyll.sanitized_path已委托给单例类PathManager其净化核心sanitize_and_joinlib/jekyll/path_manager.rb是一条完整、可独立剖析的路径净化流水线def sanitize_and_join(base_directory, questionable_path) clean_path if questionable_path.start_with?(~) questionable_path.dup.insert(0, /) else questionable_path end clean_path File.expand_path(clean_path, /) return clean_path if clean_path.eql?(base_directory) # remove any remaining extra leading slashes not stripped away by calling # File.expand_path above. clean_path.squeeze!(/) return clean_path if clean_path.start_with?(slashed_dir_cache(base_directory)) clean_path.sub!(%r!\A\w:/!, /) join(base_directory, clean_path) end逐个环节拆解波浪号~转义~在 shell 与File.expand_path语义中代表主目录若questionable_path以~开头先在其前面插入/避免它被解析到当前用户主目录。对应的测试见 test/test_path_sanitization.rbescape tilde。路径归一化File.expand_path(clean_path, /)以/为基准展开路径消除.、..等相对段这是阻断../../穿越的核心一步。测试用例f./../../../../../../files/hi.txt会被净化成源目录下的files/hi.txt见 test/test_path_sanitization.rb。去重多余斜杠squeeze!(/)把连续多个/压缩为一个对应 History 中 Strip extra slashes viaJekyll.sanitized_path(#7182) 的演进。前缀守卫用slashed_dir_cache(base_directory)lib/jekyll/path_manager.rb即基础目录末尾补/的缓存版本检查净化后的路径是否已经位于基础目录内是则直接返回避免二次拼接产生base/base/...的重复前缀。盘符剥离1.5.1 的遗产sub!(%r!\A\w:/!, /)仅剥离开头的盘符让 Windows 绝对路径如C:\x在非 Windows 环境下不会变成脱离基础目录的绝对路径。安全拼接join(base_directory, clean_path)lib/jekyll/path_manager.rb用缓存化的File.join完成最终拼接返回结果被freeze冻结防止调用方意外修改共享缓存字符串。此外PathManager.sanitized_path对nil输入直接返回冻结的基础目录副本lib/jekyll/path_manager.rb对应 Handlenilargument toJekyll.sanitized_path(#8415) 的加固以及 test/test_path_manager.rb 中的相关断言。四、sanitized_path 在 Jekyll 内部的真实调用场景这条安全防线并非摆设而是 Jekyll 各核心模块处理外部输入时的必经关卡。从源码中可以看到它的典型调用点站点目录访问的统一入口Site#in_source_dirlib/jekyll/site.rb把任意传入路径逐级与 source 目录拼接Site#in_theme_dir、Site#in_dest_dir等同类方法均复用Jekyll.sanitized_path保证路径不越界。配置文件加载Configuration#config_fileslib/jekyll/configuration.rb用其探测source/_config.yml等配置文件的真实路径防止--config参数指向源目录之外的文件。serve 命令读取文件Commands::Serve的read_filelib/jekyll/commands/serve.rb在通过 HTTP 服务暴露站点文件时同样经过净化。集合静态文件读取Collection#read_static_filelib/jekyll/collection.rb用它计算相对目录。主题资源定位Theme在解析主题文件夹时也会先用Jekyll.sanitized_path再做File.realpathlib/jekyll/theme.rb避免符号链接把路径引出主题根目录。可以说凡是外部字符串将要变成文件系统路径的位置都能看到sanitized_path的身影——这正是 1.5.1 修复一个看似偏门的字符串处理 Bug却被列入安全发布的原因它守卫的是整条路径信任链的入口。五、从 1.5.1 到现在的回归保障一个小版本修复能否被长期守住取决于回归测试是否跟得上。当前仓库为路径净化保留了完整的两层测试test/test_path_sanitization.rb 直接测试Jekyll.sanitized_path的对外行为除 1.5.1 的原始用例strip just the initial drive name外还覆盖 tilde 转义、路径穿越消除、多余斜杠、nil输入、Windows 盘符处理D:/demo/_site保留、D:/sitemap.xml不与D:/site混淆等场景test/test_path_manager.rb 测试内部PathManager的冻结字符串与缓存语义确保性能优化缓存File.join结果以减少内存分配没有破坏返回值的不可变性。后续版本中对这条路径的每一次调整如 #7182 去多余斜杠、#8415 处理 nil、#8424 引入缓存均记录在 History.markdown都建立在这套测试的约束之上保证 2014 年的那次安全修复不会在未来的重构中悄悄退化。六、结语Jekyll 1.5.1 的故事虽然只有短短一个函数、一个正则锚点的变化却浓缩了静态站点生成器安全设计的一个关键命题任何外部输入在变成文件路径之前都必须经过一道显式的、可测试的净化边界。sanitized_path用\A锚点修正了过度剥离的回归用expand_path消解了穿越企图用前缀校验与冻结字符串保证了结果的确定性——这套组合拳从 1.5.1 一直延续到今天值得每一位在处理用户输入与文件系统交互的开发者参考。【免费下载链接】jekyll:globe_with_meridians: Jekyll is a blog-aware static site generator in Ruby项目地址: https://gitcode.com/gh_mirrors/je/jekyll创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表