Supply Chain Attacks in Container Ecosystems
Understanding and mitigating risks from compromised base images, dependencies, and build pipelines
The container supply chain is vast and complex. A single FROM ubuntu:latest pulls in thousands of packages, each a potential attack vector.
Attack Surface Analysis
Base Image Poisoning
Attackers compromise popular base images or create typosquatted versions:
FROM ubunu:latest # Typo - malicious image
# vs
FROM ubuntu:latest # Legitimate
Real incident: The coa npm package was typosquatted, leading to cryptocurrency theft when developers mistyped the package name.
Layer Injection
Each Docker layer is an opportunity for code injection:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install # <- Malicious dependencies here
COPY . .
CMD ["node", "server.js"]
The Dependency Problem
Modern applications pull in hundreds of transitive dependencies. Attackers target these:
Case Study: event-stream Backdoor
In 2018, the event-stream npm package (2M downloads/week) was compromised:
- Attacker gained maintainer access
- Added malicious dependency
flatmap-stream - Code specifically targeted cryptocurrency wallets
- Remained undetected for months
Build Pipeline Compromise
CI/CD systems are high-value targets with access to secrets and deployment credentials.
Attack Vector: Compromised GitHub Action
# Malicious action exfiltrates secrets
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: malicious-org/build-action@v1 # Backdoored
env:
AWS_SECRET: ${{ secrets.AWS_SECRET }}
Defense: Image Signing and Verification
Sigstore/Cosign
Sign container images to prove provenance:
# Sign image
cosign sign --key cosign.key \
registry.io/myapp:v1.0.0
# Verify before deployment
cosign verify --key cosign.pub \
registry.io/myapp:v1.0.0
Admission Control
Block unsigned images using Kyverno:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-images
spec:
validationFailureAction: enforce
rules:
- name: check-signature
match:
resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "registry.io/*"
attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
SBOM: Software Bill of Materials
Generate and verify SBOMs to track dependencies:
# Generate SBOM with Syft
syft packages registry.io/myapp:v1.0.0 -o spdx-json > sbom.json
# Scan for vulnerabilities with Grype
grype sbom:sbom.json
Minimal Base Images
Reduce attack surface by using distroless or scratch images:
# Multi-stage build
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server
# Distroless final image
FROM gcr.io/distroless/static-debian11
COPY --from=builder /app/server /server
CMD ["/server"]
Benefits:
- No shell (prevents reverse shells)
- No package manager (blocks lateral movement)
- Minimal CVE exposure
Runtime Monitoring
Detect unexpected behavior with Falco:
- rule: Unexpected outbound connection
desc: Container making connection to unexpected IP
condition: >
outbound and container and
not fd.sip in (allowed_ips) and
not fd.sport in (allowed_ports)
output: >
Suspicious outbound connection
(pod=%k8s.pod.name dest=%fd.rip:%fd.rport)
priority: WARNING
Best Practices Checklist
- Pin base image versions - Use SHA256 digests, not tags
- Scan images in CI/CD - Trivy, Grype, Snyk
- Sign and verify all images - Cosign, Notation
- Generate and track SBOMs - Know your dependencies
- Use minimal base images - Distroless, Alpine, scratch
- Implement network policies - Limit egress from containers
- Monitor for drift - Detect unauthorized changes
- Regular dependency updates - Automated Dependabot/Renovate
The supply chain is your weakest link. Verify everything, trust nothing.