docker 容器安全问题实验

简介: docker 容器安全问题实验

问题演示



使用 root 账号创建一个临时文件 a.txt:


[root@localhost ~]# echo "aaa" > /root/tmp/a.txt
[root@localhost ~]# cat /root/tmp/a.txt 
aaa
复制代码


确认 work 账号没有 a.txt 的权限:


[work@localhost ~]$ cat /root/tmp/a.txt
cat: /root/tmp/a.txt: Permission denied
复制代码


使用下面命令启动一个redis容器并挂载数据卷 /root/tmp :


# 注意这里是work账号
[work@localhost ~]$ docker run -d -v /root/tmp:/root/tmp  redis:6.0.10
f709b9d2ef3acd8edd525ba6a47246976715d720c047c868bea0631d51f4ab54docker run -d -v /root/tmp:/root/tmp  redis:6.0.10
复制代码


进入刚才创建的 redis 容器并修改 a.txt 文件:


# 注意这里也是work账号
[work@localhost ~]$ docker exec -it f70 bash
root@f709b9d2ef3a:/data# echo "bbb" >> /root/tmp/a.txt 
root@f709b9d2ef3a:/data# cat /root/tmp/a.txt 
aaa
bbb
复制代码


回到宿主机上,使用 root 账号确认 /root/tmp/a.txt 文件内容:


[root@localhost ~]# cat /root/tmp/a.txt 
aaa
bbb
复制代码


可以看到一个普通的 work 账号,通过docker容器提权操作了一个没有权限的文档。


原理



使用work账号进入刚才的redis容器然后保持:


[work@localhost ~]$ docker exec -it f70 bash
root@f709b9d2ef3a:/data# id
uid=0(root) gid=0(root) groups=0(root)
复制代码


在宿主机使用root账号查看一下容器的bash进程


[root@localhost ~]# docker top f70
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
systemd+            13516               13497               0                   04:18               ?                   00:00:03            redis-server *:6379
root                13679               13497               0                   04:32               pts/0               00:00:00            bash
复制代码


在宿主机确认13679进程的user是root


[root@localhost ~]# ps -ef | grep 13679
root     13679 13497  0 04:32 pts/0    00:00:00 bash
复制代码


可以看到work账号进入容器bash程序的时候,切换成root账号,所以权限得到提升。如果绑定了root的目录,则就可以对root目录进行操作。


这是因为我们的docker服务是使用root账号启动的:


[root@localhost ~]# ps -ef | grep dockerd
root     12694     1  0 03:59 ?        00:00:01 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
复制代码


容器用户



在宿主机查看redis进程, 可以知道宿主机上redis进程的username是 polkitd :


$ ps -ef | grep redis
polkitd  12876 12857  0 14:17 ?        00:00:00 redis-server *:6379
复制代码


也可以使用 docker top 查看:


$ docker top dca
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
polkitd             12876               12857               1                   14:17               ?                   00:00:00            redis-server *:6379
复制代码


查看宿主机上polkitd账号的uid:


$ id polkitd
uid=999(polkitd) gid=998(polkitd) 组=998(polkitd)
复制代码


进入容器查看容器内用户:


root@5be8ce78244c:/data# gosu redis bash  # 使用gosu切换到redis账号
redis@5be8ce78244c:/data$ id
uid=999(redis) gid=999(redis) groups=999(redis)
复制代码


这里可以发现容器内redis和宿主机polkitd的uid是一致的。


redis:6.0.10镜像没有安装 ps, 可以进入容器后执行 apt-get update && apt-get install procps 安装。然后查看redis进程的uid:


root@6c54c348686d:/data# ps -ef
UID         PID   PPID  C STIME TTY          TIME CMD
redis         1      0  0 08:08 ?        00:00:00 redis-server *:6379
root         26      0  0 08:09 pts/0    00:00:00 bash
root        350     26  0 08:15 pts/0    00:00:00 ps -ef
复制代码


确认容器内是使用redis账号启动的服务,服务进程号是 1。还可以在宿主机上确认redis账号不存在:


$ id redis
id: redis: no such user
复制代码


我们可以得出结论1: 容器用户uid和宿主机用户的uid相同,名称可以不同


指定容器用户



容器可以指定用户。编写 Dockerfile


FROM redis:6.0.10
RUN groupadd -r test1 && useradd -r -g test1 test1  # 创建一下test1用户
复制代码


编译镜像


docker build -t redis:redis_test .
复制代码


运行自定义的redis镜像, 运行时需要指定用户


docker run -d --user test1 redis:redis_test
复制代码


docker run -u 参数帮助:  --user string   Username or UID (format: <name|uid>[:<group|gid>])


在宿主机上查看redis进程信息, 发现是systemd-bus-proxy创建的进程:


$ ps -axo user:20,pid,pcpu,pmem,vsz,rss,tty,stat,start,time,comm | grep redis
systemd-bus-proxy    15429  0.1  0.0  52796 11916 ?        Ssl  16:24:01 00:00:01 redis-server
复制代码


ps -ef 中如果username超过8个字符,会显示成 systemd+, 所以上面命令要使用 use:20 格式化一下长度


看看systemd-bus-proxy的uid


$ id systemd-bus-proxy
uid=998(systemd-bus-proxy) gid=996(systemd-bus-proxy) groups=996(systemd-bus-proxy)
复制代码


进入容器查看:


$ docker exec -it 680 bash
test1@680ccf686e5f:/data$ id  # 注意当前用户变成了test1,uid=998
uid=998(test1) gid=998(test1) groups=998(test1)
复制代码


使用test1运行的容器没有权限安装包,使用下面方式确认进程的uid=998


$ cat /proc/1/status | grep id
Tgid: 1
Ngid: 0
Pid:  1
PPid: 0
TracerPid:  0
Uid:  998 998 998 998
Gid:  998 998 998 998
复制代码


docker的机制是容器的id=1的进程是主进程。


我们可以得出结论2: 容器用户可以在镜像中创建,在container运行时使用--user参数指定


gosu切换用户



容器可以指定用户,自然涉及到切换用户的问题,宿主机上使用 su , 容器内建议 gosu,下面是Docker — 从入门到实践中的内容:


如果以 root 执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立 好的用户来运行某个服务进程,不要使用 su 或者 sudo ,这些都需要比较麻烦 的配置,而且在 TTY 缺失的环境下经常出错。


redis:6.0.10的 Dockerfile 源码中可以看到创建了redis=999的账号:


# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
RUN groupadd -r -g 999 redis && useradd -r -g redis -u 999 redis
复制代码


重点是 docker-entrypoint.sh 部分:


root@6c54c348686d:/data# whereis docker-entrypoint.sh
docker-entrypoint: /usr/local/bin/docker-entrypoint.sh
root@6c54c348686d:/data# cat /usr/local/bin/docker-entrypoint.sh
#!/bin/sh
set -e
# first arg is `-f` or `--some-option`
# or first arg is `something.conf`
if [ "${1#-}" != "$1" ] || [ "${1%.conf}" != "$1" ]; then
  set -- redis-server "$@"
fi
# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
  find . \! -user redis -exec chown redis '{}' +
  exec gosu redis "$0" "$@"  # 使用redis账号启动redis-server
fi
exec "$@"
复制代码


所以默认的redis:6.0.10显示uid=999。


下面是gosu官方的日志:


$ docker run -it --rm ubuntu:trusty su -c 'exec ps aux'
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0  46636  2688 ?        Ss+  02:22   0:00 su -c exec ps a
root         6  0.0  0.0  15576  2220 ?        Rs   02:22   0:00 ps aux
$ docker run -it --rm ubuntu:trusty sudo ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  3.0  0.0  46020  3144 ?        Ss+  02:22   0:00 sudo ps aux
root         7  0.0  0.0  15576  2172 ?        R+   02:22   0:00 ps aux
$ docker run -it --rm -v $PWD/gosu-amd64:/usr/local/bin/gosu:ro ubuntu:trusty gosu root ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   7140   768 ?        Rs+  02:22   0:00 ps aux
复制代码


sudo会作为被授权的命令的父进程一直存在,直到该命令退出,gosu则没有这个问题。


版本



本次使用使用的相关版本:


NAME="CentOS Linux"
VERSION="7 (Core)"
...
Client: Docker Engine - Community
 Version:           20.10.7
 API version:       1.41
...
Server: Docker Engine - Community
 Engine:
  Version:          20.10.7
  API version:      1.41 (minimum version 1.12)
  ...
复制代码


清理



可以使用下面命令清理实验环境


docker ps -aq | xargs docker  stop  # 关闭容器
docker container prune  # 清理容器
docker rmi redis:redis_test  # 删除镜像
docker image prune  # 清理镜像
复制代码


参考链接




目录
相关文章
|
2月前
|
监控 Kubernetes 安全
还没搞懂Docker? Docker容器技术实战指南 ! 从入门到企业级应用 !
蒋星熠Jaxonic,技术探索者,以代码为笔,在二进制星河中书写极客诗篇。专注Docker与容器化实践,分享从入门到企业级应用的深度经验,助力开发者乘风破浪,驶向云原生新世界。
还没搞懂Docker? Docker容器技术实战指南 ! 从入门到企业级应用 !
|
2月前
|
NoSQL 算法 Redis
【Docker】(3)学习Docker中 镜像与容器数据卷、映射关系!手把手带你安装 MySql主从同步 和 Redis三主三从集群!并且进行主从切换与扩容操作,还有分析 哈希分区 等知识点!
Union文件系统(UnionFS)是一种**分层、轻量级并且高性能的文件系统**,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem) Union 文件系统是 Docker 镜像的基础。 镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
503 5
|
2月前
|
监控 Linux 调度
【赵渝强老师】Docker容器的资源管理机制
本文介绍了Linux CGroup技术及其在Docker资源管理中的应用。通过实例演示了如何利用CGroup限制应用程序的CPU、内存和I/O带宽使用,实现系统资源的精细化控制,帮助理解Docker底层资源限制机制。
237 6
|
2月前
|
存储 关系型数据库 MySQL
MySQL Docker 容器化部署全指南
MySQL是一款开源关系型数据库,广泛用于Web及企业应用。Docker容器化部署可解决环境不一致、依赖冲突问题,实现高效、隔离、轻量的MySQL服务运行,支持数据持久化与快速迁移,适用于开发、测试及生产环境。
528 4
|
3月前
|
Kubernetes Devops Docker
Kubernetes 和 Docker Swarm:现代 DevOps 的理想容器编排工具
本指南深入解析 Kubernetes 与 Docker Swarm 两大主流容器编排工具,涵盖安装、架构、网络、监控等核心维度,助您根据团队能力与业务需求精准选型,把握云原生时代的技术主动权。
312 1
|
4月前
|
Kubernetes Docker Python
Docker 与 Kubernetes 容器化部署核心技术及企业级应用实践全方案解析
本文详解Docker与Kubernetes容器化技术,涵盖概念原理、环境搭建、镜像构建、应用部署及监控扩展,助你掌握企业级容器化方案,提升应用开发与运维效率。
840 108
|
5月前
|
存储 监控 测试技术
如何将现有的应用程序迁移到Docker容器中?
如何将现有的应用程序迁移到Docker容器中?
457 57