2017-11-10 14:10:25
阅读:609次
点赞(0)
收藏
来源: gdelugre.github.io
作者:eridanus96
译者:eridanus96
预估稿费:200RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
前言
在今年4月,一个影响Samba的竞争条件(Race Condition)漏洞被公开,其编号为CVE-2017-2619,漏洞描述如下:
“对文件系统的输出部分具有写入权限的客户端,借助于SMB1 Unix扩展或NFS创建符号链接,再通过重命名一个被realpath()转换过的路径并创建符号链接的方式,实现对服务器的争用。而一旦客户端成功争用,由于此时已经完成对输出的共享路径的检查,就导致服务器会访问新的符号链接。而这个新的符号链接,则可以指向服务器文件系统中的任何位置。”
具体到这个Bug的利用,该公告补充说:“尽管该条件竞争难以成功,但从理论上讲是可能的。”
然而,假如共享文件夹储存在攻击者所控制的磁盘上,那么情况就变得完全不同。
准备阶段
一些网络设备(例如家庭使用的路由器,或网络存储器NAS)都支持外接U盘,并且能够在网络上共享该U盘中内容。我们就以法国常用的DSL路由器为例,这款路由器可以兼容绝大多数的ISP。
插入U盘后,系统会自动检测其分区,并且自动装载,并在局域网中创建网络共享。其中的网络共享,通常都会借助Samba来实现。
首先要做的,是在分区上创建一个符号链接,并验证我们目前确实无法读取除共享文件夹以外的任何文件。除非使用了非常旧版本的Samba,否则默认情况下,widelinks都会被设置为禁用,这次尝试应该会失败。
在服务器端,通常是按照下面的步骤来执行:
1. 接收到打开文件的请求,文件路径为A;
2. 检查路径A,确认其是否包含在共享文件夹内;
3. 如果检查无误,则该文件被打开。
Samba使用函数realpath转换路径,并验证它是否包含在共享文件夹中。在linux上,realpath将依次使用lstat和readlink系统调用来解析符号链接,而最终的操作将会调用open。
我们可以在受漏洞影响的Samba上运行strace来验证上述动作。当我们尝试打开路径a/b/c指向的file文件时,显示如下:
stat("a/b/c",{st_mode=S_IFREG|0644,st_size=0,...})=0 getcwd("/mnt/shared",4096)=12 lstat("/mnt/shared/a",{st_mode=S_IFDIR|0755,st_size=4096,...})=0 lstat("/mnt/shared/a/b",{st_mode=S_IFDIR|0755,st_size=4096,...})=0 lstat("/mnt/shared/a/b/c",{st_mode=S_IFLNK|0777,st_size=4,...})=0 readlink("/mnt/shared/a/b/c","file",4095)=4 lstat("/mnt/shared/a/b/file",{st_mode=S_IFREG|0644,st_size=0,...})=0 stat("a/b/c",{st_mode=S_IFREG|0644,st_size=0,...})=0 getxattr("a/b/c","system.posix_acl_access",0x7fff672973c0,132)=-1ENODATA(Nodataavailable) stat("a/b/c",{st_mode=S_IFREG|0644,st_size=0,...})=0 open("a/b/c",O_RDONLY)=12 由于这些操作不是完全同时进行的,因此无法保证我们所检查的文件与我们所打开的文件是同一个。我们此次攻击的基本方法,就是通过使用伪造的USB存储设备,使得realpath的操作对象和打开的操作对象是不同的文件,从而欺骗操作系统。
漏洞利用
要利用此设计缺陷,最直接的方法是:在调用realpath(路径, ……)函数后、调用open(路径, ……)函数前,触发对相应分区的重新装载。这种方式不但能够对不同的文件进行操作,并且还可以在不同的文件系统上进行。然而,这种方法是不切实际的,主要是因为以下两点:
1. 这一时间上的条件限定过于苛刻,几乎不可能满足;
2. 大多数网络设备,会在存储设备移除时,立即关闭服务端。
既然我们控制了存储设备,那么一个更好的办法就是,在访问某些磁盘扇区的时候,去检测对realpath的调用,并在open操作期间,返回不同的数据。
文件系统缓存
实际上,现代的操作系统都会尽可能地减少I/O访问的次数。在路径解析的过程中,Linux内核可能必须通过读取硬盘来获取路径组件(Path Component)的信息。但如果不再需要,该操作就不会再次执行。取而代之的是,会将这些信息保存在内存中的dentry(或dcache)缓存之中。
目录项(dentry),是将文件名与索引节点(inode)关联起来的内核结构。对于本地文件系统,它们会一直保留在dcache中,直到内核回收内存以供其他程序使用。
因此,open过程中的路径解析,会首先查找dcache中的每一个路径组件,而不会对其设备有所怀疑。
可以看出,在这里之所以使用缓存,是考虑到了性能方面,而并没有顾及到它的安全性。所以实际上,我们可以在两次调用之间,去删除缓存中的内容,这样的操作会更为可行。
删除缓存内容
Linux dcache是一个最近最少使用(LRU)的缓存,也就是第一个被替换的条目是最近使用次数最少的。缓存并没有固定的大小,它将会占用尽可能多的内存。但内核也同时有权在其他地方需要时,减小缓存占用的空间,并对内存进行回收。
当内存回收时,内核将枚举缓存中的目录项,并收集其中未使用的(引用计数器为0)。每一个目录项中都包含对其父目录项的引用。内核还会保留对当前正在寻找的路径组件的引用。
我们考虑路径a/b/c和路径a/d的解析。在路径解析后,目录项的树将如下图所示:
内核将首先对c和d进行回收,然后是b,最后是a。但是,如果在查找组件b的过程中发生,内核就无法释放它,同理a也一样,因为它们两个的引用计数器不会为0。
在这种情况下,即使是包含符号链接的路径,其结果也是一样的。
我们再考虑另一种情况:路径a/b/c,其中b是指向目录a/x的符号链接。
在b的解析过程中,内核将跟踪符号链接,并继续在x处进行路径遍历。符号链接b将保持其引用计数器为0,并且即使是在路径解析期间,它也可以从dcache中删除。
这就意味着,通过在路径中加入一个符号链接,使得在lstat系统调用返回前,有可能释放它的目录项。在接下来的调用open过程中,磁盘会再次被读取,从而对相同的路径执行打开操作。
在几个系统调用的短暂时间里,缓存的目录项看上去不太可能会被清除。但事实上是有可能的,因为我们可以让整个操作变得非常缓慢。
通过延迟SCSI相应数据包,我们可以让系统调用处于阻塞状态。Linux在默认情况下会等待30秒SCSI请求,然后再判断磁盘是否已经卡死。了解这一点之后,我们就可以借助于此,再加上大量的符号链接,轻松地让一个简单的realpath操作花上一个小时的时间,最后仍然会返回正确的结果。
我们再考虑路径a/b/c指向文件f的情况,我们可以将大量的符号链接聚在一起(但应少于40个,以避免ELOOP错误),并将它们放在磁盘上的不同位置,以有效减慢整个路径的解析速度。我们还可以通过拆分SCSI响应并返回较小的数据块,或者是通过在不同的物理位置使用索引节点创建中级目录(intermediate directories)来减慢进程。
当realpath操作在lstat和readlink之间被缓慢的I/O卡住时,我们可以开始将缓存的符号链接入口(图中的绿色框)清除出去。有多种方法可以实现,其中最为简单的一种是执行SMBLoris攻击,创建大量的连接并分配大量内存。在高强度的内存使用下,dcache的空间会变小。仅仅需要几秒钟的时间,就可以填满内存,并让目录项被清除,因此这种方法非常可行。当然,还可以与其他DoS漏洞一同使用,从而导致高内存消耗,达到相同的目的。
目录项被清除的时间,取决于设备上可用的RAM大小。我们可能需要对DoS进行一些设定,让它既能占用足够的内存来减小缓存空间,又不会导致触发OOMkiller的保护机制。
制作假USB存储
为了制作我们的“慢速存储设备”,我们需要一个具有USB OTG接口并运行Linux的设备,例如树莓派Zero。
使用Linux的g_mass_storage模块和OTG接口,我们可以模拟USB密钥。模块使用一个参数文件来指定磁盘映像的路径。我们指定该磁盘映像位于用户空间文件系统(FUSE)的挂载点。这样一来,我们可以在用户空间去控制对磁盘的每一次物理访问。
内核模块将SCSI READ命令转换为vfs_read调用。这一调用由用户空间文件系统处理,并转发至用户级程序。用户级应用将读取磁盘映像文件,并可能会调用sleep来延迟SCSI响应,具体要取决于所读取的内容。
攻击演示
下面的演示将在一台运行Samba 4.1.13的服务器上进行。分区的格式为ext3,包含符号树xxx1/…/xxx39,每一个xxx符号链接重定向到一个以magic_为前缀的目录,将会被我们的控制器截获。
在read操作的过程中,Samba 4将会进行如下操作:
1. 使用stat获得目标文件详细信息并缓存结果;
2. 检查文件名与realpath;
3. 检查文件权限与ACL(stat和getxattr);
4. 使用fstat获得文件描述符信息,检查设备/索引节点从第一步开始未发生更改;
5. 读取文件。
在步骤1中,我们将stat调用的指向转到/etc/passwd。在步骤2中,我们将路径指向转到一个虚拟的passwd文件。然后在步骤3,我们再次转回/etc/passwd。这样一来,我们就能有效地绕过步骤2和5之中的检查。
请注意,服务器有1GB的RAM。并且为了创建和销毁正确数量的进程,在DoS进行之前是经过计算的。为了使攻击成功,我们还必须使用不会超时的Samba客户端,以应对服务端响应时间过长的情况。在这里,我使用了一个依赖于pysmb的自定义脚本,也可以通过修改过的smbclient来实现。
结语
在4.4.12、4.5.7和4.6.1版本后的Samba不受此漏洞的影响。用于攻击的源代码请参见:https://github.com/gdelugre/path-pivot。
值得一提的是,这样的攻击还可以扩展到不同的场景之中,比如也可能会适用于:
UPnP多媒体服务器(DLNA);
为外部驱动器提供文件浏览功能的设备(图像查看器、多媒体播放器);
固件更新时,其签名被验证并存储在USB密钥中。
该攻击方法给我们带来的经验是,不要理所当然地认为外部磁盘会在不同调用中返回相同的值,在处理外部驱动器上存储的文件时要多加小心。
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://gdelugre.github.io/2017/11/06/samba-path-pivot-attack/