游客发表
2018年,授权一个叫“docker”的访问容器引擎在国内还没像今天这样铺开。那年夏天,漏洞我帮朋友排查一台阿里云上的授权服务器异常——CPU跑满,带宽被占,访问但登录上去看进程,漏洞一切正常。授权最后在crontab里翻出一个定时任务,访问下载脚本、漏洞拉镜像、授权跑容器,访问一气呵成。漏洞攻击者用的授权入口就一个:2375端口。
这个端口是访问Docker守护进程的默认监听端口之一,准确说是漏洞未加密的TCP socket。Docker官方从设计上就把它标为“不安全”,但架不住图省事的人多。开发环境里,为了让本地IDE能连远程Docker,为了让CI/CD流水线能直接推镜像,很多人在daemon.json里加一行"hosts": ["tcp://0.0.0.0:2375", "unix:///var/run/docker.sock"],然后重启服务。防火墙?安全组?内网嘛,没事。
问题就出在这个“没事”上。Docker API本身不校验身份。你连上2375,发一个GET /containers/json,它就把所有容器列表吐给你。再发POST /containers/create,指定镜像和挂载参数,一个容器就起来了。挂载什么?/目录。命令是什么?chroot /host /bin/bash -c "..."。容器逃逸?根本不需要逃,你把宿主机根目录挂进容器,容器里就是宿主机。攻击者拿到的是root权限的shell,跑在受害者的物理机上。
我见过最离谱的一个案例。某创业公司用Docker跑内部测试环境,2375端口开在公网IP上,安全组只放行了公司出口IP。但他们的出口IP是动态的,运维图方便,直接改成0.0.0.0/0。三个月后,服务器上多了七个挖矿容器,镜像来自一个叫“xmr-stak”的仓库。CPU占用率被限制在70%,所以监控没报警。直到月底收到阿里云的超额流量账单,才发现不对劲。
攻击者的操作其实很“标准”。先GET /info看Docker版本,确认API可用。然后POST /images/create?fromImage=alpine拉一个最小镜像。接着POST /containers/create,Binds参数写["/:/mnt"],Cmd写["/bin/sh", "-c", "echo ... > /mnt/root/.ssh/authorized_keys"]。一个容器跑完,宿主机的SSH公钥就被换了。整个过程不到十秒,日志里只留下几条API调用记录。
为什么这个漏洞这么多年还在?因为Docker的默认配置太“安静”了。你装完Docker,它默认只监听unix socket,2375是关闭的。但一旦有人手动打开,没有任何警告弹窗,没有邮件通知,docker info里也不会用红字标出来。它就像一扇没锁的后门,门本身不显眼,但推开就是整个院子。
修复方式倒不复杂。要么别开2375,用SSH隧道或者docker context走unix socket。要么开2376,配TLS证书,--tlsverify加上--tlscacert、--tlscert、--tlskey三件套。再不行,至少用iptables限制源IP。但现实是,很多团队在CI/CD里图快,直接export DOCKER_HOST=tcp://prod-server:2375,把生产环境当本地用。
去年我扫过一个B段,2375端口开放的主机有四十多台。其中一台的/containers/json返回了十七个容器,名字全是“jenkins-agent-xxx”。我试着POST /containers/create,没挂载任何目录,只跑了sleep 3600。容器起来了。我没做进一步操作,退出来,给那个IP的whois邮箱发了封邮件。三天后端口关了。但谁知道之前有没有人已经进去过?
Docker API未授权访问不是那种“惊天动地”的漏洞。它没有CVE编号,没有复杂的利用链,甚至不需要写一行exploit代码。它就是一个配置选项,一个默认关闭但总有人打开的服务。你打开它,就等于把宿主机root权限放在公网上,等着第一个curl过来的人。
友情链接