CI/CD systems are core infrastructure of modern software engineering—they have read and write access to code repositories, hold deployment credentials for cloud platforms, and can directly modify production environments. Once a pipeline is compromised, an attacker can simultaneously gain code control, Secrets access, and cloud environment penetration capability, with an impact scope far exceeding the compromise of a single server.
This skill organizes CI/CD attack methodology in the form of a decision tree. For detailed technical methods for each platform, please refer to the corresponding reference documents.
In-depth Reference
After identifying a specific platform, load the corresponding reference document to obtain complete attack technical details:
- GitHub Actions specialized attack methods → read references/github-actions-abuse.md
- Platforms such as Jenkins / Terraform / Atlantis / GitLab CI / Azure DevOps / CircleCI / Drone / Buildkite → read references/platform-specific.md
- Cross-platform build file injection (package.json, setup.py, Makefile, Bazel, Maven/Gradle, csproj) → read references/build-file-injection.md
Phase 0: CI/CD System Identification
When the following identifying files are found in the target code repository or infrastructure, the corresponding CI/CD platform can be determined:
| Identifying File/Feature | CI/CD Platform | Next Step |
|---|---|---|
.github/workflows/*.yml |
GitHub Actions | → Phase 2 PPE + refer to github-actions-abuse.md |
Jenkinsfile or /script console |
Jenkins | → Phase 4 Jenkins specialized + refer to platform-specific.md |
.gitlab-ci.yml |
GitLab CI | → Phase 2 PPE + refer to platform-specific.md |
.circleci/config.yml |
CircleCI | → Phase 2 PPE + refer to platform-specific.md |
azure-pipelines.yml |
Azure DevOps | → Phase 2 PPE + Service Connection Secret extraction |
.drone.yml |
Drone CI | → Phase 2 PPE + Self-hosted Runner check |
.buildkite/*.yml |
Buildkite | → Phase 2 PPE + Agent cloud/K8s permission check |
atlantis.yaml + Webhook |
Atlantis | → Phase 4 Atlantis specialized |
*.tf + Terraform Cloud |
Terraform Cloud | → Phase 4 Terraform specialized |
serverless.yml |
Serverless Framework | → refer to platform-specific.md |
docker-compose.yml + CI trigger |
Docker Build | → Check build context leakage |
| Airflow DAGs / Concourse pipeline | Airflow / Concourse | → refer to platform-specific.md |
Quick identification tips:
- After cloning the repository, search for configuration files: look for
Jenkinsfile、.github/workflows、.gitlab-ci.yml、.circleci、atlantis.yaml - Check Webhook configuration: Webhook URLs expose the CI/CD service type and internal network addresses
- Enumerate CI/CD service ports: Jenkins defaults to 8080, Atlantis defaults to 4141, GitLab Runner 8093
Phase 1: VCS Attack Surface
Before entering pipeline attacks, VCS (version control system) itself is an important attack surface.
1.1 Code Leakage
Common sources of sensitive information in code repositories:
- Git history: Deleted keys/credentials may still be in the commit history (
git log -psearch all branches) - Public repositories: Use GitHub Dork to search organization name + keywords (
password、secret、token、AWS_) - Deleted Fork data: Commit data in deleted forks on GitHub can still be accessed via commit hash
- Internal repository exposure: Fork data from before a private repository became public can be brute-forced via short SHA-1 enumeration
1.2 Branch Protection Bypass
Branch protection is a key line of defense against malicious code being merged, but there are multiple ways to bypass it:
| Bypass scenario | Condition | Attack method |
|---|---|---|
| Insufficient approvals | Control multiple accounts | Use another account to approve your own PR |
| GITHUB_TOKEN self-approval | Actions has write permissions | Approve a PR via GITHUB_TOKEN in Actions |
| Approval not invalidated after push | "dismiss on push" not enabled | First submit legitimate code to obtain approval, then push malicious code |
| CODEOWNERS misconfiguration | Incorrect file format | GitHub does not report an error but CODEOWNERS protection becomes ineffective |
| New branch unprotected | Use wildcard * but there is a delay |
When a new branch is created, branch protection has not yet taken effect, and the first push can trigger Actions |
| Administrator exemption | Administrators not included in the rules | Administrators can directly bypass all branch protection |
| PR hijacking | Can modify others' PRs | Inject malicious code into someone else's PR and then approve and merge it yourself |
1.3 Webhook abuse
- No Secret verification: An attacker can forge Webhook requests to trigger the pipeline
- Secret exposed in URL: The Secret is attached in URL parameters and is easily logged
- IP allowlist bypass: Creating an account on GitHub/GitLab and configuring a Webhook is enough to send requests to Jenkins/Atlantis within the allowlist
- Bitbucket Cloud: Webhook Secret is not supported, so it can only rely on the IP allowlist
Phase 2: PPE -- Poisoned Pipeline Execution
PPE is the core framework of CI/CD attacks. Attackers "poison" the pipeline by modifying CI configuration files or their dependency files, causing it to execute malicious commands.
PPE determination decision tree
是否能写入/修改 CI 配置文件(workflow yml / Jenkinsfile / .gitlab-ci.yml)?
├── 是 → D-PPE(直接毒化)
│ ├── 能直接推送到触发分支 → 最简单路径
│ └── 需要 PR → 检查 PR 是否触发流水线
│
├── 否,但能修改 CI 配置依赖的文件?
│ ├── Makefile / build.sh / package.json / requirements.txt → I-PPE(间接毒化)
│ ├── Terraform .tf 文件 → I-PPE via external data source
│ └── Dockerfile → I-PPE via build context
│
└── 否,且无仓库写权限?
└── 3PE(第三方毒化)
├── 能否提交外部 PR? → pull_request 触发器
├── PR 标题/描述是否被注入到 shell 命令? → Context Script Injection
└── 能否通过 issue/comment 触发? → issue_comment 触发器
D-PPE(Direct PPE)
The attacker directly modifies the CI configuration file. This is the most direct attack path:
- Prerequisite: Has repository write permission, or can trigger a pipeline that uses the PR branch configuration through a PR
- Target file:
.github/workflows/*.yml、Jenkinsfile、.gitlab-ci.yml、.circleci/config.yml - Key condition: Some platforms (such as GitHub Actions'
pull_requesttrigger) use the workflow version submitted by the PR author—this means the attacker's modifications will be executed
I-PPE(Indirect PPE)
The attacker does not modify the CI configuration file itself, but instead modifies files that the CI configuration depends on:
- Build script:
Makefile、build.sh、package.json'sscriptsfield - Dependency file:
requirements.txt、package-lock.json(supply chain injection) - IaC configuration:Terraform
.tfin the fileexternaldata source orlocal-execprovisioner - Docker build: Control the Dockerfile or build context path
The limitation of I-PPE is that the attacker can only access Secrets that the pipeline originally loads, and cannot declare new Secret references through configuration.
3PE(Third-Party PPE / Public PPE)
External attackers without repository write permission trigger the pipeline in the following ways:
Context Script Injection (most common):
When the CI configuration uses ${{ github.event.pull_request.title }} and other user-controllable expressions in run: When concatenating shell commands in a step, an attacker can inject commands via the PR title. GitHub Actions renders the expression before execution, so shell quote escaping cannot prevent it.
Dangerous trigger comparison:
| Trigger | Secrets access | Write permissions | Code version used | Risk |
|---|---|---|---|---|
pull_request |
None (GITHUB_TOKEN read-only) | None | PR branch | Low (but can upload malicious Artifacts) |
pull_request_target |
Yes | Yes | Base branch (safe by default) | High (extremely dangerous if checkout PR code) |
workflow_run |
Yes | Yes | Depends on configuration | High (often exploited in chains) |
issue_comment |
Yes | Yes | Base branch | High (RCE when combined with checkout PR ref) |
pull_request_target fatal misuse:
Although pull_request_target runs in the base branch context (seemingly safe), if the workflow explicitly checks out the PR code (ref: ${{ github.event.pull_request.head.sha }}), the attacker's code executes in an environment with Secrets and write permissions.
Phase 3: Secrets theft and lateral movement
After successfully poisoning the pipeline, the core goal is to steal Secrets and expand outward.
3.1 Secrets extraction methods
Environment variables:
env | base64export all environment variables (base64 encoding can bypass GitHub's log masking)- Double base64 encoding further bypasses masking:
echo '${{ toJson(secrets) }}' | base64 -w0 | base64 -w0
File system:
- Temporary script files:
/home/runner/work/_temp/*(GitHub Actions stores the rendered script) - Process memory:
gcoreor/proc/<pid>/memread plaintext Secrets from the Runner.Worker/Runner.Listener processes
When direct exfiltration is not possible:
- Use GITHUB_TOKEN to create repositories/issues/comments as a data relay
- Write Secrets to Artifacts and upload them
- Write to Actions logs (note masking rules)
3.2 CI/CD to cloud environment traversal
CI/CD Runners usually have cloud platform credentials for deployment. These credentials are the bridge from CI/CD to the cloud environment:
| Credential source | Location | Exploitation method |
|---|---|---|
| OIDC Token(GitHub → AWS/GCP/Azure) | id-token: write permissions |
Request OIDC Token → AssumeRoleWithWebIdentity |
| AWS static AK/SK | Environment variables or Secrets | Refer to the cloud-aksk-exploit skill |
| GCP Service Account JSON | File or environment variable | Refer to the gcp-pentesting skill |
| Azure Service Principal | Environment variables | Refer to the Azure-related content in the aws-pentesting skill |
| Terraform Cloud Runner credentials | tfc-aws-*/tfc-gcp-* files |
Directly use CLI to operate cloud resources |
| Kubernetes kubeconfig | ~/.kube/config or mounted SA Token |
Cluster takeover |
| Docker Registry Token | ~/.docker/config.json |
Push malicious images |
Additional attack surface for Self-hosted Runners:
- Cloud metadata service (169.254.169.254) → obtain IAM role credentials bound to the instance
- Internal network lateral movement → Access other internal services
- Docker API (2375/tcp) → Container escape
- Kubernetes cluster access → DaemonSet lateral scaling
3.3 Production Environment Takeover
If the pipeline is directly responsible for production deployment:
- Code tampering: Inject backdoor code into build artifacts
- Supply chain attack: Tamper with the npm/PyPI package publishing process (steal publish token → publish malicious version)
- Image poisoning: Modify Docker image layers to inject malicious code
- IaC tampering: Modify Terraform/CloudFormation configuration to create backdoor resources
Phase 4: Platform-Specific Attacks
4.1 GitHub Actions
GitHub Actions is one of the most widely used CI/CD platforms, with a rich attack surface:
- Artifact Poisoning: Low-privilege workflow uploads malicious Artifact → High-privilege workflow downloads and executes it
- Cache Poisoning: Cache shared across workflows lacks trust isolation → Poison scripts/binaries in the cache
- GITHUB_TOKEN abuse: Merge PR, approve PR, create PR
- Action Tag hijacking: After an attacker gains write access to an Action repository, force-push a tag to point to a malicious commit
- AI Agent injection: LLM Agent in CI (Gemini/Claude Code Action) can be controlled by prompt injection
→ Read references/github-actions-abuse.md
4.2 Jenkins
Jenkins' high-privilege characteristics make it a high-value target:
- Groovy Script Console(
/script) → Direct RCE - Pipeline creation/modification → Execute arbitrary commands through Jenkinsfile
- Credential dumping → Groovy script exports all secrets in credentials.xml
- File read → RCE → Read
master.key+hudson.util.Secret→ Offline decrypt all credentials → Forge remember-me Cookie
→ Read references/platform-specific.md
4.3 Terraform / Atlantis
The core of IaC tool attacks lies in code as execution:
- Terraform Plan RCE:
externaldata source or malicious Provider can execute code at theplanstage - Terraform Apply RCE:
local-execprovisioner executes at theapplystage - State file tampering: Write to state file to inject malicious provider → Next plan/apply triggers RCE
- Atlantis Plan injection: Modify in PR
.tffile triggersatlantis plan→ RCE on the Atlantis server - Atlantis Custom Workflow: When
allow_custom_workflows=true,atlantis.yamlcan define arbitrary execution steps - Terraform Cloud Speculative Plan: Steal TFC Token → Trigger speculative plan → Execute code on Runner → Obtain injected cloud credentials
→ Read references/platform-specific.md
4.4 Other Platforms Quick Reference
| Platform | Key attack paths | Details |
|---|---|---|
| GitLab CI | Runner Token theft, shared Runner escape, CI variable injection | → platform-specific.md |
| CircleCI | Context Secrets theft, SSH Rerun, project variable import | → platform-specific.md |
| Azure DevOps | Service Connection Secret, variable/parameter injection, Runner hijack | → platform-specific.md |
| Drone CI | .drone.yml commands injection, Self-hosted Runner cloud/K8s privileges |
→ platform-specific.md |
| Buildkite | .buildkite/*.yml command injection, Agent's cloud/K8s privileges |
→ platform-specific.md |
| Serverless Framework | IAM Role privilege escalation (default AdministratorAccess), Plugin poisoning | → platform-specific.md |
| CloudFlare | Workers abuse, Zero Trust bypass | → platform-specific.md |
| Concourse / Airflow | Refer to platform-specific.md quick reference table | → platform-specific.md |
Precautions
Operational security:
- CI/CD systems usually have complete audit logs—PR creation, workflow execution, and Secrets access are all recorded
- PRs on GitHub may still be captured by SIEM even after the account is deleted
- Operations on a Self-hosted Runner may trigger EDR/HIDS alerts
- During authorized testing, the scope and impact of CI/CD testing should be confirmed with the client in advance
Secrets protection bypass:
- GitHub Actions log masking only protects rendered output; plaintext still exists in process memory
- Using base64 encoding or encryption can bypass most automatic masking mechanisms
- Derived data such as JWT signature values will not be masked
Supply chain attack escalation:
- After stealing an npm/PyPI publish token, malicious package versions can be published
- npm's
preinstall/postinstallhook executes automatically at install time - Python's
.pthfile executes automatically when the interpreter starts - A single CI/CD compromise may cascade to affect the entire supply chain (see the tj-actions/Ultralytics incident)