Linux内核漏洞浅析

与Windows相比,Linux被认为具有更好的安全性和其他扩展性能。这些特性使得Linux在操作系统领域异军突起,得到越来越多的重视。随着Linux应用量的增加,其安全性也逐渐受到了公众甚或黑客的关注。那么,Linux是否真的如其支持厂商们所宣称的那样安全呢?本期我们请到了启明星辰信息技术有限公司积极防御实验室工程师赵伟,对Linux进行专业的漏洞技术分析。

Linux内核精短、稳定性高、可扩展性好、硬件需求低、免费、网络功能丰富、适用于多种cpu等特性,使之在操作系统领域异军突起。其独特的魅力使它不仅在pc机上占据一定的份额,而且越来越多地被使用在各种嵌入式设备中,并被当作专业的路由器,防火墙,或者高端的服务器OS来使用。各种类型的Linux发行版本也如雨后春笋般冒了出来,国内更是掀起了Linux的使用热潮,很多政府部门因安全需要也被要求使用Linux。正是因为Linux被越来越多地使用,其安全性也渐渐受到了公众的关注,当然,也更多地受到了黑客的关注。通常,我们讨论Linux系统安全都是从Linux安全配置的角度或者Linux的安全特性等方面来讨论的,而这一次我们转换一下视角,从Linux系统中存在的漏洞与这些漏洞产生的影响来讨论Linux的安全性。

首先来说明一下这次我们讨论Linux系统安全的范围,其实通常我们所说的Linux是指GNU/Linux系统,Linux是系统中使用的操作系统内核。这一次我们重点从Linux系统内核中存在的几类非常有特点的漏洞来讨论Linux系统的安全性。

权限提升类漏洞

一般来说,利用系统上一些程序的逻辑缺陷或缓冲区溢出的手段,攻击者很容易在本地获得Linux服务器上管理员权限root;在一些远程的情况下,攻击者会利用一些以root身份执行的有缺陷的系统守护进程来取得root权限,或利用有缺陷的服务进程漏洞来取得普通用户权限用以远程登录服务器。目前很多Linux服务器都用关闭各种不需要的服务和进程的方式来提升自身的安全性,但是只要这个服务器上运行着某些服务,攻击者就可以找到权限提升的途径。下面是一个比较新的导致权限提升的漏洞。

do_brk()边界检查不充分漏洞在2003年9月份被Linux内核开发人员发现,并在9月底发布的Linux kernel 2.6.0-test6中对其进行了修补。但是Linux内核开发人员并没有意识到此漏洞的威胁,所以没有做任何通报,一些安全专家与黑客却看到了此漏洞蕴涵的巨大威力。在2003年11月黑客利用rsync中一个未公开的堆溢出与此漏洞配合,成功地攻击了多台Debian与Gentoo Linux的服务器。

下面让我们简单描述一下该漏洞。该漏洞被发现于brk系统调用中。brk系统调用可以对用户进程的堆的大小进行操作,使堆扩展或者缩小。而brk内部就是直接使用do_brk()函数来做具体的操作, do_brk()函数在调整进程堆的大小时既没有对参数len进行任何检查(不检查大小也不检查正负),也没有对addr+len是否超过TASK_SIZE做检查。这样我们就可以向它提交任意大小的参数len,使用户进程的大小任意改变以至可以超过TASK_SIZE的限制,使系统认为内核范围的内存空间也是可以被用户访问的,这样的话普通用户就可以访问到内核的内存区域。通过一定的操作,攻击者就可以获得管理员权限。这个漏洞极其危险,利用这个漏洞可以使攻击者直接对内核区域操作,可以绕过很多Linux系统下的安全保护模块。

此漏洞的发现提出了一种新的漏洞概念,即通过扩展用户的内存空间到系统内核的内存空间来提升权限。当发现这种漏洞时,通过研究我们就认为内核中一定还会存在类似的漏洞,果然几个月后黑客们又在Linux内核中发现与brk相似的漏洞。通过这次成功的预测,更证实了对这种新型的概念型漏洞进行研究很有助于安全人员在系统中发现新的漏洞。

拒绝服务类漏洞

拒绝服务攻击是目前比较流行的攻击方式,它并不取得服务器权限,而是使服务器崩溃或失去响应。对Linux的拒绝服务大多数都无须登录即可对系统发起拒绝服务攻击,使系统或相关的应用程序崩溃或失去响应能力,这种方式属于利用系统本身漏洞或其守护进程缺陷及不正确设置进行攻击。

另外一种情况,攻击者登录到Linux系统后,利用这类漏洞,也可以使系统本身或应用程序崩溃。这种漏洞主要由程序对意外情况的处理失误引起,如写临时文件之前不检查文件是否存在,盲目跟随链接等。

下面,我们简单描述一下Linux在处理intel IA386 CPU中的寄存器时发生错误而产生的拒绝服务漏洞。该漏洞是因为IA386多媒体指令使用的寄存器MXCSR的特性导致的。由于IA386 CPU规定MXCSR寄存器的高16位不能有任何位被置位,否则CPU就会报错导致系统崩溃。为了保证系统正常运转,在linux系统中有一段代码专门对MXCSR的这个特性作处理,而这一段代码在特定的情况下会出现错误,导致MXCSR中的高16位没有被清零,使系统崩溃。如果攻击者制造了这种“极限”的内存情况就会对系统产生DoS效果。

攻击者通过调用get_fpxregs函数可以读取多媒体寄存器至用户空间,这样用户就可以取得MXCSR寄存器的值。调用set_fpxregs函数可以使用用户空间提供的数据对MXCSR寄存器进行赋值。通过对MXCSR的高16位进行清0,就保证了IA386 CPU的这个特性。如果产生一种极限效果使程序跳过这一行,使MXCSR寄存器的高16位没有被清0,一旦MXCSR寄存器的高16位有任何位被置位,系统就会立即崩溃!

因为利用这个漏洞攻击者还需要登录到系统,这个漏洞也不能使攻击者提升权限,只能达到DoS的效果,所以这个漏洞的危害还是比较小的。但是分析这个漏洞就没有意义了吗?其实由分析这个漏洞可以看出:Linux内核开发成员对这种内存拷贝时出现错误的情况没有进行考虑,以至造成了这个漏洞,分析了解了这个漏洞后,在漏洞挖掘方面也出现了一种新的类型,使我们在以后的开发中可以尽量避免这种情况。

接下来让我们看一种Linux内核算法上出现的漏洞。先来简单介绍一下这个漏洞,当Linux系统接收到攻击者经过特殊构造的包后,会引起hash表产生冲突导致服务器资源被耗尽。这里所说的hash冲突就是指:许多数值经过某种hash算法运算以后得出的值相同,并且这些值都被储存在同一个hash槽内,这就使hash表变成了一个单向链表。而对此hash表的插入操作会从原来的复杂度O(n)变为O(n*n)。这样就会导致系统消耗巨大的cpu资源,从而产生了DoS攻击效果。

我们先看一下在linux中使用的hash算法,这个算法用在对Linux route catch的索引与分片重组的操作中。在今年五月Rice University计算机科学系的Scott A. Crosby与Dan S. Wallach提出了一种新的低带宽的DoS攻击方法,即针对应用程序所使用的hash算法的脆弱性进行攻击。这种方法提出:如果应用程序使用的hash算法存在弱点,也就是说hash算法不能有效地把数据进行散列,攻击者就可以通过构造特殊的值使hash算法产生冲突引起DoS攻击。

202

203 static __inline__ unsigned rt_hash_code(u32 daddr, u32 saddr, u8 tos)

204 {

205 unsigned hash = ((daddr & 0xF0F0F0F0) >> 4) │

206 ((daddr & 0x0F0F0F0F) << 4);

207 hash ^= saddr ^ tos;

208 hash ^= (hash >> 16);

209 return (hash ^ (hash >> 8)) & rt_hash_mask;

210 }

以上的代码就是Linux对ip包进行路由或者重组时使用的算法。此算法由于过于简单而不能把route缓存进行有效的散列,从而产生了DoS漏洞。下面我们来分析一下此函数。

203行为此函数的函数名与入口参数,u32 daddr为32位的目的地址,而u32 saddr为32位的原地址,tos为协议。

205行-206行是把目标地址前后字节进行转换。

207行把原地址与tos进行异或后再与hash异或然后再赋值给hash。

208行把hash的值向右偏移16位然后与hash异或再赋值给hash。

209行是此函数返回hash与它本身向右偏移8位的值异或,然后再跟rt_hash_mask进行与操作的值。

这种攻击是一种较为少见的拒绝服务方式,因为它利用了系统本身的算法中的漏洞。该漏洞也代表了一种新的漏洞发掘的方向,就是针对应用软件或者系统使用的hash算法进行漏洞挖掘。因此,这种针对hash表攻击的方法对不仅对Linux,而且会对很多应用软件产生影响,比如说Perl5在这个perl的版本中使用的hash算法就容易使攻击者利用精心筛选的数据,使用perl5进行编程的应用程序使用的hash表产生hash冲突,包括一些代理服务器软件,甚至一些IDS软件,防火墙等,因使用的是Linux内核都会被此种攻击影响。

Linux内核中的整数溢出漏洞

Linux Kernel 2.4 NFSv3 XDR处理器例程远程拒绝服务漏洞在2003年7月29日公布,影响Linux Kernel 2.4.21以下的所有Linux内核版本。

该漏洞存在于XDR处理器例程中,相关内核源代码文件为nfs3xdr.c. 此漏洞是由于一个整形漏洞引起的(正数/负数不匹配)。攻击者可以构造一个特殊的XDR头(通过设置变量int size为负数)发送给Linux系统即可触发此漏洞。当Linux系统的NFSv3 XDR处理程序收到这个被特殊构造的包时,程序中的检测语句会错误地判断包的大小,从而在内核中拷贝巨大的内存,导致内核数据被破坏,致使Linux系统崩溃。

漏洞代码:

static inline u32 *

decode_fh(u32 *p, struct svc_fh *fhp)

{

int size;

fh_init(fhp, NFS3_FHSIZE);

size = ntohl(*p++);

if (size > NFS3_FHSIZE)

return NULL;

memcpy(&fhp->fh_handle.fh_base, p, size); fhp->fh_handle.fh_size = size;

return p + XDR_QUADLEN(size);

}

因为此内存拷贝时在内核内存区域中进行,会破坏内核中的数据导致内核崩溃,所以此漏洞并没有证实可以用来远程获取权限,而且利用此漏洞时攻击者必须可以mount此系统上的目录,更为利用此漏洞增加了困难。

我们的目的在于通过这个漏洞的特点来寻找此种类型的漏洞并更好地修补它。大家可以看到,该漏洞是一个非常典型的整数溢出漏洞,如果在内核中存在这样的漏洞是非常危险的。所以Linux的内核开发人员对Linux内核中关于数据大小的变量都作了处理(使用了unsigned int),这样就避免了再次出现这种典型的整数溢出。通过对这种特别典型的漏洞原理进行分析,开发人员可以在以后的开发中避免出现这种漏洞。

IP地址欺骗类漏洞

由于tcp/ip本身的缺陷,导致很多操作系统都存在tcp/ip堆栈漏洞,使攻击者进行ip地址欺骗非常容易实现。Linux也不例外。虽然IP地址欺骗不会对Linux服务器本身造成很严重的影响,但是对很多利用Linux为操作系统的防火墙和IDS产品来说,这个漏洞却是致命的。

IP地址欺骗是很多攻击的基础,之所以使用这个方法,是因为IP自身的缺点。IP协议依据IP头中的目的地址项来发送IP数据包。如果目的地址是本地网络内的地址,该IP包就被直接发送到目的地。如果目的地址不在本地网络内,该IP包就会被发送到网关,再由网关决定将其发送到何处。这是IP路由IP包的方法。IP路由IP包时对IP头中提供的IP源地址不做任何检查,认为IP头中的IP源地址即为发送该包的机器的IP地址。当接收到该包的目的主机要与源主机进行通信时,它以接收到的IP包的IP头中IP源地址作为其发送的IP包的目的地址,来与源主机进行数据通信。IP的这种数据通信方式虽然非常简单和高效,但它同时也是IP的一个安全隐患,很多网络安全事故都是由IP的这个缺点而引发的。

黑客或入侵者利用伪造的IP发送地址产生虚假的数据分组,乔装成来自内部站的分组过滤器,这种类型的攻击是非常危险的。关于涉及到的分组真正是内部的,还是外部的分组被包装得看起来像内部分组的种种迹象都已丧失殆尽。只要系统发现发送地址在自己的范围之内,就把该分组按内部通信对待并让其通过。

通常主机A与主机B的TCP连接是通过主机A向主机B提出请求建立起来的,而其间A和B的确认仅仅根据由主机A产生并经主机B验证的初始序列号ISN。具体分三个步骤。

主机A产生它的ISN,传送给主机B,请求建立连接;B接收到来自A的带有SYN标志的ISN后,将自己本身的ISN连同应答信息ACK一同返回给A;A再将B传送来的ISN及应答信息ACK返回给B。至此,正常情况下,主机A与B的TCP连接就建立起来了。

B ---- SYN ----> A

B <---- SYN+ACK ---- A

B ---- ACK ----> A

假设C企图攻击A,因为A和B是相互信任的,如果C已经知道了被A信任的B,那么就要想办法使得B的网络功能瘫痪,防止别的东西干扰自己的攻击。在这里普遍使用的是SYN flood。攻击者向被攻击主机发送许多TCP- SYN包。这些TCP-SYN包的源地址并不是攻击者所在主机的IP地址,而是攻击者自己填入的IP地址。当被攻击主机接收到攻击者发送来的TCP-SYN包后,会为一个TCP连接分配一定的资源,并且会以接收到的数据包中的源地址(即攻击者自己伪造的IP地址)为目的地址向目的主机发送TCP-(SYN+ACK)应答包。由于攻击者自己伪造的IP地址一定是精心选择的不存在的地址,所以被攻击主机永远也不可能收到它发送出去的TCP-(SYN+ACK)包的应答包,因而被攻击主机的TCP状态机处于等待状态。如果被攻击主机的TCP状态机有超时控制的话,直到超时,为该连接分配的资源才会被回收。因此如果攻击者向被攻击主机发送足够多的TCP-SYN包,并且足够快,被攻击主机的TCP模块肯定会因为无法为新的TCP连接分配到系统资源而处于服务拒绝状态。即使被攻击主机所在网络的管理员监听到了攻击者的数据包也无法依据IP头的源地址信息判定攻击者是谁。

当B的网络功能暂时瘫痪时, C必须想方设法确定A当前的ISN。首先连向25端口,因为SMTP是没有安全校验机制的,与前面类似,不过这次需要记录A的ISN,以及C到A的大致的RTT(round trip time)。这个步骤要重复多次以便求出RTT的平均值。一旦C知道了A的ISN基值和增加规律,就可以计算出从C到A需要RTT/2 的时间。然后立即进入攻击,否则在这之间有其他主机与A连接,ISN将比预料的多。

C向A发送带有SYN标志的数据段请求连接,只是信源IP改成了B。A向B回送SYN+ACK数据段,B已经无法响应,B的TCP层只是简单地丢弃A的回送数据段。这个时候C需要暂停一小会儿,让A有足够时间发送SYN+ACK,因为C看不到这个包。然后C再次伪装成B向A发送ACK,此时发送的数据段带有C预测的A的ISN+1。如果预测准确,连接建立,数据传送开始。问题在于即使连接建立,A仍然会向B发送数据,而不是C,C仍然无法看到A发往B的数据段,C必须蒙着头按照协议标准假冒B向A发送命令,于是攻击完成。如果预测不准确,A将发送一个带有RST标志的数据段异常终止连接,C只有从头再来。随着不断地纠正预测的ISN,攻击者最终会与目标主机建立一个会晤。通过这种方式,攻击者以合法用户的身份登录到目标主机而不需进一步的确认。如果反复试验使得目标主机能够接收对网络的ROOT登录,那么就可以完全控制整个网络。

C(B) ---- SYN ----> A

B <---- SYN+ACK ---- A

C(B) ---- ACK ----> A

C(B) ---- PSH ----> A

IP欺骗攻击利用了RPC服务器仅仅依赖于信源IP地址进行安全校验的特性,攻击最困难的地方在于预测A的ISN。攻击难度比较大,但成功的可能性也很大。C必须精确地预见可能从A发往B的信息,以及A期待来自B的什么应答信息,这要求攻击者对协议本身相当熟悉。同时需要明白,这种攻击根本不可能在交互状态下完成,必须写程序完成。当然在准备阶段可以用netxray之类的工具进行协议分析。

总 结

通过分析上面的几个漏洞大家也可以看到Linux并不是完美的,还有很多的地方需要完善。有些漏洞极大地影响了Linux的推广和使用,例如上面那个Linux hash表冲突的漏洞,因为一些IDS厂商和防火墙厂商就是基于Linux内核来开发自己的产品,如果还是使用的Linux本身的hash算法就会受到这种漏洞的影响,极易被攻击者进行DoS攻击。因为防火墙、IDS本身就是安全产品,如果它们被攻击就会使用户产生极大的损失,所以我们需要对这些漏洞进行跟踪分析,并通过了解它们的特性以避免在系统中再次产生这些类型的漏洞,通过对这些类型的漏洞进行预测挖掘,使我们能积极地防御黑客的攻击破坏。

作者简介

赵伟 2003年就职于启明星辰积极防御实验室,SST成员,国家计算机网络安全事件应急小组(CNCERT/CC)成员。主要研究方向:Unix/Linux漏洞挖掘,Unix/Linux应用系统安全,Linux内核漏洞挖掘,Linux HIDS的开发和事件研究,XML相关安全。参与过多项计算机犯罪取证,计算机容灾系统等相关的国家项目与863项目。

(0)

相关推荐

  • 浅谈Linux内核创建新进程的全过程

    进程描述 进程描述符(task_struct) 用来描述进程的数据结构,可以理解为进程的属性.比如进程的状态.进程的标识(PID)等,都被封装在了进程描述符这个数据结构中,该数据结构被定义为task_struct 进程控制块(PCB) 是操作系统核心中一种数据结构,主要表示进程状态. 进程状态 fork() fork()在父.子进程各返回一次.在父进程中返回子进程的 pid,在子进程中返回0. fork一个子进程的代码 #include <stdio.h> #include <stdli

  • Linux内核模块和驱动的编写

    Linux内核是一个整体是结构,因此向内核添加任何东西,或者删除某些功能,都十分困难.为了解决这个问题引入了内核机制.从而可以动态的想内核中添加或者删除模块. 模块不被编译在内核中,因而控制了内核的大小.然而模块一旦被插入内核,他就和内核其他部分一样.这样一来就会曾家一部分系统开销.同时,如果模块出现问题,也许会带来系统的崩溃. 模块的实现机制: 启动时,由函数 void inti_modules() 来初始化模块,因为启动事很多时候没有模块.这个函数往往把内核自身当作一个虚模块. 如由系统需要

  • 一张图看尽Linux内核运行原理

    众所周知的是,几乎整个互联网都运行在 Linux 上,从网络协议,到服务器,到你平常访问的绝大多数网站,都能看到它的身影.Linux 内核就是最复杂最流行的开源项目之一.如果你希望学习内核知识,在网上可以搜到无数的资料,但是 Linux 内核还是一个非常难弄明白的项目. 俗话说:一图胜千言,今天我们就为大家介绍一张完整的 Linux 内核运行原理图,通过这张图,你可以很方便地学习内核知识. 在 Linux 内核中,有许多层次.模块.功能调用和函数:要把其中的每一块儿都弄明白很不容易,不过 Mak

  • 解析Linux内核的基本的模块管理与时间管理操作

    内核模块管理 Linux设备驱动会以内核模块的形式出现,因此学会编写Linux内核模块编程是学习linux设备驱动的先决条件. Linux内核的整体结构非常庞大,其包含的组件非常多.我们把需要的功能都编译到linux内核,以模块方式扩展内核功能. 先来看下最简单的内核模块 #include <linux/init.h> #include <linux/module.h> static int __init hello_init(void) { printk(KERN_ALERT &

  • Linux内核链表实现过程

    关于双链表实现,一般教科书上定义一个双向链表节点的方法如下: 复制代码 代码如下: struct list_node{stuct list_node *pre;stuct list_node *next;ElemType data; } 即一个链表节点包含:一个指向前向节点的指针.一个指向后续节点的指针,以及数据域共三部分.但查看linux内核代码中的list实现时,会发现其与教科书上的方法有很大的差别.来看看linux是如何实现双链表.双链表节点定义 复制代码 代码如下: struct lis

  • SYN Cookie在Linux内核中的实现

    概述 在目前以IPv4为支撑的网络协议上搭建的网络环境中,SYN Flood是一种非常危险而常见的DoS攻击方式.到目前为止,能够有效防范SYN Flood攻击的手段并不多,而SYN Cookie就是其中最著名的一种.SYN Cookie原理由D. J. Bernstain和 Eric Schenk发明.在很多操作系统上都有各种各样的实现.其中包括Linux.本文就分别介绍一下SYN Flood攻击和SYN Cookie的原理,更重要的是介绍Linux内核中实现SYN Cookie的方式.最后,

  • Linux内核启动参数详解

    1.环境: Ubuntu 16.04 Linux linuxidc 4.4.0-89-generic #112-Ubuntu SMP Mon Jul 31 19:38:41 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux 2.查看当前linux内核的启动参数: cat /proc/cmdline 笔者的输出内容如下: BOOT_IMAGE=/boot/vmlinuz-4.4.0-89-generic root=UUID=bef418fa-4202-4513-b39

  • Linux内核中红黑树算法的实现详解

    一.简介 平衡二叉树(BalancedBinary Tree或Height-Balanced Tree) 又称AVL树.它或者是一棵空树,或者是具有下列性质的二叉树:它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1.若将二叉树上结点的平衡因子BF(BalanceFactor)定义为该结点的左子树的深度减去它的右子树的深度,则平衡二叉树上所有结点的平衡因子只可能是-1.0和1.(此段定义来自严蔚敏的<数据结构(C语言版)>) 红黑树 R-B Tree,全称是Red-B

  • Linux内核漏洞浅析

    与Windows相比,Linux被认为具有更好的安全性和其他扩展性能.这些特性使得Linux在操作系统领域异军突起,得到越来越多的重视.随着Linux应用量的增加,其安全性也逐渐受到了公众甚或黑客的关注.那么,Linux是否真的如其支持厂商们所宣称的那样安全呢?本期我们请到了启明星辰信息技术有限公司积极防御实验室工程师赵伟,对Linux进行专业的漏洞技术分析. Linux内核精短.稳定性高.可扩展性好.硬件需求低.免费.网络功能丰富.适用于多种cpu等特性,使之在操作系统领域异军突起.其独特的魅

  • linux内核copy_{to, from}_user()的思考

    目录 一.什么是copy_{to,from}_user() 1.copy_{to,from}_user()对比memcpy() 2.函数定义 二.CONFIG_ARM64_SW_TTBR0_PAN原理 三.测试 四.总结 一.什么是copy_{to,from}_user() 它是kernel space和user space沟通的桥梁.所有的数据交互都应该使用类似这种接口.但是他的作用究竟是什么呢?我们对下提出疑问: 为什么需要copy_{to,from}_user(),它究竟在背后为我们做了什

  • 简单谈谈Linux内核定时器

    软件意义上的定时器最终依赖硬件定时器来实现, 内核在时钟中断发生后检测各定时器是否到期 , 到期后的定时器处理函数将作为软中断在底半部执行 .实质上,时钟中断处理程序会 换起TIMER_SOFTIRQ软中断 ,运行当前处理器上到期的所有定时器. 总结起来还是软中断的流程 a.注册软中断处理函数 /*/linux/kernel.timer.c*/ void __init init_timers(void) -->open_softirq(TIMER_SOFTIRQ, run_timer_softi

随机推荐