docker.sock 的测试和本质
在使用 Docker 的过程中,总会看到 /var/run/docker.sock 的身影。docker 是客户端程序,dockerd 是守护进程,两者的通信需要通过 docker.sock。
- 它究竟是什么?
- 为什么运行
docker命令需要root权限或加入docker组? - 为什么说映射
docker.sock到容器中是不安全的行为?
docker.sock 的测试
如何手动、朴素地和 docker.sock 通信,戳破它的神秘感?
使用第三方工具
方法 A:使用 curl
curl 原生支持通过参数指定 Unix Socket 文件,并手动构造 HTTP 请求:
1 | curl --unix-socket /var/run/docker.sock http://localhost/containers/json |
参数说明:
--unix-socket /var/run/docker.sock:掐断网卡和TCP协议栈,直接把数据打包塞进这个本地文件对应的内核内存里http://localhost是一个占位符(因为UDS不需要IP),而/containers/json就是Docker官方定义的“列出正在运行的容器”的HTTP REST API路由
返回结果:
1 | [{"Id":"919cacb14bff1838b3cb0c3c1332f53d0692c89018d5282571f497594171fe47","Names":["/reverent_meninsky"],"Image":"nginx","ImageID":"sha256:4c41e2d0a3a55b325ed572cd0200bdb72af1433c3daa2bca6ef8eea68ce504a2","ImageManifestDescriptor":{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:59a9ae22b8933b536391e6c1708fa761297e922809c97675c5a56ba316be62af","size":2290,"annotations":{"com.docker.official-images.bashbrew.arch":"amd64","org.opencontainers.image.base.digest":"sha256:4876fdea29cac1a33a3195f1eb1952feb1fca7dd6dd00be3e2f1879cf20d34d2","org.opencontainers.image.base.name":"debian:trixie-slim","org.opencontainers.image.created":"2026-10-21T22:16:50Z","org.opencontainers.image.revision":"6fabb4c960a02a1e576d2789974c45f4cd2aa854","org.opencontainers.image.source":"https://github.com/nginx/docker-nginx.git#6fabb4c960a02a1e576d2789974c45f4cd2aa854:mainline/debian","org.opencontainers.image.url":"https://hub.docker.com/_/nginx","org.opencontainers.image.version":"1.31.8"},"platform":{"architecture":"amd64","os":"linux"}},"Command":"/docker-entrypoint.sh nginx -g 'daemon off;'","Created":1793025621,"Ports":[{"IP":"","PrivatePort":80,"Type":"tcp"}],"Labels":{"maintainer":"NGINX Docker Maintainers <[email protected]>"},"State":"running","Status":"Up 4 minutes","HostConfig":{"NetworkMode":"bridge"},"Health":{"Status":"none","FailingStreak":0},"NetworkSettings":{"Networks":{"bridge":{"IPAMConfig":null,"Links":null,"Aliases":null,"DriverOpts":null,"GwPriority":0,"NetworkID":"7990194027e5bd07a1a19a1e045adb037312e1a29c65d69153c5a94f29548d0b","EndpointID":"2377548cf9d62c084155f0873b3b56d46faf2e89dbe21da0a468049748cce234","Gateway":"172.17.0.1","IPAddress":"172.17.0.2","MacAddress":"c2:4e:9e:85:67:75","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"DNSNames":null}}},"Mounts":[]}] |
方法B:使用 nc(Netcat)
- 连接
Socket文件:1
nc -U /var/run/docker.sock
- 手动输入
HTTP请求报文(两次回车发送)1
2GET /containers/json HTTP/1.1
Host: docker.sock - 返回结果
1
2
3
4
5
6
7
8
9
10HTTP/1.1 200 OK
Api-Version: 1.56
Content-Type: application/json
Docker-Experimental: false
Ostype: linux
Server: Docker/29.9.2 (linux)
Date: Mon, 26 Oct 2026 14:51:21 GMT
Content-Length: 1856
[{"Id":"919cacb14bff1838b3cb0c3c1332f53d0692c89018d5282571f497594171fe47","Names":["/reverent_meninsky"],"Image":"nginx","ImageID":"sha256:4c41e2d0a3a55b325ed572cd0200bdb72af1433c3daa2bca6ef8eea68ce504a2","ImageManifestDescriptor":{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:59a9ae22b8933b536391e6c1708fa761297e922809c97675c5a56ba316be62af","size":2290,"annotations":{"com.docker.official-images.bashbrew.arch":"amd64","org.opencontainers.image.base.digest":"sha256:4876fdea29cac1a33a3195f1eb1952feb1fca7dd6dd00be3e2f1879cf20d34d2","org.opencontainers.image.base.name":"debian:trixie-slim","org.opencontainers.image.created":"2026-10-21T22:16:50Z","org.opencontainers.image.revision":"6fabb4c960a02a1e576d2789974c45f4cd2aa854","org.opencontainers.image.source":"https://github.com/nginx/docker-nginx.git#6fabb4c960a02a1e576d2789974c45f4cd2aa854:mainline/debian","org.opencontainers.image.url":"https://hub.docker.com/_/nginx","org.opencontainers.image.version":"1.31.8"},"platform":{"architecture":"amd64","os":"linux"}},"Command":"/docker-entrypoint.sh nginx -g 'daemon off;'","Created":1793025621,"Ports":[{"IP":"","PrivatePort":80,"Type":"tcp"}],"Labels":{"maintainer":"NGINX Docker Maintainers <[email protected]>"},"State":"running","Status":"Up 10 minutes","HostConfig":{"NetworkMode":"bridge"},"Health":{"Status":"none","FailingStreak":0},"NetworkSettings":{"Networks":{"bridge":{"IPAMConfig":null,"Links":null,"Aliases":null,"DriverOpts":null,"GwPriority":0,"NetworkID":"7990194027e5bd07a1a19a1e045adb037312e1a29c65d69153c5a94f29548d0b","EndpointID":"2377548cf9d62c084155f0873b3b56d46faf2e89dbe21da0a468049748cce234","Gateway":"172.17.0.1","IPAddress":"172.17.0.2","MacAddress":"c2:4e:9e:85:67:75","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"DNSNames":null}}},"Mounts":[]}]
docker 指令的本质
以 docker ps 为例:
- 输入
docker ps - 终端执行
/usr/bin/docker二进制程序。(这是一个Go语言编写的客户端CLI工具) CLI工具读取环境变量(默认寻找/var/run/docker.sock文件)CLI工具在内存中组装出一个标准的HTTP报文1
GET /containers/json HTTP/1.1\r\nHost: docker.sock\r\n\r\n
CLI工具通过内核管道,将这些字符写入/var/run/docker.sockdockerd的内置HTTP路由解析器发现是GET /containers/json,于是调用内部函数,向Linux内核(Cgroups和Namespaces)查询当前有哪些隔离进程在运行dockerd将查询到的结构体格式化为一段标准JSON文本,包裹在HTTP响应报文中返回CLI工具收到响应后,解析并显示到console
使用编写的程序
使用 Java 编写程序进行读写测试。
Java 16+官方原生引入了对Unix Domain Socket的支持。
1 | import java.io.IOException; |
测试结果:
1 | 成功连接到底层 docker.sock 文件! |
使用 Python 编写程序进行读写测试
1 | import socket |
docker.sock 的本质
是一个 UDS
docker.sock 是一个 Unix 域套接字(Unix Domain Socket),简称 UDS。
在 Linux 操作系统中,一切皆文件。/var/run/docker.sock 在文件系统中表现为一个特殊的套接字文件。
- 不走网络协议栈
- 纯内存进程间通信(
IPC,Inter-Process Communication,进程间通信) - 监听进程
dockerd
UDS 的本质是 Linux 内核在内存中开辟的一个高效率、带文件权限的 IPC 管道。
它虽然在文件系统中表现为一个文件(/var/run/docker.dock),但不能像打开普通文本文件一样用 cat 或 echo 去读写它。这个文件在文件系统中只是一个挂载点(入口标识符),它的真实作用有两个:
- 权限防火墙:利用 Linux 的文件权限机制。谁有这个文件的读写权,谁就能和对应的内核内存管道通信。
- 进程寻址:让客户端进程能够通过一个路径找到服务端进程。
而 docker.sock 的权限如下:
1 | ls -al /var/run/docker.sock |
owner 为 root,group 为 docker,两者都有读写权限。
1 | srw-rw---- 1 root docker 0 Oct 26 22:37 /var/run/docker.sock |
查看 UDS 信息
如何根据 UDS 文件名,查找正在监听它的真实进程 ID?
方法 A:使用 ss
ss 是现代 Linux 系统中用来查看套接字状态的终极工具(替代 netstat)
1 | sudo ss -xpl | grep docker.sock |
返回结果:
1 | u_str LISTEN 0 4096 /run/docker.sock 1510334 * 0 users:(("dockerd",pid=669273,fd=5),("systemd",pid=1,fd=76)) |
方法 B:使用 lsof
lsof意为List Open Files
1 | sudo lsof /var/run/docker.sock |
返回结果:
1 | COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME |
查看进程信息
1 | ps -fp 669273 |
返回结果:
1 | UID PID PPID C STIME TTY TIME CMD |
权限的重要性
在 Linux 内核眼里,没有权限高低的客户端工具,只有是否持有 Socket 文件读写权限的进程。如果被入侵,黑客可以利用简单的 POST 请求去实现容器创建或宿主机提权。