GH Actions - Cache Poisoning
缓存投毒
在CI中的任何地方使用action/cache Git操作将运行两个步骤:一个步骤将在调用时的运行过程中发生,另一个将在工作流后发生(如果运行操作返回了缓存未命中)。
运行操作 - 用于搜索和检索缓存。搜索是使用缓存键进行的,结果要么是缓存命中(成功,数据在缓存中找到)要么是缓存未命中。如果找到,文件和目录将从缓存中检索出来以供活动使用。如果结果是缓存未命中,所需的文件和目录将被下载,就像是第一次调用它们一样。
工作流后操作 - 用于保存缓存。如果运行操作中的缓存调用结果返回了缓存未命中,此操作将使用提供的键保存我们想要缓存的目录的当前状态。这个操作会自动发生,不需要显式调用。
访问限制通过在不同分支之间创建逻辑边界提供缓存隔离和安全性 (例如:为分支Feature-A创建的缓存[基于main]将无法被分支Feature-B的拉取请求访问[基于main])。
缓存操作首先搜索分支中包含工作流运行的键和恢复键的缓存命中。如果当前分支中没有命中,缓存操作会搜索父分支和上游分支中的键并恢复键。
访问缓存是按分支(当前和父级)限定的,这意味着提供对所述分支的所有工作流跨运行的访问。
另一个重要的注意事项是,GitHub不允许一旦条目被推送就进行修改 - 缓存条目是只读记录。
我们使用了一个包含两个工作流的示例CI。这个例子展示了如何从一个低权限工作流转移到一个高权限工作流的攻击。
单元测试工作流运行单元测试和代码覆盖率工具。我们假设其中一个工具是恶意的或容易受到远程代码执行的攻击。工作流不需要使用action/cache Git操作。任何工作流都可以访问缓存。
发布工作流构建和发布应用程序工件。此工作流使用缓存来优化使用Golang依赖项。
单元测试工作流使用一个恶意操作,通过更改Golang日志库(go.uber.org/zap@v1)添加字符串‘BAD library’到应用程序工件描述中,添加了一个带有恶意内容的缓存条目。
接下来,发布工作流使用这个被投毒的缓存条目。结果,恶意代码被注入到构建的Golang二进制文件和镜像中。缓存保持被投毒状态,直到丢弃条目键(通常由依赖项更新触发)。使用相同缓存键的任何其他工作流、运行和子分支都会受到同样的投毒缓存的影响。
在我们进行的测试中,我们成功地将字符串‘BAD library’注入到镜像描述中:
这是在0.4.1版本中。接下来,我们更新了标签并多次重建镜像,观察到‘Bad library’仍然存在于描述中。
这个技术取自https://scribesecurity.com/blog/github-cache-poisoning/
Last updated