前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >K8S 生态周报| 深入源码剖析 Kubernetes 的漏洞

K8S 生态周报| 深入源码剖析 Kubernetes 的漏洞

作者头像
Jintao Zhang
发布2022-12-07 14:40:14
4450
发布2022-12-07 14:40:14
举报
文章被收录于专栏:MoeLove

“「K8S 生态周报」内容主要包含我所接触到的 K8S 生态相关的每周值得推荐的一些信息。欢迎订阅知乎专栏「k8s生态」[1]。 ”

大家好,我是张晋涛。

上游进展

Kubernetes 发布了 v1.22.16 和 1.23.14,1.24.8,1.25.4等版本,其中最重要的就是以下两个安全漏洞了。

CVE-2022-3162

当用户被授权允许在集群范围内 list 或 watch 某个 namespace 范围的自定义资源时,可以读取在同一 API 组下,不同类型的其他自定义资源。

这个漏洞影响范围是:

  • kube-apiserver v1.25.0 - v1.25.3
  • kube-apiserver v1.24.0 - v1.24.7
  • kube-apiserver v1.23.0 - v1.23.13
  • kube-apiserver v1.22.0 - v1.22.15
  • kube-apiserver < v1.21.?

CVE-2022-3294

我们平时如果想要进入 Pod 内进行操作(即 kubectl exec)的时候,它的过程是:

  • kubectl -> kube-apiserver:

kubectl 会请求 /api/v1/namespaces/<ns>/pods/<pod>/exec 到 kube-apiserver,实际的代码也很简单,可以看到是一个 POST 请求,并且按照传递的参数构造请求。

代码语言:javascript
复制
 fn := func() error {
  restClient, err := restclient.RESTClientFor(p.Config)
  if err != nil {
   return err
  }

  // TODO: consider abstracting into a client invocation or client helper
  req := restClient.Post().
   Resource("pods").
   Name(pod.Name).
   Namespace(pod.Namespace).
   SubResource("exec")
  req.VersionedParams(&corev1.PodExecOptions{
   Container: containerName,
   Command:   p.Command,
   Stdin:     p.Stdin,
   Stdout:    p.Out != nil,
   Stderr:    p.ErrOut != nil,
   TTY:       t.Raw,
  }, scheme.ParameterCodec)

  return p.Executor.Execute("POST", req.URL(), p.Config, p.In, p.Out, p.ErrOut, t.Raw, sizeQueue)
 }
  • kube-apiserver -> 目标节点的 kubelet

当 kube-apiserver 接收到来自 client 的请求后,就需要构造新的请求然后到目标节点上执行了。 这部分的最直接的代码是如下的内容,会有一个 ExecLocation 函数,用来返回目标位置。

代码语言:javascript
复制
func ExecLocation(
 ctx context.Context,
 getter ResourceGetter,
 connInfo client.ConnectionInfoGetter,
 name string,
 opts *api.PodExecOptions,
) (*url.URL, http.RoundTripper, error) {
 return streamLocation(ctx, getter, connInfo, name, opts, opts.Container, "exec")
}

当然,和节点信息有关的部分,是在 streamLocation 函数的部分来获取的,如下:

代码语言:javascript
复制
container, err = validateContainer(container, pod)
 if err != nil {
  return nil, nil, err
 }

 nodeName := types.NodeName(pod.Spec.NodeName)
 if len(nodeName) == 0 {
  // If pod has not been assigned a host, return an empty location
  return nil, nil, errors.NewBadRequest(fmt.Sprintf("pod %s does not have a host assigned", name))
 }
 nodeInfo, err := connInfo.GetConnectionInfo(ctx, nodeName)
 if err != nil {
  return nil, nil, err
 }
 params := url.Values{}
 if err := streamParams(params, opts); err != nil {
  return nil, nil, err
 }
 loc := &url.URL{
  Scheme:   nodeInfo.Scheme,
  Host:     net.JoinHostPort(nodeInfo.Hostname, nodeInfo.Port),
  Path:     fmt.Sprintf("/%s/%s/%s/%s", path, pod.Namespace, pod.Name, container),
  RawQuery: params.Encode(),
 }
 return loc, nodeInfo.Transport, nil

通过以上的步骤,kube-apiserver 就知道要跟哪个 Node 连接了。

不过这里有个需要注意的内容,上面的 ResourceGetter 是个通过 ResourceLocation 获取资源的接口,这也是这个漏洞中的核心。

代码语言:javascript
复制
func ResourceLocation(getter ResourceGetter, connection client.ConnectionInfoGetter, proxyTransport http.RoundTripper, ctx context.Context, id string) (*url.URL, http.RoundTripper, error) {
 schemeReq, name, portReq, valid := utilnet.SplitSchemeNamePort(id)
 if !valid {
  return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid node request %q", id))
 }
 info, err := connection.GetConnectionInfo(ctx, types.NodeName(name))
 if err != nil {
  return nil, nil, err
 }

+   if err := proxyutil.IsProxyableHostname(ctx, &net.Resolver{}, info.Hostname); err != nil {
+       return nil, nil, errors.NewBadRequest(err.Error())
+   }

 // We check if we want to get a default Kubelet's transport. It happens if either:
 // - no port is specified in request (Kubelet's port is default)
 // - the requested port matches the kubelet port for this node
 if portReq == "" || portReq == info.Port {
  return &url.URL{
    Scheme: info.Scheme,
    Host:   net.JoinHostPort(info.Hostname, info.Port),
   },
   info.Transport,
   nil
 }

-   if err := proxyutil.IsProxyableHostname(ctx, &net.Resolver{}, info.Hostname); err != nil {
-       return nil, nil, errors.NewBadRequest(err.Error())
-   }

 // Otherwise, return the requested scheme and port, and the proxy transport
 return &url.URL{Scheme: schemeReq, Host: net.JoinHostPort(info.Hostname, portReq)}, proxyTransport, nil
}

上面是在 v1.22.16 中的修复,可以看到实际是把 proxyutil.IsProxyableHostname 的判断逻辑移动到了前面,在之前有可能会跳过此判断。

如果跳过了这个判断,就可能会导致原本经过认证的请求被发送到 API Server 所在的私有网络(说直白点,就是有可能会篡改目标地址)。

所以,这个漏洞的触发条件也很明确,只有能篡改 Node 地址才会受到影响。

受影响的版本如下:

  • Kubernetes kube-apiserver ≤ v1.25.3
  • Kubernetes kube-apiserver ≤ v1.24.7
  • Kubernetes kube-apiserver ≤ v1.23.13
  • Kubernetes kube-apiserver ≤ v1.22.15

解决办法要么是升级 kube-apiserver,要么可以设置 egress proxy 来进行管理。 但是如果升级 kube-apiserver 也有可能会导致一些依赖于 Node/Proxy 的子资源场景下的不可用,需要注意。

本周 Kubernetes v1.26.0-rc.0 也发布了,按照之前的习惯,正式版和这个版本中差别就不会很大了。 下期我会写一篇介绍 v1.26 版本中重点需要关注的内容,敬请期待!

好了,以上就是本次的全部内容,我们下期再聊!


参考资料

[1] k8s生态: https://zhuanlan.zhihu.com/container

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-11-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 MoeLove 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 上游进展
    • CVE-2022-3162
      • CVE-2022-3294
        • 参考资料
    相关产品与服务
    容器服务
    腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档