在软件工程领域,CI/CD是持续集成、持续交付(或部署)的总称。CI/CD通过标准化的方式、自动化的手段,减少软件构建、测试、打包、部署过程中的人为干预,极大提高代码发布速度。

cicd-goat是一个专门配置的存在漏洞的CI/CD测试环境,覆盖由OWASP发布的CI/CD领域Top10的安全风险。这两者的作者都是由Cider Security公司,现已被Palo Alto Networks收购。

cicd-goat

cicd-goat的架构图如下:

靶场解题环境部署在CTFd平台,CI/CD环境分两类:

  • 绝大多数环境采用Gitea进行代码版本管理,Jenkins进行CICD
  • 难度为难的Gryphon采用Gitlab、Gitlab runner进行CICD

cicd-goat项目本身也是一个CI/CD环境,里面运用了一些我前面不熟悉的技术(例如使用Terraform对S3对象存储进行权限控制)。
通过对其自身环境的理解,也可以有助于理解CI/CD概念本身及其安全性,类似于HackTheBox博客文章It is Okay to Use Writeups里提到的“拿到root之后,花时间去理解环境是怎么运行的”。我打算后面再补充一篇分析cicd-goat运行环境文章。

参考项目README文档中的说明,下载docker-compose文件之后直接运行即可。

mkdir cicd-goat; cd cicd-goat
curl -o docker-compose.yaml https://raw.githubusercontent.com/cider-security-research/cicd-goat/main/docker-compose.yaml
get-content docker-compose.yaml | %{$_ -replace "bridge","nat"}
docker compose up -d

解题记录

Part1中包含简单、中等两个难度题目的解题过程记录。

Easy

White Rabbit

在CI/CD环境中,关键点是找到我们(或者攻击者)能控制的内容如何能影响到Pipeline的运行。

在Jenkins的wonderland-white-rabbit项目扫描多分支Pipeline记录中,我们可以看到对Pull Requests有检查,这说明我们可能可以通过提交包含恶意内容的Pull Request,来窃取flag1

Jenkinsfile的说明文档中,有关于如何处理秘密信息和秘密文件的说明,比较简单的实现是声明环境变量,并在后续步骤中使用环境变量。

在Gitea修改Jenkinsfile文件,并提交Pull Request后,Jenkins重新扫描会识别到Pull Request、并运行PR的Jenkinsfile中定义的流程。
Pull Request

Jenkins Status

而在执行单元的日志中,可以看到环境变量的base64输出,解码之后即可看到明文的flag1

log

Mad Hatter

在Jenkins的wonderland-mad-hatter项目扫描多分支Pipeline记录中,并没有对PR的检查,而且Gitea里的mad-hatter项目中并没有Jenkinsfile文件,那么管道流是如何实现的呢?
mad-hatter scan log

mad-hatter gitea

进一步在Gitea项目中浏览,发现了一个mad-hatter-pipeline项目,只包含一个Jenkinsfile文件。其文件内容中包含flag3凭证的处理:

    stage('make'){
steps {
withCredentials([usernamePassword(credentialsId: 'flag3', usernameVariable: 'USERNAME', passwordVariable: 'FLAG')]) {
sh 'make || true'
}
}
}
}

make命令根据makefile文件内容,进行自动化编译处理,恰巧在Gitea的mad-hatter-pipeline项目中有Makefile文件。
makefile

我修改Makefile文件后发现管道流一直没执行,几番尝试后发现新建分支可以触发管道流。
(注:我回过头看最前面截图时发现,扫描日志中一开始就有Checking branches...的输出,只不过最开始忽略了。🤦‍♂️)
check branches

随后在Build History中查看make编译过程的日志,即可得到flag。
make log

Duchess

这一个题目需求在Git项目中需要到泄露的Pypi Token,我直接在文件中找了很久、也尝试去使用JWT解码代码中的测试字符串,都没通过。

最后在Git的提交历史中搜索到删除token的提交,查看详情即可看到泄露的Pypi Token。
commit

remove token

这种泄露账号密码、API token的场景还挺常见。

  1. 首先不推荐将私有项目放在公有云仓库上;
  2. 其次如果放了敏感信息,最好的解决办法是删仓库,如果仓库被fork了,可以提交DMCA申请Github删;
  3. 如果不想删仓库的话,使用git-filter-repo等类似项目处理时要注意,有些文件存在Github上,可能通过这些文件拿到泄露的信息。可以参考这里

Medium

Caterpillar

这是很经典的一个题目,攻击者对Gitea上caterpillar项目拥有只读权限、可以提交PR,在Jenkins上caterpillar有生产环境和测试环境两种,生产环境对main分支持续集成,包含flag2,而测试环境则对PR进行集成、但不包含flag2。漏洞点在于测试环境环境变量中包含GITEA_TOKEN,攻击者可以使用这个TOKEN更新main分支代码。

首先fork代码、修改Jenkins文件,并提交PR:
modify jenkinsfile

在测试环境中拿到GITEA_TOKEN:
get gitea token

使用Gitea的API进行仓库内文件更新,需要注意的是filepath参数不需要、也不能以/开头,body参数中其他无关内容建议删除、只保留contentsha即可:
update file

随后即可在Jenkins上生产环境的日志中查看到flag2信息:
get flag

Cheshire Cat

Cheshire Cat需求用户将Jenkins构建任务运行在Jenkins Controller节点上。

Jenkins官方文档中对Pipeline语法的agent部分进行了说明,手册里并没写很详细,实际上我是在Github上搜索现有Jenkinsfile文件才知道如何去配置。

更新项目Jenkinsfile文件,使用agent {label 'built-in'}指定Pipeline运行在控制节点上,同时删去无关代码、避免报错:
jenkinsfile

待管道流运行后,即可拿到flag5
get flag5

Twiddledum

这是一个NodeJS环境的环境,实际执行的项目是twiddledum,但是这个项目引用了twiddledee,而后者我们能控制。

直接运行Jenkins中wonderland-twiddledum项目,在日志中可以看到运行twiddledee中index.js的记录:
twiddledee log

此时修改twiddledee项目、植入需要测试的代码,并重新发包
hack the planet

清理环境之后再次运行时,可以看到日志中出现了我们植入内容的记录:
new log

实际测试时需要删除掉原有tags,添加新版本的release文件:
new release

清理环境之后再次运行时,即可得到flag6

Dodo

Dodo使用Terraform进行S3对象存储的权限配置,并使用checkov对权限进行自动化检测。当代码仓库是任意人可读、并且通过权限校验时,给出flag7。
check

在checkov的文档中,给出了抑制/跳过检查的方法:

resource "aws_s3_bucket" "foo-bucket" {
region = var.region
#checkov:skip=CKV_AWS_20:The bucket is a public static content host
bucket = local.bucket_name
force_destroy = true
acl = "public-read"
}

参照文档修改main.tf文件:
main.tf commit

再次运行Jenkins任务即可拿到flag7(注:如果运行过程中出现报错,需要多运行几次):
get flag7