系统安全加固和防护,针对正常程序(特指用户态应用),可以快速更新版本来对新增漏洞快速响应。然而针对Linux内核漏洞,受制于业务复杂性,以及系统稳定性考量,运维很难实现批量、即时的内核更新重启;此外,面对发行版碎片化以及部分系统官方停止支持的问题,作为基础设施的系统特别是内核,消除漏洞的成本过高,漏洞缓解和漏洞利用防御,是很有必要的最后防线。
为Linux内核提供基于强制检查手段的安全方案,主要目标为:
类似Linux热补丁的方式,防护方案以内核模块形式加载代码到内核态,借助ftrace,hook特定内核函数,在hook中针对不同防护逻辑执行检查,并在确认有漏洞利用发生的现场实行阻断。
借助ftrace的函数粒度插装,以及控制流劫持能力,可以在特定目标函数调用点,用以下方法服务于检查/阻断目的:
call
指令,调用者是否来自内核空间代码;根据cvedetails的统计口径,Linux内核注册有CVE编号的漏洞按标签划分,忽略DoS类型,分布集中于溢出、内存破坏、信息获取(泄漏),bypass、代码执行、提权,而标签有重叠性,如溢出类型又可根据适用性划入提权。
相比用户态程序漏洞的用途场景,内核漏洞的利用目的性单一,主要就是提权,以及服务于提权的内核信息泄漏、缓解机制绕过,所以防御的要点,应主要是在利用方式上,做通用的检查阻断;其次针对不同的上游行为,再做补充检查。
提权关键为在内核态借root权限,修改进程task_struct对应的cred结构体,来保证返回用户态后保有root权限。
绝大多数的提权,是通过ret2user或ROP拼接,最终执行commit_creds(prepare_kernel_cred(0))
,而后返回用户态。这两者可以通过下述对应防护方法,在commit_creds
函数入口检查。
在特殊的研究中,也存在借助任意地址写或竞争条件漏洞,直接定位并覆盖进程的cred数据实现提权。这种也许可以通过插装某些频繁执行的内核函数,判断是否在某两次之间未执行commit_creds
可cred
却发生改变,并还原备份的cred
;但这也无法做到通用的、即时的阻断。
最朴素简单的内核漏洞利用方式是从内核态以root权限,直接访问执行内核态代码,从而修改进程权限后退回用户进程。
在未开启SMEP、KPTI防护机器上,可以劫持内核态下$rip
到用户态exp的代码,在其中执行如commit_creds
。这种情况下,可以对这种可能被从用户态执行的内核函数,检查是否有合法的调用指令地址可以判定。
ROP本身也可归类为缓解机制绕过,不过因其通用化,需要设计通用解决方式。
针对ROP防御的难点在于通过拼凑gadget指令执行,先天绕过函数入口检查。例如,常被用来从内核态完成提权后返回用户态的函数swapgs_restore_regs_and_return_to_usermode
,ROP跳转到函数跳过头部堆栈平衡指令之后的位置,此时无法检查。
因此,防御仅能在某些漏洞的ROP链中,会直接跳转到特定函数入口执行的情况下,针对性插装,检查其上游指令是否为合法的call
指令。最显然的方式是提权中往往ROP直接ret到prepare_kernel_cred
和commit_creds
函数入口执行。而其它函数往往是特定利用方式(特别是缓解机制绕过方式)所通常用到的,例如下文提到的call_usermodehelper
,selinux_disable
,orderly_poweroff
。
堆喷射是各种内核UAF漏洞常见的利用方法,借助堆喷射可实现从UAF到提权的办法。一般有三种利用方法:碰撞cred的chunk;泄漏内核地址结合ROP;任意地址读写。 堆喷射的三个原则:需要使用低权限可调用的系统调用;内核堆块弹性大小;堆块中可控负载。TO BE CONTINUED
UAF是对堆内存释放后指针未置为NULL,这样在重新申请相同大小堆块时,slub机制会将刚刚释放的内存分配给新对象,从而通过前一个释放指针操作后面新申请堆内存的方式。这在内核当中是很常见的类型。
在较旧版本,UAF可很容易被用于提权,只需要通过UAF申请并释放一个cred
大小堆块,之后fork
进程,新进程分配的cred
会落入上述指针的目标,可直接改写。但是此后cred
一般单独分配不再由slub管理,这种方法失效。
当前流行的UAF利用方式是通过用户态可打开的设备/dev/ptmx,打开该设备会分配tty_struct
结构内存,其中tty_operations
成员指针指向一系列函数指针,其中的write
指针在向设备写操作时被调用。因此在利用UAF漏洞控制了打开该设备分配的tty_struct
结构体堆块后,就可以控制上述write
指针,指向shellcode实现ret2usr,或指向ROP链。
对此种方法进行防御比较困难的点是,无法简单禁用ptmx设备,因为sshd、telnetd都依赖于虚拟终端设备pty,而pty是通过ptmx作为master操作分配的slave。此外,UAF漏洞利用ptmx是通过直接修改用户态可间接调用的函数指针,无法通过控制流完整性检测;而该指针是打开设备时在堆上分配的,不是进程相关唯一变量,也无法借助数据完整性检测。TODO:调试得到此时函数指针在内核中的调用栈,是否可以根据该指针的调用者做检查,判断函数指针是否指向疑似ROP或JOP gadget。
SMEP在系统中由CR4寄存器的第20bit决定。在较新内核版本,CR4的第20、21bit在系统启动后被锁定,即便通过任何方式覆盖,也会自动重置为1,无法绕过。而在较低版本,通过覆盖bit位来禁用SMEP可行。这一般是通过ROP调用到native_write_cr4()
函数实现,因此对该函数hook检查即可。
作为ret2usr变体,ret2dir将用户态代码与内核地址别名映射,从而以内核地址执行用户态代码,规避SMEP/KPTI缓解机制。
这种方式,暂时没有在启动时检查某个内核地址是否与用户态内存有别名映射的关系的方法检查防御。待验证的两个思路从该方法的两种获取physmap基址的操作进行阻断:一种是阻断/proc/<pid>/pagemap信息向用户态拷贝;一种是监控可疑的通过大量mmap
堆喷行为。
与ret2dir很类似,VDSO是内核态与用户态都映射的内存空间,特定内核版本有典型思路,利用内核态任意写漏洞将提权代码在内核态写入、在用户态执行。
modprobe_path
覆盖提权在有任意内存地址写漏洞时,一个常用提权方法为,调用modprobe_path
指向的用户态处理函数,因此覆盖该变量就可以root权限执行任意程序。
对此种类型漏洞防御,难以在变量覆盖的层面做轮询判断;退而求其次,需要在攻击者修改变量之后,主动回调触发对指定程序调用的路径上,做匹配阻断。其中一个路径为,构造非法ELF文件执行,触发错误并由内核调用modprobe_path
注册程序,考虑到这种由非法execve
目标格式触发回调的调用路径为
(1)do_execve()(2)do_execveat_common()(3)bprm_execve()(4)exec_binprm()(5)search_binary_handler()(6)request_module()(7)call_usermodehelper()
可以hook call_usermodehelper
,回溯调用栈符合以上组合,就跳过对用户程序的启动,从而以废除对未知格式文件加载错误处置为代价,防御提权。
而另一种攻击向量提出可以创建netlink socket来直接调用request_module()
,这是一个更短的函数调用链,无法靠牺牲netlink socket的方式来阻断,此时需要
仍在分析完善中。内核信息泄漏往往仅在漏洞利用中体现,而Linux社区对这类问题认定为漏洞的积极性不高,所以需要持续搜集更大量真实exp来完善。
针对Linux内核漏洞与防御的覆盖度,选取外部广泛索引的Linux Kernel Defence Map作为基准,归纳了所有已知的脆弱性和漏洞利用方法。其中,我们的缓解机制解决的有:
而本方案不会尝试解决的设计相关的漏洞或利用方式包括:
根据前面引用的Linux内核漏洞统计,去除拒绝服务类型外,可用于利用的漏洞分布在溢出、机制绕过、提权、信息获取、代码执行、内存破坏等主要类型。因为上述不覆盖的类型主要是个例而非通用漏洞与利用类型,且考虑到无法防御的特殊漏洞利用方式(如ret2dir的部分利用方式),往往根据系统环境,仅有一定概率可利用成功,而没有通用的利用手法。因此本方案设计的漏洞利用防御覆盖度,可达到对80%以上漏洞利用攻击向量的防御。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。