写在前面
最近在公司做 CI/CD 安全专项,做了很多调研,检测防护工具、方法论等,也分析过一些供应链投毒的 case,但总感觉不够系统不成体系。所以希望通过 cicd-goat 靶场,对整个研发运维流程中的风险能有一个较为全面的认知,事实上通过对靶场通关的研究也收获了不少。
在cicd-goat 靶场中我看到了很多容器技术的影子。其实,云原生和 CI/CD 的联系一直比较紧密,二者都是颠覆原有传统开发模式的革命性技术。很多 CI/CD 基础设施都用 Kubernetes、Docker 等容器技术搭建,同时 CI/CD 又以自动化、敏捷开发的特性便捷、迅速的为云原生环境输送容器化应用。
CI/CD 技术及其衍生出的新系统的引入,也为整个开发的生命周期带来了新的风险。本次文章将借用 Github 很火的 CI/CD 研究项目 cicd-goat,来窥探 CI/CD 的安全风险,除了通关记录之外也会做适当的拓展,本系列文章会在每一关最后穿插 CI/CD OWASP-Top10 的安全风险,以帮助大家更好了解 CI/CD 安全。
由于这次内容较长,本系列主要分上、中、下三篇文章推送,第一篇主要简单介绍 cicd-goat 环境搭建,以及 Easy 部分;第二篇主要介绍 Moderate 部分;第三篇主要介绍 hard 部分,这一部分的关卡比较复杂,我会在每一关最后附上攻击路径草图供大家分析参考。
总之,通关的过程不是很顺利,有借鉴大佬的思路,也有自己的摸索,学到很多东西。这里将自己通关的过程和坑点记录一下,如果有不对的地方,欢迎各位大佬指正。
一、通关前的准备
1.1 cicd-goat 项目简介
项目由Cider Security 公司(2022 年被Palo Alto Networks 收购)的安全研究者们创建和维护,下面摘自cicd-goat 官方 Github[1] 的简介(机翻):
CI/CD Goat 项目允许工程师和安全从业者通过一组 11 个挑战来学习和实践 CI/CD 安全性,这些挑战针对真实的、全面的 CI/CD 环境进行。这些场景的难度级别各不相同,每个场景都侧重于一个主要攻击媒介。
这些挑战涵盖了十大 CI/CD 安全风险[2],包括流量控制机制不足、PPE(中毒管道执行)、依赖链滥用、PBAC(基于管道的访问控制)等。
下面是靶场 Web 系统的访问地址和初始账号密码(普通用户),管理员用户的账密[3]在这里。一般情况下除非测试或排错之外,用管理员账号是不符合规则的,建议不要轻易使用。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
下面是官方的靶场架构图:
靶场关卡总览:
1.2 心理建设
就整个通关过程而言,还是有不少坑点的,主要分为下面几点:
-
• 国内网络环境原因 -
• 需要自己解决镜像拉取问题; -
• 环境里默认的 Dockerfile 或 CI 构建文件中的安装过程会失败(需要添加国内源); -
• gitlab 容器 terrform 初始化很容易出错,需要慢慢排错。 -
• 题目本身的复杂度 -
• 官方给的solutions[4] 比较简略,有的甚至不能顺利通关,个别关卡需要参考别人的 wirtup,或自己摸索; -
• 网上找的国内外 wirteup 文章很少有能把 hard 部分详细写出来的。
在开始之前,首先心里要有个预期,很顺利的完成通关几乎是不可能的,排错的时候心态不要崩。相信你最后一定会收获很多。
1.3 环境搭建
本人是在 linux 主机上搭建的,如果您的笔记本内存充足也可以选择在本地电脑上搭建,建议至少预留 8G 内存。
1.3.1 环境搭建过程
环境的构建命令还是比较简单的,docker-compose 一键启动容器,支持 Linux、mac、windows。
Linux & Mac
curl -o cicd-goat/docker-compose.yaml --create-dirs https://raw.githubusercontent.com/cider-security-research/cicd-goat/main/docker-compose.yamlcd cicd-goat && docker compose up -d
Windows
mkdir cicd-goat;cd cicd-goatcurl -o docker-compose.yaml https://raw.githubusercontent.com/cider-security-research/cicd-goat/main/docker-compose.yamlget-content docker-compose.yaml |%{$_-replace "bridge","nat"}docker compose up -d
1.3.2 环境搭建的坑点和问题
注:Easy 和 Moderate 部分的关卡是用不到 Gitlab 的,Gitlab 环境调试比较麻烦,如果安装失败可以先不管,先进行前面的关卡。
1.3.1 解决镜像拉取的问题
国内的网络环境拉取镜像会有问题,但是也有解决办法。可以使用 docker 全局代理,也可以用国外 VPS --> 阿里云镜像仓库 --> 本地 的方式转存,也可以打成 tar 包在本地 load,这里不多说。
有位大佬也分享过一种方式,可以参考这篇文章。
1.3.2 解决Gitlab卡慢的问题
用官方默认的安装方式安装后,Gitlab 会很卡,每个页面会加载 7~10s 左右(64G 内存主机)。所以需要修改一下 Gitlab 配置,防止卡顿,方便后面的操作。
# 进入gitlab容器docker exec-it gitlab bash# 修改gitlab配置文件vi /etc/gitlab/gitlab.rb# 在gitlab.rb文件最后添加如下内容## 减少内存,且防止卡puma['worker_processes']=4puma['per_worker_max_memory_mb']=2048sidekiq['concurrency']=16postgresql['shared_buffers']="256MB"postgresql['max_worker_processes']=8# 刷新配置gitlab-ctl reconfigure# 重启gitlabgitlab-ctl restart
1.3.3 Gitlab 容器内部terraform初始化失败的问题
Gitlab 模块的靶场在启动容器的时候使用 terrafrom 做了初始化,包括预置 token、创建仓库、创建包等等。
terrafrom 在初始化的时候会从先 hashicorp 官网安装一些 provider,以方便和目标资源进行交互。这里也经常会因为网络环境问题报错,一次安装不成功是很正常的,耐心调错即可。
调错时可以一边对照日志,一边对照容器的初始化脚本[5]。网络环境问题可以选择配置代理或换国内 terraform 的镜像源解决。
1.3.4 Gitlab pipline构建过程中的报错
pipline 报错的原因主要在安装依赖时网络环境问题,比如在 apk install 或 pip install 等情况下过程会非常慢,timeout 报错导致 pipline 终止是很常见。建议先用管理员账号统一替换成国内源,然后再进行构建。
1.4 前置知识了解
在开始之前,你需要先了解一下 Jenkins、Gitlab、Gitea 这些系统是干啥的,主要充当什么角色,如果能再了解一些基本的配置和操作就更好。受限于篇幅原因,我们这里做一个简要介绍,不会很深入。
-
• Jenkins是一个开源的自动化服务器,主要用于持续集成(CI)和持续交付(CD)。它支持各种开发、构建、测试和部署流程的自动化操作,是现代软件开发生命周期中不可或缺的工具之一; - • 随着技术的不断发展,也催生出一些更适用于云原生场景的 CICD 系统,比如Tekton、Argo CD 等,同时 Gitlab 也有自己的 CICD 模块,但从笔者的见到的一些情况来看 Jenkins 的使用还是最广泛的;
- • 从安全角度来讲,Jenkins 类的 CICD 系统是一个非常重要的风险点,向左连接着代码管理仓库,承接着编译、构建、打包自动化的任务,同时向右连接着制品仓库、运行时环境可以实现自动部署。可想而知,系统中会存放大量的敏感凭据以支撑这一系列的操作,而其中任何一个凭据的泄露或管理不善,都有可能导致整个研发流程被投毒,导致风险点大面积扩散。
-
• CI/CD 配置文件:在持续集成/持续部署(CI/CD)中,配置文件用于定义自动化构建、测试和部署的流程。不同的 CI/CD 平台(如 Jenkins、GitHub Actions、GitLab CI、CircleCI 等)有不同的配置格式,但基本上都是描述性文件。通过 CI/CD 配置文件,我们可以访问存储在 CI/CD 系统中的敏感凭据,并且可以执行任意命令。CICD 的攻击场景中,有一个重要的点就是想方设法对 CICD 配置文件(比如 Jenkinsfile)进行投毒,这也被称为 PPE(Poisoned Pipeline Execution,投毒管道执行)。 -
• Gitea 是一个轻量级、开源的自托管 Git 服务,类似于 GitHub、GitLab 和 Bitbucket。但它不提供 CICD 之类的功能,可以理解为它就是一个纯代码仓库。比较适合适合个人开发者、小型团队以及中小型企业。 -
• Gitlab 是一个基于 Git 的开源 DevOps 平台,和 Github 比较像,提供从代码版本控制到持续集成/持续交付(CI/CD)和部署全流程解决方案。在这次的靶场中,hard 部分才会涉及此系统。
如果你了解的还不是很深入,不用担心,可以先开始靶场了,在体验过程中你会进一步加深理解。
二、复现过程(Easy部分)
2.1 White Rabbit
2.1.1 题目与提示
题目与提示:
我迟到了,我迟到了!没有时间说“你好,再见”了!在被抓住之前,利用你对 Wonderland/white-rabbit 存储库的访问权限窃取存储在 Jenkins 凭证存储中的 flag1 机密。
🔔尝试通过存储库触发管道。
🔔如何使用 Jenkinsfile 访问凭证?
2.1.2 分析过程
首先,登录Gitea http://{YOUR_IP}:3000/,访问私有仓库Wonderland/white-rabbit中的代码,我们发现Jenkinsfile文件是可以直接修改的,所以将文件内容修改为如下:
pipeline { agent any stages { stage ('poc'){ steps { withCredentials([string(credentialsId:'flag1', variable:'msg')]){ sh ''' echo $msg | base64 ''' } } } }}
注意⚠️:由于Jenkins的Masks Passwords[6]插件的这个功能,在你打印凭据信息时会把真实的凭据隐藏,所以需要把它进行base64编码后再输出。
上面的步骤可以通过 gitea 页面操作,也可以使用下面git命令完成:
git clone http://{YOUR_IP}:3000/Wonderland/white-rabbit.gitgit checkout -b gitflag2vim Jenkinsfilegit commit -a -m "cat flag"git push -u origin gitflag2
Jenkins的凭据(cerdentials[7])一般存储在/var/jenkins_home/credentials.xml文件中和用户目录下,前者作用于全局,后者作用于特定用户:
接下来需要创建一个合并请求,以触发 pipline 的执行从而打印 flag, 这个需要在gitea页面上操作:
创建后,可以看到jenkinsfile文件修改的明细:
同时,合并请求已经触发了jenkins的pipeline(PR-2),可以在jenkins后台http://{YOUR_IP}:8080/ 看到poc这个stage已经执行了:
可以在输出log中看到flag的base64编码:
最后base64解密输出,提交flag。
2.1.3 风险点
⚠️D-PPE(直接管道投毒执行)
这一关主要体现的风险在于直接管道投毒执行(D-PPE),它是OWASP CI/CD 类前十大风险中管道投毒执行(PPE)[8]的一个子类。主要是指攻击者在获取某个 SCM (如 gitlab、gitea、github 等)的凭据后,有权限直接修改仓库中的 CI 配置文件(如 Jenkinsfile),从而利用“push”或“PR”事件触发管道执行 CI 配置文件中的恶意命令。
攻击者可以通过 CI 文件的投毒,来获取构建节点上的权限或者读取 pipline 中预置的敏感凭据信息。在这一关中读取的 flag,那么在真实场景可能是 SSH 密码、镜像仓库 token、生产集群 kubeconfig 等等,危害可见一斑。
2.2 Mad Hatter
2.2.1 题目与提示
题目与提示:
Jenkinsfile 受到保护?听起来像是一个非生日聚会。使用您对 Wonderland/mad-hatter 存储库的访问权限窃取 flag3 机密
🔔 Jenkinsfile 存储在哪里?在 Wonderland 组织中搜索 repo 名称,您可能会找到一些有用的 repo。
🔔 Jenkinsfile 运行哪些命令?
2.2.2 分析过程
(1)发现没有权限直接修改CI配置文件
在代码仓库中,我们发现mad-hatter
仓库中并没有Jenkinsfile文件,而Jenkinsfile文件被单独存储在mad-hatter-pipeline
仓库中。在现实情况下可能会因为安全性,devops 工程师将CI配置文件和代码分开存储。
下面是mad-hatter-pipeline项目中的Jenkinsfile内容:
pipeline { agent any environment { PROJECT ="yagmail" } stages { stage ('Install_Requirements'){ steps { sh """ virtualenv venv pip3 install -r requirements.txt || true """ } } stage ('Lint'){ steps { sh "pylint ${PROJECT} || true" } } stage ('Unit Tests'){ steps { sh "pytest || true" } } stage('make'){ steps { withCredentials([usernamePassword(credentialsId:'flag3', usernameVariable:'USERNAME', passwordVariable:'FLAG')]){ sh 'make || true' } } } } post { always { cleanWs() } }}
我们注意到第四个stage,也就是Jenkinsfile的make阶段,使用了凭证绑定[9]技术,也就是将凭证绑定到环境变量以供各种构建步骤使用。如果没有权限限制,我们其实可以直接打印出FLAG环境变量的内容来获取flag。
但是,我们发现没有权限直接修改Jenkinsfile文件:
(2)投毒CI配置文件引用的Makefile
虽然无法直接修改Jenkinsfile文件,但是Jenkinsfile执行make操作的时候会引用Makefile,也就是说我们可以通过投毒Makefile 来间接投毒管道。
一定要注意,因为Makefile格式限制,命令前面是一个TAP键,不能用4个空格代替,也不能 copy 别人的粘贴过来。
否则就会出现下面的报错:
修改好Makefile并push到thealice-patch-3分支,然后创建合并请求。
当然,上述操作也可以通过git完成:
git clone http://{YOUR_IP}:3000/Wonderland/mad-hatter.git# 创建一个新分支并切换到该分支git checkout -b thealice-patch-3# 修改Makefile文件vim Makefile# 将变化区的文件先从工作区提交到暂存区,然后将暂存区中的变化提交到仓库区git commit -a -m "get flag2"# 将本地分支thealice-path-3推送到远程仓库origin,并设置一个上游分支(下次就可以直接git push了)git push --set-upstream origin thealice-patch-3
(3)获取 flag3
随后到Jenkins中查看make的log输出:
解码下面的base64提交即可:
2.2.3 风险点
⚠️I-PPE(间接管道投毒执行)
间接管道投毒执行(I-PPE)与直接管道投毒执行(D-PPE)不同的是,I-PPE 是在没有权限直接修改代码仓库中的 CI 配置文件的情况下,通过修改 CI 配置文件中引用(依赖)的文件,比如 Makefile、shell 脚本、第三方工具等,间接的方式达到管道投毒的目的。这一点其实比较好理解。
2.3 Duchess
2.3.1 题目与提示
如果每个人都管好自己的事,世界就会比现在更快地运转起来。这也适用于你的秘密吗?你可以访问 Wonderland/duchess 存储库,该存储库大量使用 Python。公爵夫人非常关心她的凭证的安全性,但肯定有一些 PyPi 令牌留在某个地方……你能找到它吗?
🔔 PyPi 令牌的前缀为“pypi-”。
🔔 过去可能犯过错误。
2.3.2 分析过程
从题目来看flag应该是泄露在duchess仓库中的某个地方,并且前缀为"pypi-"。
方法 1 翻找历史commit
因此可以先从仓库的commit记录中检索“token”或“pypi”等字眼,然后一个一个去翻找前缀为"pypi-"的flag。这一点在实战中很受用。
最终可以在“remove pypi token”的commit记录中找到flag,如下图:
但是,如果commit记录比较多的话,这样的方式比较低效。我们可以借助工具--gitleaks。
方法 2 第三方工具扫描
首先将Wonderland/duchess仓库clone到本地,应该是公开仓库,clone不需要输入密码:
git clone http://{YOUR_IP}:3000/wonderland/duchess.git
然后利用gitleaks[10]检测仓库中的敏感凭据信息:
gitleaks 是一个 SAST 工具,用于检测和防止git repos 中的硬编码隐私,如密码、api 密钥和令牌;支持丰富的secret类型,可以自定义规则,不仅可以扫描最新的源代码,还能回溯整个git历史记录。
⚠️需要注意git不能是低版本,否则gitleaks会报错,应该升级到git 2.x版本
# 进入仓库cd/opt/duchess# gitleaks检测gitleaks detect -v
从检测的信息中可以查找出pipy的凭据信息:
但是secret显示的不够全,所以需要利用git show <commit-hash>
获取历史信息,进一步获取到pypi token的完整信息。
git show 43f216c2268a94ff03e5400cd4ca7a11243821b0
最后提交pypi的完整token即可。
2.3.3 风险点
⚠️凭证管理不当
凭证管理不当[12](Insufficient credential hygiene)的风险主要涉及攻击者能够通过流水线中存在的访问控制漏洞、不安全的凭据管理以及过于宽松的凭证,获取并使用分布在流水线中的各种密钥和令牌。
常见的风险有以下几个点:
-
• 代码中包含硬编码的凭据被推送的代码仓库中:这也是这一关体现的风险点。开发人员由于疏忽或缺乏安全意识将密钥硬编码在代码中,这是比较常见的问题,甚至很多 CVE 漏洞的底层原理也是硬编码。 -
• 在构建部部署过程中不安全的使用凭据:比如将明文密钥硬编码在 CI/CD 配置文件中;CI/CD 系统中的凭据没有设置合理的作用范围;凭据可以被未经审查的代码访问;凭据在管道中使用完之后没有及时清理等。 -
• 容器镜像层中的硬编码凭据:这个也是比较常见的现象,任何可以下载镜像的人都能访问凭据; -
• 打印到控制台输出的凭据:比如在 Jenkins 中的 console log 中打印出明文的凭据,或者在其他日志文件中记录明文凭据; -
• 长期未轮替的凭据:这个和密码管理是一个道理,但 CI/CD 系统中存在大量的各种系统的凭据,研发或运维人员经常为了方便,凭据都设置较长的有效期。同时又遵循着“如果没有问题,就不要修复”的原则,导致凭证多年没有更换,攻击队(或黑客)可能拿着同一个凭据打好几年。
TIPS:修复代码中的硬编码问题,不是仅删除当前代码中的硬编码就够了,也需要删除 commit 历史记录中的敏感信息。如果打包了或者构建了镜像,也应该清理相关制品中的硬编码。
预告
下一篇将介绍Moderate 部分的通关记录,以及每一个关卡引申出来的安全风险,敬请期待!
引用链接
[1]
CICD-Goat 官方 Github:https://github.com/cider-security-research/cicd-goat[2]
十大 CI/CD 安全风险:https://owasp.org/www-project-top-10-ci-cd-security-risks/[3]
管理员用户的账密:https://github.com/cider-security-research/cicd-goat/blob/main/break-glass.md[4]
solutions:https://github.com/cider-security-research/cicd-goat/tree/main/solutions[5]
初始化脚本:https://github.com/cider-security-research/cicd-goat/blob/main/gitlab/run.sh[6]
Masks Passwords:https://plugins.jenkins.io/mask-passwords/[7]
cerdentials:https://www.jenkins.io/zh/doc/book/using/using-credentials/[8]
管道投毒执行(PPE):https://owasp.org/www-project-top-10-ci-cd-security-risks/CICD-SEC-04-Poisoned-Pipeline-Execution[9]
凭证绑定:https://plugins.jenkins.io/credentials-binding/[10]
gitleaks:https://github.com/gitleaks/gitleaks[11]
secret 类型:https://github.com/gitleaks/gitleaks/tree/master/cmd/generate/config/rules[12]
凭证管理不当: https://owasp.org/www-project-top-10-ci-cd-security-risks/CICD-SEC-06-Insufficient-Credential-Hygiene
原文始发于微信公众号(喵苗安全):从cicd-goat了解供应链安全风险(上)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论