个人博客导航页(点击右侧链接即可打开个人博客):大牛带你入门技术栈
概述
云原生(Cloud Native)是一套技术体系和方法论,它由2个词组成,云(Cloud)和原生(Native)。云(Cloud)表示应用程序位于云中,而不是传统的数据中心;原生(Native)表示应用程序从设计之初即考虑到云的环境,原生为云而设计,在云上以最佳状态运行,充分利用和发挥云平台的弹性和分布式优势。
云原生的代表技术包括容器、服务网格(Service Mesh)、微服务(Microservice)、不可变基础设施和声明式API。更多对于云原生的介绍请参考CNCF/Foundation。
笔者将“云原生安全”抽象成如上图所示的技术沙盘。自底向上看,底层从硬件安全(可信环境)到宿主机安全 。将容器编排技术(Kubernetes等)看作云上的“操作系统”,它负责自动化部署、扩缩容、管理应用等。在它之上由微服务、Service Mesh、容器技术(Docker等)、容器镜像(仓库)组成。它们之间相辅相成,以这些技术为基础构建云原生安全。
我们再对容器安全做一层抽象,又可以看作构建时安全(Build)、部署时安全(Deployment)、运行时安全(Runtime)。
在美团内部,镜像安全由容器镜像分析平台保障。它以规则引擎的形式运营监管容器镜像,默认规则支持对镜像中Dockerfile、可疑文件、敏感权限、敏感端口、基础软件漏洞、业务软件漏洞以及CIS和NIST的最佳实践做检查,并提供风险趋势分析,同时它确保部分构建时安全。
容器在云原生架构下由容器编排技术(例如Kubernetes)负责部署,部署安全同时也与上文提及的容器编排安全有交集。
运行安全管控交由HIDS负责(可参考《保障IDC安全:分布式HIDS集群架构设计》一文)。本文所讨论的范畴也属于运行安全之一,主要解决以容器逃逸为模型构建的风险(在本文中,若无特殊说明,容器指代Docker)。
对于安全实施准则,我们将其分为三个阶段:
- 攻击前:裁剪攻击面,减少对外暴露的攻击面(本文涉及的场景关键词:隔离);
- 攻击时:降低攻击成功率(本文涉及的场景关键词:加固);
- 攻击后:减少攻击成功后攻击者所能获取的有价值的信息、数据以及增加留后门的难度等。
近些年,数据中心的基础架构逐渐从传统的虚拟化(例如KVM+QEMU架构)转向容器化(Kubernetes+Docker架构),但“逃逸”始终都是企业要在这2种架构下所面对的最严峻的安全问题,同时它也是容器风险中最具代表性的安全问题。笔者将以容器逃逸为切入点,从攻击者角度(容器逃逸)到防御者角度(缓解容器逃逸)来阐述容器安全的实践,从而缓解容器风险。
容器风险
容器提供了将应用程序的代码、配置、依赖项打包到单个对象的标准方法。容器建立在两项关键技术之上:Linux Namespace和Linux Cgroups。
Namespace创建一个近乎隔离的用户空间,并为应用程序提供系统资源(文件系统、网络栈、进程和用户ID)。Cgroup强制限制硬件资源,如CPU、内存、设备和网络等。
容器和VM不同之处在于,VM模拟硬件系统,每个VM都可以在独立环境中运行OS。管理程序模拟CPU、内存、存储、网络资源等,这些硬件可由多个VM共享多次。
容器一共有7个攻击面:Linux Kernel、Namespace/Cgroups/Aufs、Seccomp-bpf、Libs、Language VM、User Code、Container(Docker) engine。
笔者以容器逃逸为风险模型,提炼出3个攻击面:
- Linux内核漏洞;
- 容器自身;
- 不安全部署(配置)。
1. Linux内核漏洞
容器的内核与宿主内核共享,使用Namespace与Cgroups这两项技术,使容器内的资源与宿主机隔离,所以Linux内核产生的漏洞能导致容器逃逸。
内核提权VS容器逃逸
通用Linux内核提权方法论
-
信息收集:收集一切对写exploit有帮助的信息。 如:内核版本,需要确定攻击的内核是什么版本? 这个内核版本开启了哪些加固配置? 还需知道在写shellcode的时候会调用哪些内核函数?这时候就需要查询内核符号表,得到函数地址。 还可从内核中得到一些对编写利用有帮助的地址信息、结构信息等等。
-
触发阶段:触发相关漏洞,控制RIP,劫持内核代码路径,简而言之,获取在内核中任意执行代码的能力。
-
布置shellcode:在编写内核exploit代码的时候,需要找到一块内存来存放我们的shellcode 。 这块内存至少得满足两个条件:
- 第一:在触发漏洞时,我们要劫持代码路径,必须保证代码路径可以到达存放shellcode的内存。
- 第二:这块内存是可以被执行的,换句话说,存放shellcode的这块内存具有可执行权限。
-
执行阶段
- 第一ÿ