一、背景
随着云原生、DevOps、CI/CD等愈发流行与普及,容器云技术的使用也越来越广泛。许多云服务提供商已经开始提供基于容器的云服务,以满足其不断增长的需求,在众多容器技术中,Docker是目前被广泛应用的一种容器化技术。《Flexera 2022年云计算报告》中提到Docker和Kubernetes的使用仍然相当可观。
容器逃逸就是指攻击者利用容器环境中的漏洞或弱点,从容器中逃脱并获取宿主机的访问权限,从而利用逃逸后的权限对宿主机上的其他容器或数据进行攻击。同一宿主机上的多个容器共享一个Linux内核,容器可以直接获得宿主机的Root权限,并且容器可直接与宿主内核进行通信,一旦容器内的用户从普通权限提升到Root权限,或因配置不当一开始就以Root身份启动容器进入主机系统,就会导致提权攻击,影响宿主机或其他容器的运行安全。如CVE-2019-5736漏洞就是利用恶意容器覆盖宿主机上的runC二进制文件,从而获得Root权限。
二、防御方法
容器机制由于内核共享的特性而不安全。为了防止容器逃逸,可以采用一些背景技术来加强容器环境的安全性。其中一种防御方法是使用容器运行时的安全特性,例如Linux的Namespace和Cgroups。这些特性可以帮助隔离容器与宿主机和其他容器的资源,并限制容器对宿主机的访问权限;另一种防御方法是使用容器安全解决方案,如容器安全监控工具、漏洞扫描工具和安全加固工具。这些工具可以帮助发现容器环境中的安全漏洞和弱点,并及时修复,从而减少容器逃逸的风险。但随着攻击技术的发展,提权攻击模型中涉及的特定内核函数成为攻击的关键环节,需要针对性地进行防御。
阻断容器进程中利用提权漏洞进行攻击的特定内核函数的思想如下:
在调用者进程试图修改内核数据结构real_cred和内核数据结构cred之前,判断该调用者进程是否在容器内或在控制主机上;如果调用者进程在容器内,则检测从容器内部调用内核静态函数commit_creds( )的操作是否为权限升级操作,并在该从容器内部调用内核静态函数commit_creds( )的操作是权限升级操作的情况下,生成停止修改内核数据结构real_cred和内核数据结构cred的指令,以使所述调用者进程无法对内核数据结构real_cred和内核数据结构cred进行修改;如果调用者进程在控制主机上,则继续进程运行原函数流程。
判断调用者进程是否在容器内或在控制主机上的流程包括:
- 获取调用者进程中nsproxy字段的值;
- 在Linux控制主机上的进程上获取init_nsproxy字段的值;
- 将nsproxy字段的值与init_nsproxy字段的值进行比较,如果nsproxy字段的值等于init_nsproxy字段的值,则调用者进程在控制主机上;如果nsproxy字段的值不等于init_nsproxy字段的值,则调用者进程在容器内。
判断从容器内部调用内核静态函数commit_creds( )的操作是否为权限升级操作包括:
- 将从容器内部调用内核静态函数commit_creds( )的操作的输入参数指向的cred数据结构内的数据与当前进程中内核数据结构real_cred内的数据进行比较,数据包括:参数uid、参数gid和参数cap_bset;
- 如果从容器内部调用内核静态函数commit_creds( )的操作所对应的参数uid或参数gid更小,或者从容器内部调用内核静态函数commit_creds( )的操作所对应的参数cap_bset更大,则判定所述从容器内部调用内核静态函数commit_creds( )的操作为权限升级操作。
- 从容器内部调用内核静态函数commit_creds( )的操作不是权限升级操作的情况下继续进程运行原函数流程。
三、总结
通过对关键函数进行阻断来检测容器逃逸的方法简单明了,且对原有代码改动较小,不会对系统的稳定性和性能产生显著影响;该方法增加的代码量也较少,易于实施和维护;自动拦截容器进程的提权行为,能够有效防止大部分容器逃逸攻击。