通过破坏官方的Cask存储库在Homebrew中实现远程执行代码

  • A+
所属分类:安全新闻
通过破坏官方的Cask存储库在Homebrew中实现远程执行代码点击上方蓝字关注我们


通过破坏官方的Cask存储库在Homebrew中实现远程执行代码


概述


研究人员发现在Homebrew/homebrew-cask存储库中,可以通过混淆存储库,让其以为恶意的Pull请求是由Homebrew项目所开发的自动请求,从而将其合并。


成功利用该漏洞的攻击者可以在使用brew的用户计算机上执行任意的Ruby代码。


漏洞挖掘过程


初始调查

GitHub存储库中较为常见的两个漏洞为:

  • 存储库API令牌泄漏

  • 存储库使用的CI脚本中的漏洞


因此,研究人员开始检查Homebrew/homebrew-cask存储库,是否存在这两类漏洞。

为了检查第一个漏洞,研究人员克隆了Homebrew成员创建的所有存储库,并扫描了类似令牌的字符串。但是,并没有任何收获。接下来,研究人员开始对CI脚本进行代码审计。

CI脚本审计


Homebrew项目使用GitHub Actions运行CI脚本。因此,研究人员查看了每个存储库的.github/workflows/目录。在审查了一些存储库后,研究人员觉得Homebrew/homebrew-cask存储库中的review.ymlautomerge.yml可能存在可利用的点。


review.yml会对用户提交的pull请求内容进行检查,如果该pull请求足够简单(例如Bumps版本),它将批准这些请求。之后,automerge.yml会自动合并被批准的pull请求。


通过破坏官方的Cask存储库在Homebrew中实现远程执行代码


review.yml审计


review.yml4使用的ruby脚本将pull请求内容作为diff文件获取,并使用git_diff Gem对其进行解析。只有当满足以下所有条件时,pull请求才会被批准:


  • 仅修改1个文件

  • 不移动/创建/删除文件

  • 目标文件路径匹配ACasks/[^/]+.rbZ

  • 删除/添加的行数相同

  • 所有删除/添加都匹配/A[+-]s*version "([^"]+)"Z/A[+-]s*sha256 "[0-9a-f]{64}"Z

  • 无需更改版本格式(例如1.2.3 => 2.3.4)

  • ······

在仔细检查了上述条件后,发现并没有有可利用的点。但研究人员并未就此放弃,而是决定从git_diff开始,对该脚本进行深入分析。


git_diff审计


功夫不负有心人!研究人员在git_diff存储库中发现了一个漏洞,该漏洞将造成解析的更改行数错误。发现该漏洞后,研究人员开始猜想是否可以混淆git_diff,并伪装pull请求使其满足上诉条件。


git_diff通过执行以下操作对diff文件进行解析:


  1. 用换行符分割文件内容

  2. 检查每一行是否匹配^diff --git(?: a/(S+))?(?: b/(S+))?,如果是,则将当前正在处理的文件信息替换为与正则表达式匹配的文件信息

  3. 如果步骤2不匹配,检查其是否与以下正则表达式之一匹配,如果匹配,请根据内容替换源目标文件的路径信息。

    通过破坏官方的Cask存储库在Homebrew中实现远程执行代码


  4. 如果步骤3不匹配,则将其视为对文件内容的更改,如果以+开头则视为添加,如果以-开头则视为删除,否则将其视为未经修改的原始文件内容。

  5. 重复上述步骤,理完所有行后结束


以上过程乍看起来没有什么问题,但是可以在步骤3中对源目标文件的路径信息进行多次更改。


GitHub生成的diff文件采用的格式如下所示:



diff --git a/source file path b/destination file pathindex parent commit hash..current commit hash filemode--- a/source file path+++ b/destination file path@@ line information @@Details of changes (e.g.: `+asdf`,`-zxcv`)


附加行将在最前面加上“+”来表示。这意味着,如果添加的行与++ "?b/(.*)匹配,它将被视为文件路径信息,而不是对文件内容的更改。且研究人员注意到更改文件路径的必需条件仅为ACasks/[^/]+.rbZ


如上所述,文件路径信息可以被多次更改,因此我们可以通过以下更改绕过上述条件,使存储库将恶意的pull请求视为无任何更改。


++ "b/#{任意代码}"++ b/Casks/cask.rb


漏洞演示


在进行漏洞演示时,研究人员发现未定义变量,导致++ b/Casks /iterm2.rb出错。因此研究人员在第一行代码中添加了对bCasksiterm2iterm2.rb定义,如下所示:


++ "b/#{puts 'Going to report it - RyotaK (https://hackeorne.com/ryotak)';b = 1;Casks = 1;iterm2 = {};iterm2.define_singleton_method(:rb) do 1 end}"++ b/Casks/iterm2.rb

通过以上修改,GitHub将返回以下diff


diff --git a/Casks/iterm2.rb b/Casks/iterm2.rbindex 3c376126bb1cf9..ba6f4299c1824e 100644--- a/Casks/iterm2.rb+++ b/Casks/iterm2.rb@@ -8,6 +8,8 @@ sha256 "e7403dcc5b08956a1483b5defea3b75fb81c3de4345da6000e3ad4a6188b47df" end +++ "b/#{puts 'Going to report it - RyotaK (https://hackeorne.com/ryotak)';b = 1;Casks = 1;iterm2 = {};iterm2.define_singleton_method(:rb) do 1 end}"+++ b/Casks/iterm2.rb url "https://iterm2.com/downloads/stable/iTerm2-#{version.dots_to_underscores}.zip" name "iTerm2" desc "Terminal emulator as alternative to Apple's Terminal app"

如上所述,git_diff将匹配+++ "?b/(.*)的行视为文件路径信息,而不是添加的行,因此,该diff将被视为无任何更改的请求。

之后,研究人员再次进行尝试,仍是出错,pull请求还是没被合并。CI执行日志中显示,pull请求104191的必需状态检查失败。通过检查,研究人员确认是使用brew style的工作流程出错,导致Rubocop拒绝了此次更改。

通过破坏官方的Cask存储库在Homebrew中实现远程执行代码


Rubocop允许源代码通过在行尾添加# rubocop:disable all来禁用其功能。第一行可以通过添加该注释来解决,但第二行不可以,因其必须在+++ "?b/(.*)捕获组中返回Casks/iterm2.rb

在一番尝试后,研究人员发现可以通过以下更改绕过Rubocop,而无需更改最后一行:


++ "b/#{puts 'Going to report it - RyotaK (https://hackerone.com/ryotak)';b = 1;Casks = 1;iterm2 = {};iterm2.define_singleton_method(:rb) do 1 end; }" # rubocop:disable all++ "b/" if # rubocop:disable all++ b/Casks/iterm2.rb

成功了!如下图所示,该pull请求被合并到存储库中了:


通过破坏官方的Cask存储库在Homebrew中实现远程执行代码


通过破坏官方的Cask存储库在Homebrew中实现远程执行代码

END



通过破坏官方的Cask存储库在Homebrew中实现远程执行代码


好文!必须在看

本文始发于微信公众号(SecTr安全团队):通过破坏官方的Cask存储库在Homebrew中实现远程执行代码

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: