2017 OpenStack Days China不仅仅是中国OpenStack技术与方案的展示,更是汇聚了来自亚洲各国OpenStack大咖的技术趴!最全面的行业案例展示分享、最细致的讲解如何从开发到运维、最丰富的技术干货、最好的参会体验
各位来宾,大家下午好!我是来自奇虎360运营安全研究部的安全研究员张谦,我想先在这儿介绍一下我们团队,大家关注虚拟化产品的安全研究上面可能会对我们团队稍微熟悉一点。我们团队成立于2015年年底,主要的研究方向是虚拟化产品的安全漏洞,我们团队先后在虚拟化产品,比如说Docker、VMware,从2015年底到现在一共发现了几十枚安全漏洞,都通报给了CVD,都有相应的编码。说起我们团队在今年3月份全球顶级的黑客大会上,加了一个逃逸的比赛项目,因为这个比赛是持续三天,最后一天比赛上我们团队贡献了一枚VMware的Docker逃逸,我们最后一天搞了一个什么项目?可以通过一个网页,攻击者可以通过网页完成本地拿我们的VMware Docker逃避直接到宿主机。安全研究的思路以攻促防,以我们安全研究的攻击思路和这些年的技术积累,帮助360在旗下的安全产品上提供一些技术上其他团队没有办法预约的技术壁垒,把这些相应的攻防经验融入到360旗下的安全产品里面。
刚才前几位嘉宾都讲解了他们公司也好或者他们公司产品的组织架构,我本身是一个技术出身的,所以我今天带来的干货是一些跟技术关联比较大的东西。因为我们团队在2016年上半年完成了Docker逃逸,从Docker容器逃逸到整个Docker的物理机里边。今天我就给大家讲讲Docker逃逸与防护策略。
Docker可能在座各位应该在云安全行业里面也是不陌生的这么一个东西,也是前一阵炒得比较火,Docker本身是开源项目,是用Go语言写的开源项目,是一个非常轻量化的解决方案,实际上它的基础就是Linux(LXC),让用户感觉处在真实一个虚拟机里面一样。这是一个对比图,一个传统的虚拟机还有Docker容器带来的虚拟化,可以看上面,传统虚拟机需要虚拟出硬件设备,也就是需要虚拟处一个操作系统出来。Docker实际上利用Docker容器和真正的物理机,实际上用的都是一个操作系统的内核,也就是说它在资源消耗上少了一个Host OS本身需要消耗很多资源,相比传统的虚拟机来说这是它的优点。这是从数量级上面的对比,Docker在启动的时候实际上就跟Linux的进程一样,启动的是时候秒级,虚拟机至少需要几十秒时间,这是它的硬盘的消耗。假设我们现在有这么一种应用场景,比方说一台16核32G内存的主机上面需要跑几百个应用,每个应用上面是一个网站,虚拟机需要至少做到两点,一种是资源隔离,这个虚拟机的操作不能影响到其他虚拟机,首先假如说我们在这样一台基础上开500个虚拟机,本身虚拟化所带来的消耗是非常严重的,但是如果用Docker,Docker本身就相当于是一个Linux上的进程,开500个进程在这样一台配置的主机上绝对没有什么问题的。
接下来给大家讲讲Docker的一些核心技术,实际上Docker本身因为本身没有做完全的虚拟化,很多东西用的都是里面支持的一些内核特性。比如说第一个是一个命名空间,Docker本身提供了6个命名空间,这个命名空间有没有用?Docker容器在不同的命名空间里边是不能影响其他容器的。Docker一共提供了6种NameSpace,就是主机名用户名@之后有一个主机名,Docker就可以就把这个主机隔离开,有一个信号量,还有进程编号,在初始化之后有一个引进进程,在Docker容器里面编号启动之后全都是从E开始的。还有网络设备,也是全隔离的。还有文件系统,你在Docker容器里做文件操作是不能影响其他容器的。User和Mount是隔离用户组,每个用户在容器里面在里面的权限是不一样的。这是系统调用参数,从名字上来看,可以看clone,有UTS对应,IPC对应,PID也是对应,唯一不同的是Mount,实际上Mount的历史实际上是第一个支持的NameSpace,当然了内核维护者没有想到之后还会出来12345,所以它在实现Mount的时候就直接就是NameSpace。
这里有一些技术,就是扫代码,刚才前几位都讲产品,我给大家换换脑子。在NameSpace里面有一个描述符,就叫做nsproxy,这个count是引用基数,还有Mount文件系统的NameSpace,还有PID,刚才说有6个,唯一缺少的是User的NameSpace,它所属于哪个User NameSpace,实际上所有东西都是用在这几个数据结构里面。
同时在Linux下编程比较多,会用到clone的函数,这里需要这么几个参数,其中的flags参数就是刚才说的clone里面需要什么NameSpace就把相应的值传到里面。接下来我们从源码上分析一下clone系统调用是怎样把各种NameSpace创建出来,在Docker里面怎么把内核里面把容器创造出来。
这是函数调用站的一个图,首先是用户传到clone,平时咱们用Linux编程的时候用到clone,fork的话全都是0,传到do-fork,复制到dup,在所有数据结构做复制,拷贝进程的证书,进程证书很有意思,如果这个clone flags从一开始传递过来,会创建一个新的NameSpace,这个函数很有意思,在Docker容器里面,一进去之后,一看咱们在里面已经入驻的选项,这里面有几个Full-SET,把证书的权限全都是Full-SET,把所有权限全都付给Docker容器,实际上Linux的权限划分一共有38个,38个每个全景都是一个位,所有的权限通过64位的数全部都能保存下来,实际上这个就是64位无符号整形的数,把所有的权限全都付给全部的权限。证书也属于User NameSpace,就属于从上一步传过来的这个。NameSpace创立完了之后就该拷贝其他的五个NameSpace了,接下来执行,执行之后这几个新的NameSpace也调相应的函数,把新创建的NameSpace传到里面,也就是说这几个创建出来的新的,比如UTS、IPC都是新创建出来的。
刚刚讲完NameSpace,实际上就是把资源做一个资源隔离。咱们举一个非常简单的例子,就是以主机名称做非常简单的例子来看内核是怎么实现资源隔离的。这个NameSpace就是主机的NameSpace一共有这么几个成员,其中附名字的这块就是Name,登录了名字之后就保存在Name里面,这是相应的数据结构。这边是现在有一个设置主机名称,这边进来之后会先检查当前的进程是否有权限修改UTS所属的User里面是否有这个权限,在创建进程的时候实际上已经看到本身给进程附加上所有的Full-SET。接下来检查程度,把用户传过来的HostName拷贝到这个结构里。
我们来看登录之后,内核怎么取?就调UTSName,把这个Name取下来,把Name直接拷贝到用户空间,这个Name就是从用户开始传过来的,这是它的长度,把HostName拷贝到用户空间里面。实际上它怎么做到资源隔离呢?实际上把不同的变量存到不同的NameSpace里面,每次设置的时候,都会从当前的进程里面的NameSpace里面去取。
这个权限检查,能看到刚才有一个权限检查,检查当前UTS NameSpace所属的User NameSpace是否有权限,再往深一步怎么做?首先把当前进程的证书以及需要在哪个NameSpace里面有传到这个函数里面,有三个参数,一个是当前进程的证书,还有目标,我需要在这个NameSpace,要去检查刚才说的64位,看这里面有没有权限,如果说没有,就返回权限不足,基本上在Linux里面每一次做权限检查的时候都会调内核里面的这个函数,比如你进入一个你不该进入的目录,它实际上也会调它。
下一步假如说你当前并不是在同一个NameSpace里边,这边有一个init-User-ns,一个是普通账户,登录之后普通账户要锁在NameSpace里面,也是这个init-User-NameSpace,它所有的权限都是满的,普通用户这边是0,这边检查一下当前进程的User-NameSpace是不是附类,如果当前进程创建是目的User NameSpace的附类它拥有全部的权限。这是整个跟NameSpace相关的系统调用,刚才unshare用得非常频繁,它的作用是不需要启动新进程就可以达到NameSpace隔离的效果。这个setes是已经存在的NameSpace,通过两个Docker符号,当前进入PID,在文件系统里面创建自己的目录,目录名字就是以进程PID来命名的,可以看出来上面有这些号,这些号就会在这里面去用。
下面讲Docker的Control Groups,是做系统资源,比如CIO或者网络的流量控制,可以把资源做切片,比现在Control Groups可以对容器的内存CPU做一些限制,我觉得这个容器我不可能给它附100%的CPU资源,我就可以通过CGroups来限制它,像CPU,还有块设备,还有网络的,还有内存的,这是CGroups的术语,这是典型的CGroups用文件系统来实现的一个和内核交互的东西,我们可以设置CGroups,像写一个文件一样,比方我在这里边新建一个CGroups,就是vsec,我可以限制里面有一些系统参数,我可以设置直接写比如说5万,写到这个文件里边,比如说cpu.cfs-quota-us,内核处理的时候就会直接限制CPU使用的百分比。下面是一个例子,我可以写这样一个脚本,这个脚本就是写的一个死循环,本身不做Control Groups是占用100%的CPU,接下来通过CGroups直接写到这个文件里边的话,再把PID6814写到tasks文件里面就可以直接变成50%,写到这个文件里面,它是sys文件系统,实际上还是在内核调用还是虚拟文件系统能直接找到这个文件相当于函数,在这个里面把进程的PID加到列表里边,当进程使用CPU资源的时候就会从这5万里边去限制CPU应该管理员希望占用的百分比。
AUFS时间关系,先略过去,实际上相比Linux启动的时候,就好像Docker在启动进项的过程,它的过程就好像Linux启动内核初始化的时候用的步骤,实际上是一样的。时间关系,我把这块先略过去。
说几个安全策略方面的东西,比如Docker Swarm有一个集群管理配置问题,配置失误在哪?Docker Swarm会监听一个端口,2375,官方配置绑定的IP是0.0.0.0,假如你要这么配置,随便一个人都会远程访问Docker容器。这是Docker Swarm集群的图,下面从Docker文章里面出来,绑定的IP地址就是0.0.0.0,任何人都可以在网上直接接入。
这是Linux权限模型,大家可以看到Docker本身在启动的时候会禁用一些权限,实际上有的权限即使不禁用,Docker的使用者在容器里面也是没有办法用的,为什么?举个例子,就比方说我想往内核里边加一个内核模块,这个模块会调这么一个函数,把这个权限能否加模块的权限传到里面,又会去掉它,这个东西就会检查把目的的init-User-ns是否有CAP-SYS这个权限。Docker所属的User-ns和init-User-ns不一样,首先这一块条件就是不满足的,因为本身它俩并不是在同一个NameSpace里边。这边要求在这个NameSpace里边,在init-User-ns里边,到了这一步之后直接返回权限不足。咱们想一想也能知道,Docker肯定是不允许你在容器里面,即使在容器里面是入权限也不可能加载一个模块进去,加载一个模块就相当于你这样一个权限了,你有这么大的权限,你可以在整个物理机上面做任何你想干的事,这肯定是不被允许的,即使Docker把这个权限附给进程了,进程也是没有办法用的。这是Docker Linux另外一个安全特性,它可以限制容器里面所调动的系统API,这个东西需要看内核是否支持这个选项,现在默认全都是支持这个权限,全都是默认启用的。这是Docker限制44个系统调用,比如说其中有一个设置系统实时键,这个肯定会被禁用,为什么?因为Docker本身没有关系时间的NameSpace,没有时间做容器的隔离,要是这个容器把系统时间改了,肯定是影响其他容器的。这是Docker的Seccomp的系统文件,是一个白名单,在内核里面以什么样的方式来实现的,时间关系,这边也得跳过去。
最后我给大家展示一下我们团队在研究演示Docker逃逸的演示。
浏览5689次
浏览9177次
浏览3266次
浏览2786次
浏览5241次
浏览1251次
2025-01-08 昆明
2025-06-20 深圳
2025-04-19 南京
2025-08-15 上海
打开微信扫一扫,分享到朋友圈