Los 10 riesgos de seguridad más críticos en pipelines CI/CD y cómo mitigarlos.
El OWASP Top 10 de Riesgos de Seguridad CI/CD identifica los riesgos de seguridad más significativos en los pipelines de Integración Continua y Entrega Continua. Los pipelines CI/CD son objetivos de alto valor porque tienen acceso directo al código fuente, secretos y entornos de producción. Esta guía cubre el control de flujo, la gestión de identidad, los ataques a dependencias, el envenenamiento de pipelines y más.
Los mecanismos de control de flujo insuficientes permiten a los atacantes enviar código malicioso a través de los pipelines CI/CD sin revisión o puertas de aprobación adecuadas. Esto incluye eludir las reglas de protección de ramas, la falta de aprobaciones requeridas y la falta de control sobre quién puede activar despliegues a producción.
Sin un control de flujo adecuado, una sola cuenta de desarrollador comprometida o un insider malicioso puede enviar código directamente a producción, eludiendo la revisión de código, los escaneos de seguridad y los procesos de aprobación. Esto puede llevar al despliegue de puertas traseras, código de exfiltración de datos o cargas destructivas.
# No branch protection Eanyone can push directly to main name: Deploy on: push: branches: [main] # Triggers on any push to main jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: | # Deploys immediately Eno approval gate! kubectl apply -f deploy.yaml kubectl rollout status deployment/myapp
name: Deploy on: push: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm test deploy: needs: test runs-on: ubuntu-latest environment: production # Requires manual approval steps: - uses: actions/checkout@v4 - run: | kubectl apply -f deploy.yaml kubectl rollout status deployment/myapp
Los sistemas CI/CD involucran múltiples identidades: usuarios humanos, cuentas de servicio, tokens de bots e identidades de máquinas. La gestión inadecuada de IAM permite acceso excesivamente permisivo a repositorios, pipelines y objetivos de despliegue. Las credenciales compartidas, las cuentas obsoletas y la falta de MFA agravan el riesgo.
Las identidades CI/CD comprometidas con permisos excesivos pueden modificar pipelines, acceder a secretos, alterar artefactos de construcción y desplegar en producción. Las cuentas de servicio compartidas hacen imposible auditar quién realizó una acción.
# Over-permissive workflow with admin token name: CI on: push permissions: write-all # Full permissions to everything! jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: | # Using org-wide PAT with admin access curl -H "Authorization: token ${{ secrets.ADMIN_PAT }}" \ https://api.github.com/repos/org/other-repo/contents/
name: CI on: push permissions: contents: read # Minimum required permissions packages: write # Only what's needed jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/create-github-app-token@v1 # Scoped app token id: app-token with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} repositories: other-repo # Scoped to specific repo
Los pipelines CI/CD obtienen dependencias de registros de paquetes externos (npm, PyPI, Maven, Docker Hub). Los atacantes explotan esto a través de confusión de dependencias, typosquatting, cuentas de mantenedores comprometidas y paquetes maliciosos. Una sola dependencia envenenada puede ejecutar código arbitrario durante la construcción.
Las dependencias maliciosas ejecutan código durante la instalación (scripts postinstall, setup.py), robando secretos, inyectando puertas traseras en artefactos de construcción o estableciendo persistencia. Los ataques a la cadena de suministro como SolarWinds y Codecov demuestran el impacto catastrófico.
pipeline { agent any stages { stage('Build') { steps { // No lockfile verification, no integrity checks sh 'npm install' // Fetches latest Ecould be compromised! sh 'pip install -r requirements.txt' // No hash verification } } } }
pipeline { agent any stages { stage('Build') { steps { // Use lockfile with integrity verification sh 'npm ci' // Uses package-lock.json sh 'npm audit --audit-level=high' // Python: verify hashes from lockfile sh 'pip install --require-hashes -r requirements.lock' } } stage('SCA Scan') { steps { // Software Composition Analysis sh 'trivy fs --scanners vuln,secret .' } } } }
La Ejecución Envenenada de Pipeline (PPE) ocurre cuando los atacantes pueden modificar las definiciones del pipeline CI/CD o inyectar código malicioso que se ejecuta dentro del contexto del pipeline. Esto puede suceder mediante la manipulación de archivos de configuración del pipeline en ramas, pull requests de forks o la modificación de plantillas de pipeline compartidas.
Un atacante que puede modificar la configuración del pipeline obtiene acceso a todos los secretos, credenciales y permisos disponibles para el pipeline. Puede exfiltrar secretos, alterar las salidas de construcción o desplegar código malicioso, todo dentro de un contexto de ejecución confiable.
# Runs pipeline from fork PRs with access to secrets name: CI on: pull_request_target: # Runs in base repo context with secrets! branches: [main] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} # Checks out fork code! - run: make build # Fork's Makefile executes with secrets access env: DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
# Separate workflows: untrusted build + trusted deploy name: CI on: pull_request: # No secret access for PR builds branches: [main] permissions: contents: read jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # Checks out merge commit (safe) - run: npm ci && npm test # No secrets needed for build/test - uses: actions/upload-artifact@v4 with: name: build-output path: dist/
Los Controles de Acceso Basados en Pipeline (PBAC) gobiernan a qué recursos puede acceder un pipeline: cuentas de la nube, clústeres de Kubernetes, bases de datos y servicios internos. Un PBAC insuficiente significa que los pipelines tienen un acceso más amplio del necesario, violando el principio de mínimo privilegio.
Los pipelines con privilegios excesivos pueden ser explotados para acceder a recursos mucho más allá de su alcance previsto. Un pipeline de construcción comprometido de un servicio menor podría usarse para acceder a bases de datos de producción, modificar infraestructura o pivotar a otros entornos.
# Pipeline with admin-level cloud credentials deploy: stage: deploy script: - aws configure set aws_access_key_id $AWS_ACCESS_KEY - aws configure set aws_secret_access_key $AWS_SECRET_KEY # This key has AdministratorAccess policy! - aws s3 sync dist/ s3://my-bucket/ - aws ecs update-service --cluster prod --service myapp # Same credentials could access ANY AWS resource
name: Deploy on: push: branches: [main] permissions: id-token: write # For OIDC contents: read jobs: deploy: runs-on: ubuntu-latest steps: - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456:role/deploy-s3-only # Scoped role: only s3:PutObject on specific bucket aws-region: us-east-1 - run: aws s3 sync dist/ s3://my-bucket/
Los pipelines CI/CD manejan numerosas credenciales: claves API, tokens de la nube, contraseñas de registros, claves SSH y certificados de firma. La mala higiene de credenciales incluye codificar secretos en archivos de pipeline, imprimir secretos en logs, usar almacenamiento de secretos sin cifrar y nunca rotar credenciales.
Las credenciales CI/CD filtradas son uno de los vectores de acceso inicial más comunes. Los secretos expuestos en logs de construcción, confirmados en repositorios o almacenados sin cifrado proporcionan a los atacantes acceso directo a sistemas de producción, cuentas de la nube y registros de artefactos.
#!/bin/bash # Secrets hardcoded and leaked in logs export DOCKER_PASSWORD="MyS3cret!" # Hardcoded! echo "Logging in with $DOCKER_PASSWORD" # Printed to logs! docker login -u admin -p "$DOCKER_PASSWORD" registry.example.com docker push registry.example.com/myapp:latest # AWS credentials in environment Evisible in process listing export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCY" aws s3 cp artifact.zip s3://releases/
name: Publish on: push: tags: ['v*'] jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: docker/login-action@v3 # Handles credentials securely with: registry: registry.example.com username: ${{ secrets.DOCKER_USER }} # Masked in logs password: ${{ secrets.DOCKER_TOKEN }} # Short-lived token - run: | docker build -t registry.example.com/myapp:${{ github.ref_name }} . docker push registry.example.com/myapp:${{ github.ref_name }}
Los sistemas CI/CD (Jenkins, GitLab, runners de GitHub Actions) a menudo se ejecutan con configuraciones predeterminadas inseguras. Esto incluye versiones de software desactualizadas, interfaces de gestión expuestas, funciones de seguridad deshabilitadas, acceso de red excesivamente permisivo y runners autoalojados compartidos entre proyectos.
La infraestructura CI/CD mal configurada puede ser explotada para obtener acceso no autorizado a entornos de construcción, interceptar secretos o pivotar a redes internas. Los runners autoalojados compartidos permiten ataques entre proyectos donde un workflow comprometido afecta a otros.
// Jenkins with insecure configuration // - Script console enabled without auth // - Agent-to-controller access unrestricted // - Outdated plugins with known CVEs pipeline { agent any // Runs on any available agent Eno isolation stages { stage('Build') { steps { // Running as root on shared agent sh 'whoami' // root sh 'docker build -t myapp .' } } } }
pipeline { agent { kubernetes { // Ephemeral, isolated pod per build yaml """ apiVersion: v1 kind: Pod spec: securityContext: runAsNonRoot: true runAsUser: 1000 containers: - name: builder image: builder:1.2.3 securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true """ } } stages { stage('Build') { steps { container('builder') { sh 'make build' } } } } }
Los pipelines CI/CD a menudo se integran con servicios de terceros: herramientas de calidad de código, escáneres de seguridad, sistemas de notificación y plataformas de despliegue. Estas integraciones reciben tokens de acceso y permisos, creando una cadena de confianza. El uso no gobernado significa que no hay visibilidad sobre a qué pueden acceder o hacer los servicios de terceros.
Un servicio de terceros comprometido (como la brecha de Codecov) puede acceder al código fuente, secretos y artefactos de construcción. Sin gobernanza, los equipos pueden otorgar permisos excesivos a servicios desconocidos, creando vectores de ataque invisibles en la cadena de suministro.
name: CI on: push jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm test # Unknown third-party action with full repo access - uses: random-org/code-analysis@main # Unpinned, unvetted! with: token: ${{ secrets.GITHUB_TOKEN }} # Full token access! # Uploading coverage to external service with repo token - run: | bash <(curl -s https://example.com/uploader.sh) # Remote script!
name: CI on: push permissions: contents: read jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm test # Vetted action pinned to SHA - uses: github/codeql-action/analyze@8a470fddafa5cbc14 # Pinned SHA # Upload via official CLI tool, not remote scripts - uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 with: token: ${{ secrets.CODECOV_TOKEN }} # Scoped token fail_ci_if_error: true
Los artefactos de construcción (imágenes de contenedores, binarios, paquetes) fluyen a través de los pipelines CI/CD hacia producción. Sin validación de integridad, los artefactos pueden ser alterados en cualquier punto: durante la construcción, en tránsito, en el registro de artefactos o en el momento del despliegue. Esto rompe la cadena de confianza del código a producción.
Los artefactos alterados pueden contener puertas traseras, malware o lógica modificada. Sin firma y verificación, no hay forma de detectar si un artefacto fue modificado después de la construcción. Los atacantes pueden reemplazar imágenes legítimas en registros o interceptar artefactos en tránsito.
#!/bin/bash # Build and deploy without any integrity checks docker build -t myregistry.com/app:latest . docker push myregistry.com/app:latest # On deployment side Eno verification docker pull myregistry.com/app:latest # Could be tampered! docker run myregistry.com/app:latest # Tag is mutable!
name: Build and Sign on: push: tags: ['v*'] permissions: id-token: write packages: write jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: | # Build with immutable tag (SHA) docker build -t myregistry.com/app:${{ github.sha }} . docker push myregistry.com/app:${{ github.sha }} - uses: sigstore/cosign-installer@v3 - run: | # Sign image with keyless signing (Sigstore) cosign sign myregistry.com/app:${{ github.sha }} # Generate and attach SBOM syft myregistry.com/app:${{ github.sha }} -o spdx-json > sbom.json cosign attest --predicate sbom.json myregistry.com/app:${{ github.sha }}
Los entornos CI/CD generan eventos de seguridad críticos: ejecuciones de pipelines, cambios de configuración, acceso a secretos y actividades de despliegue. Sin registro y monitoreo exhaustivos, las actividades maliciosas en el pipeline pasan desapercibidas y la respuesta a incidentes se ve gravemente obstaculizada.
Sin visibilidad de las actividades CI/CD, los atacantes pueden modificar pipelines, exfiltrar secretos y alterar artefactos sin activar ninguna alerta. La falta de registros de auditoría hace imposible determinar el alcance y el impacto de una brecha.
#!/bin/bash # Pipeline with no logging or audit trail echo "Starting deployment..." kubectl apply -f deploy.yaml echo "Done." # No record of: who triggered this, what changed, # which image was deployed, what secrets were accessed # Build logs expire after 30 days with no archival
name: Audited Deploy on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Log deployment metadata run: | echo "=== Deployment Audit Log ===" echo "Triggered by: ${{ github.actor }}" echo "Commit: ${{ github.sha }}" echo "Ref: ${{ github.ref }}" echo "Workflow: ${{ github.workflow }}" echo "Run ID: ${{ github.run_id }}" - name: Deploy with audit run: | kubectl apply -f deploy.yaml 2>&1 | tee deploy.log # Send audit event to SIEM curl -X POST "${{ secrets.SIEM_WEBHOOK }}" \ -d '{"event":"deploy","actor":"${{ github.actor }}","sha":"${{ github.sha }}"}'
| ID | Vulnerabilidad | Severidad | Mitigación Clave |
|---|---|---|---|
| CICD-SEC-1 | Mecanismos de Control de Flujo Insuficientes | Critical | Protección de ramas, aprobaciones requeridas, puertas de entorno |
| CICD-SEC-2 | Gestión Inadecuada de Identidad y Acceso | Critical | Tokens de mínimo privilegio, OIDC, MFA, rotación de credenciales |
| CICD-SEC-3 | Abuso de la Cadena de Dependencias | Critical | Lockfiles, verificación de hashes, registros privados, SCA |
| CICD-SEC-4 | Ejecución Envenenada de Pipeline (PPE) | Critical | Separar construcción/despliegue, definiciones de pipeline inmutables, controles de fork |
| CICD-SEC-5 | PBAC Insuficiente | High | Federación OIDC, roles IAM limitados, separación de entornos |
| CICD-SEC-6 | Higiene de Credenciales Insuficiente | Critical | Gestión de secretos, enmascaramiento de logs, rotación, escaneo de secretos |
| CICD-SEC-7 | Configuración Insegura del Sistema | High | Runners efímeros, no-root, actualización de plugins, aislamiento de red |
| CICD-SEC-8 | Uso No Gobernado de Servicios de Terceros | High | Inventario de servicios, fijación a SHA, sin scripts remotos |
| CICD-SEC-9 | Validación Inadecuada de Integridad de Artefactos | High | Firma con Cosign, control de admisión, procedencia SLSA |
| CICD-SEC-10 | Registro y Visibilidad Insuficientes | Medium | Registro de auditoría, integración SIEM, alertas, retención de logs |