北美时间11月26日,Kubernetes爆出严重安全漏洞,该漏洞由Rancher Labs联合创始人及首席架构师Darren Shepherd发现。该漏洞CVE-2018-1002105(又名Kubernetes特权升级漏洞,https://github.com/kubernetes/kubernetes/issues/71411)被确认为严重性9.8分(满分10分),恶意用户可以使用Kubernetes API服务器连接到后端服务器以发送任意请求,并通过API服务器的TLS凭证进行身份验证。这一安全漏洞的严重性更在于它可以远程执行,***并不复杂,不需要用户交互或特殊权限。

 

漏洞被发现并验证后,Kubernetes快速响应并已经发布了修补版本v1.10.11、v1.11.5、v1.12.3和v1.13.0-rc.1。仍在使用Kubernetes v1.0.x至Kubernetes v1.9.x版本的用户,被建议即刻停止并升级到修补版本。


本文由该漏洞的发现者、Rancher Labs联合创始人及首席架构师Darren Shepherd所写。他描述了自己发现这一漏洞的完整经过,剖析了问题的机制与原理,并分享了相应的解决方案以及他本人对Kubernetes、对开源社区的看法。


Kubernetes首个严重安全漏洞发现者,谈发现过程及原理机制_第1张图片

Rancher Labs联合创始人及首席架构师 Darren Shepherd,同时也是Docker生态核心组织Docker治理委员会(DGAB)的全球仅有的四位个人顶级贡献者之一。


Amazon ALB的问题


这一切都始于2016年,当时Rancher Labs刚发布了Rancher 1.6。2016年年中的时候,亚马逊发布了ALB,这是一个新的HTTP(7层)负载均衡器。ALB的设置比ELB容易得多,因此我们会建议用户使用ALB。随后很快,我们开始收到有关ALB后端设置失败的报告,很多随机请求只会得到401、403、404、503的报错。然而,Rancher Labs的团队无法重现这些错误,我们从社区成员那里得到的日志都没有没法成为参考。我们看到了HTTP请求和响应,但无法将其与代码相关联。那个时候,我们只好认为是因为ALB发布不久、产品本身可能存在些错误。除了ALB,我们之前从未遇到任何其他负载均衡器的问题。因此那时,我们只得最终告诉用户不要使用ALB。


时间到了今年8月,又有Rancher社区成员向Rancher 2.1提交了同样的问题(https://github.com/rancher/rancher/issues/14931)。仍和以前一样,使用ALB会导致奇数401和403错误。这极大地引起了我的关注,因为Rancher 1.x和2.x之间没有共同的代码,而且ALB现在应该也已经相当成熟了。反复深入研究后,我发现问题与不处理非101响应和反向代理缓存TCP连接有关。若您想要真正理解这个问题,您必须了解TCP连接重用、websockets如何使用TCP连接以及HTTP反向代理。


TCP连接重用


在一种非常天真的HTTP方法中,客户端将打开TCP socket,发送HTTP请求,读取HTTP响应,然后关闭TCP socket。很快你就会发现你花了太多时间打开和关闭TCP连接。因此,HTTP协议具有内置的机制,以便客户端可以跨请求重用TCP连接。


WebSockets


Websockets是双向通信,其工作方式与HTTP请求/响应流不同。为了使用websockets,客户端首先会发送HTTP升级请求,服务器会以HTTP 101 Switch Protocols响应来响应这一请求。收到101之后,TCP连接将专用于websocket。在TCP连接的剩余生命周期中,它是被认为是专用于该websocket连接的。这就意味着此TCP连接永远不会被重新使用


HTTP反向代理


HTTP反向代理(负载均衡器是一种反向代理)从客户端接收请求,然后将它们发送到不同的服务器。对于标准HTTP请求,它只写入请求,读取响应,然后将响应发送到客户端。这种逻辑相当直接,而且Go也包含一个内置的反向代理:https://golang.org/pkg/net/http/httputil/#ReverseProxy。


相比之下Websockets就复杂一点。对于websocket,你必须查看请求,看到它是一个升级请求,然后发送请求,读取101响应,然后劫持TCP连接,然后开始来回复制字节。对于反向代理,它不会在此之后查看连接的内容,它只是创建一个“废弃管道”。标准Go库中不存在此逻辑,许多开源项目都编写了代码来执行此操作。


错误所在


关于错误所在,太长不看版的解释是,Kubernetes在启动“废弃管道”之前没有检查101响应。在代码的防御中,不检查101是挺常见的。(这也是我们上文所说的Rancher用户会发现的Rancher 1.x和Rancher 2.x的问题的原因,即使Rancher 1.x和Rancher 2.x使用的是完成不同的代码。)错误的场景如下:


  1. 客户端发送websocket升级请求

  2. 反向代理向后端服务器发送升级请求

  3. 后端服务器以404响应

  4. 反向代理启动复制循环并将404写入客户端

  5. 客户端看到404响应并将TCP连接添加到“空闲连接池”

 

在这种情况下,如果客户端重新使用TCP连接,它将向TCP连接写入请求,它将通过反向代理中的“废弃管道”并将其发送到前一个后端。通常这不会很糟糕,例如在负载均衡器的情况下,因为所有请求都会转到同一组同类后端。但是,当反向代理是智能的,并且是由其执行身份验证、授权和路由(即Kubernetes所做的全部工作)时,就会出现此问题。


安全漏洞


因为101未被处理,所以客户端最终使用TCP连接,该连接是对某些先前访问的后端服务的“废弃管道”。这将导致特权升级。问题是,Kubernetes将仅在反向代理中执行许多请求的授权。这意味着如果我执行一个授权失败的websocket请求路由到一个kubelet,我可以保持与该kubelet的持久连接,然后运行我选择的任何API命令,无论我是否被授权。例如,您可以在任何pod上运行exec并复制出secrets。因此,在这种情况下,已经授权的用户基本上可以获得对kubelet的完全API访问(同样的事情适用于通过kube-aggregation运行的服务)。


当您添加另一个反向代理时,会出现另一个问题。在这种情况下,您将HTTP负载均衡器放在Kubernetes API(非4层负载均衡器)之前。如果执行此操作,那个通过了身份验证的、运行着“废弃管道” 的TCP连接,将会被添加到一个任何用户都可以访问的空闲池中。那么,用户A创建了TCP连接,之后用户B仍可重新使用该连接。这样一来,未经过身份验证的用户就可以访问您的Kubernetes集群了


此时你可能会感到恐慌,因为当然每个人都会在kube-apiserver前放置一个负载均衡器。唔……首先,您必须运行HTTP负载均衡器,而不是TCP负载均衡器。负载均衡器必须了解HTTP语义才能产生此问题。其次,幸运的是大多数反向代理并不关心101个回复。这就是为什么这个问题其实(在不少开源项目中)存在已久而未被发现的原因。大多数负载均衡器在看到升级请求而非101响应后不会重用TCP连接。所以,如果您会受到这一漏洞的影响,那么您的Kubernetes设置应该已经不可靠了,您应该能看到随机失败或无法完成的请求。至少我知道ALB就是这样工作的,所以你升级到了已修补该漏洞的Kubernetes版本之前,不要使用ALB。


简而言之,Kubernetes的这一安全漏洞会允许具有正确权限的任何经过身份验证的用户获得更多权限。如果您正在运行硬件多租户集群(内含不受信任的用户),您确实应该担心并且及时应对。如果您不担心用户主动互相***(大多数多租户集群都是这样),那么不要惊慌,只需升级到已修补该漏洞的Kubernetes版本即可。最坏的情况,如果真的有未经身份验证的用户可以进入您的集群,您的负载均衡器也有可能会阻止这种情况。只要不是将API暴露给世界,并且有在其上放置一些适当的ACL,也许你的集群也还是安全的。


Rancher为Kubernetes保驾护航


对于使用Rancher Kubernetes平台的用户,你们更无须紧张。


对于把集群部署在内网的用户,完全不需要过于担心此问题,因为外部无法直接***。


通过Rancher2.0或RKE部署的kubernetes集群的用户同样不用过于担心。因为通过Ranche2.0或RKE部署的集群默认是禁止和匿名用户访问。针对通过pod exec/attach/portforward权限提权问题,目前Kubernetes发布通用的修复方法是通过升级到指定Kubernetes版本来修复,针对此Rancher也已经发布修复程序,具体修复方法请参考:https://forums.rancher.com/t/rancher-security-advisory-kubernetes-cve-2018-1002105/12598


感谢开源


我深刻地感到,这次Kubernetes的这个安全漏洞的最初发现、修复和最终交付,证明了开源社区强大的生命力。我第一次发现这个问题,也是因为Rancher的非付费开源用户给予我们的反馈。事实上,我们已经确认了这一问题并没有影响Rancher 2.x的付费客户,因为Rancher的HA架构恰好否定了ALB的行为,但我们还是去研究并解决了这个问题,因为我们太爱我们的开源用户了。也正是在研究和修复这个问题的过程中,我发现了Kubernetes自身存在的安全隐患,并通过已建立的安全公开流程向Kubernetes社区反馈了该问题。