Jekyll 任意文件读取漏洞解析include配置绕过符号链接检查的修复始末3.6.3 / 3.7.4 / 3.8.4【免费下载链接】jekyll:globe_with_meridians: Jekyll is a blog-aware static site generator in Ruby项目地址: https://gitcode.com/gh_mirrors/je/jekyll2018 年 9 月Jekyll 官方发布了一组安全修复版本3.6.3、3.7.4、3.8.4修补一个被 GitHub 安全团队报告的严重漏洞攻击者只要在_config.yml的include:数组中巧妙放入一个符号链接symlink就能让 Jekyll 在构建时读取本不该被读取的任意文件。本文以仓库中的官方安全公告为主线结合EntryFilter与Reader的源码实现和对应测试还原漏洞成因、补丁原理与升级建议帮助你理解 Jekyll 在安全模式下如何守护站点源目录的边界。漏洞公告一次针对include:配置的符号链接攻击官方公告原文明确指出通过在include数组中简单加入一个符号链接即可让被链接的文件被读入构建流程——而在任何正常情况下这些文件都不应该被读取。漏洞的触发面是_config.yml中的include:配置项。在 Jekyll 中include用于显式纳入那些默认会被忽略的文件例如隐藏文件.htaccess。默认配置仅包含[.htaccess]可见 docs/_docs/configuration/default.md。问题在于安全模式下对符号链接的拦截逻辑并没有覆盖include显式纳入的文件路径于是攻击者可以通过一个指向源目录之外如/etc/passwd、密钥文件的符号链接诱使 Jekyll 将目标文件当作页面或静态文件读取并写入_site输出目录造成任意文件内容泄露。该补丁的详细实现可参考公告中附带的 Pull Requestjekyll/jekyll#7224并已回传到全部受影响分支。公告同时强调此问题影响所有此前发布的 Jekyll 版本即便还没有升级到 3.6/3.7/3.8 的理由也应尽快升级。漏洞根源EntryFilter#filter的符号链接检查为何会被绕过要理解漏洞需要先看 Jekyll 在读取源目录时如何过滤条目。lib/jekyll/entry_filter.rb 中的filter方法逐条判定条目是否应被排除以.、_、#、~开头SPECIAL_LEADING_CHAR_REGEX的特殊文件命中exclude配置且未被include显式包含的文件符号链接symlink。其中符号链接的判断逻辑如下lib/jekyll/entry_filter.rbdef symlink?(entry) site.safe File.symlink?(entry) symlink_outside_site_source?(entry) end def symlink_outside_site_source?(entry) !File.realpath(entry).start_with?(site.in_source_dir) end从实现看symlink?有三个前置条件启用safe模式、文件确实是符号链接、并且符号链接的真实路径File.realpath指向站点源目录之外。满足这三者才会被拒绝。问题正出在过滤流程的先后顺序上lib/jekyll/entry_filter.rbdef filter(entries) entries.reject do |e| next true if e.end_with?(.) included included?(e) next true if excluded?(e) !included next true if symlink?(e) next false if included special?(e) || backup?(e) end end注意这里的执行顺序先通过included?(e)判断条目是否命中site.include随后才轮到symlink?(e)检查。单看这段代码symlink?的检查仍然存在理论上即便条目被 include 也会被符号链接检查拦下。真正的漏洞在另一条读取路径上Reader#read_included_excludes。补丁核心read_included_excludes中的符号链接拦截Jekyll 在 lib/jekyll/reader.rb 中专门处理被include:显式纳入的条目def read_included_excludes entry_filter EntryFilter.new(site) site.include.each do |entry| entry_path site.in_source_dir(entry) next if File.directory?(entry_path) next if entry_filter.symlink?(entry_path) read_included_file(entry_path) if File.file?(entry_path) end end这行next if entry_filter.symlink?(entry_path)正是本次安全补丁的关键所在对应 History.markdown 中 3.8.4 的修复记录security: fix include bypass of EntryFilter#filter symlink check。在修复之前read_included_excludes遍历site.include时直接调用了read_included_file没有任何符号链接检查——也就是说EntryFilter#filter里的symlink?检查只覆盖了常规的目录遍历read_directories/get_entries却遗漏了include这条显式读取路径。修复后凡是include中的条目都要先经过EntryFilter#symlink?复核条目是目录则跳过next if File.directory?(entry_path)条目是符号链接且在 safe 模式下真实路径指向源目录之外则跳过next if entry_filter.symlink?(entry_path)只有真实的普通文件才会被读取为页面带 YAML front matter 走PageReader或静态文件走StaticFileReader见 lib/jekyll/reader.rb。同一补丁以相同逻辑回传到三个稳定分支3.6.xHistory.markdown 中的3.6.3、3.7.x3.7.4与3.8.xHistory.markdown 中的3.8.4保证老版本用户也有修复可用。安全模式的完整语义并非一刀切拒绝所有符号链接需要特别澄清的是Jekyll 的安全模式并非拒绝所有符号链接而是拒绝指向源目录之外的符号链接。从 lib/jekyll/entry_filter.rb 的实现可以看出判定条件由site.safe、File.symlink?与File.realpath(...).start_with?(site.in_source_dir)三者共同决定safe 模式未开启默认safe: false见 docs/_docs/configuration/default.md符号链接检查整体失效站点内符号链接可自由使用——这属于用户自担风险的常规构建模式safe 模式开启--safe命令行参数或配置safe: true符号链接只有解析后的真实路径仍落在站点源目录内才会被保留指向外部的链接一律拒绝。这与 include 标签lib/jekyll/tags/include.rb的提示一致安全模式下不允许使用指向站点源目录之外的符号链接。因此本次漏洞的实际影响面主要集中在以 safe 模式构建站点、且站点目录中恰好存在或攻击者能放置指向敏感文件的符号链接的场景——例如在多用户共享构建环境、CI 流水线或托管平台上。测试验证补丁行为的可复现证据仓库的单元测试完整覆盖了补丁前后的行为边界见 test/test_entry_filter.rbfilter symlink pointing outside site source指向源目录之外的符号链接会被过滤为空集include only safe symlinks in safe modesafe 模式下解析后仍在源目录内的符号链接可以正常读取如symlink-test下的main.scssinclude only safe symlinks in safe mode even when included即使把指向源目录外的符号链接显式写进include数组include: [symlinked-file-outside-source]safe 模式下依然会被拒绝static_files中不会出现该文件——这正是本次补丁覆盖的回归场景include symlinks in unsafe mode非 safe 模式下符号链接照常纳入确认安全模式之外的行为未被收紧。对应的测试夹具位于 test/source/symlink-test/其中symlinked-file-outside-source正是用来模拟外部文件被链接进站点的攻击路径。此外布局读取同样有防御性测试见 test/test_layout_reader.rb验证主题与站点布局中的外部符号链接不会被读取。这些测试共同构成回归防线防止同类绕过在后续版本中复现。升级建议与防御实践官方公告给出了明确的处置建议立即升级到3.6.3、3.7.4或3.8.4及更高版本公告特别注明3.7.4已随github-pages-v192一并发布GitHub Pages 用户需关注依赖版本此问题影响所有此前的 Jekyll 版本不要因为当前没遇到问题而推迟升级。查看当前版本可执行jekyll --version升级后建议在本地用bundle update jekyll并重新执行一次完整构建jekyll build确认站点产物正常。除升级外还可以结合仓库证据采取以下加固措施始终以 safe 模式运行不可信来源的站点jekyll build --safe或jekyll serve --safe。补丁后的symlink?检查只有site.safe为真时才生效safe 模式是本次漏洞防护生效的前提收紧include白名单仅在确有必要时显式纳入隐藏文件切勿将站点内任意路径尤其是用户可写入路径加入include审查符号链接定期检查站点源目录内的符号链接指向杜绝指向/etc、密钥目录、其他站点目录等敏感位置的链接在构建环境隔离敏感文件CI 或托管环境应保证站点源码目录与敏感文件分属不同权限域即便发生符号链接也不会暴露机密。小结本次include绕过EntryFilter符号链接检查的漏洞是过滤主路径与显式纳入路径两套读取逻辑不一致导致的典型安全缺陷常规目录遍历会检查符号链接而read_included_excludes这条捷径在补丁前完全没有复核。修复以next if entry_filter.symlink?(entry_path)一行将include路径拉回统一的安全边界之内并同步回传到 3.6、3.7、3.8 三个分支。对于使用 Jekyll 构建站点、尤其是以 safe 模式部署在共享或多租户环境中的用户这既是必须尽快跟进的安全补丁也是理解 Jekyll 源目录边界模型lib/jekyll/entry_filter.rb lib/jekyll/reader.rb的最佳切入点。【免费下载链接】jekyll:globe_with_meridians: Jekyll is a blog-aware static site generator in Ruby项目地址: https://gitcode.com/gh_mirrors/je/jekyll创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
