这是我分析CVE-2013-1858的一篇文章. 论坛关于linux方面的资料似乎不多.我也就以这篇文章,当作我来论坛的见习作品 :-)
*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
提要:
2013年3月13日,Suse的安全研究员Sebastian Krahmer发出一封名为:
"CLONE_NEWUSER|CLONE_FS root exploit"
的邮件,暴出3.8版本的Linux内核存在一个提权漏洞,并给出了poc:
http://www.openwall.com/lists/oss-security/2013/03/13/10
该exploit利用了3.8内核允许*普通用户*利用clone(... CLONE_NEWUSER | CLONE_FS,
...)创建新进程的漏洞,巧妙实现提权。该漏洞利用的精妙之处在于:只是利用两个概
念上的缺陷,不需要费尽心力写复杂的shellcode, 仅若干个普通API就实现了提权。而
且, 这个exploit完美地演绎了一个程序如何在不同的euid下3次运行,每次进行不同
的动作。三次运行环环相扣,一步步帮助获得系统的权限。
另一方面, 给力的是,当天内核开发者就及时给出了fix:
http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=e66eded8309ebf679d3d3c1f5820d1f2ca332c71
漏洞分析:
1.CLONE_NEWUSER与CLONE_FS狼狈为奸。
随着Linux 3.8版本释出,标志历经多年开发(从2007年的2.6.23版本开始)的用户名字
空间(user namespace)的开发工作基本完成。这是第6个完成的内核名字空间(
http://lwn.net/Articles/531114/#series_index)。它们是实现内核容器(container)
中的一部分。内核容器是用于分隔资源,系统监管以及虚拟化的一个轻量级工具,简单
来说,就是把系统资源进行分隔,这样不同容器中的资源互不感知对方,实现有效分隔
。而用户名字空间的完成,有着重大意义,因为每个普通用户都可以建立自己的名字空
间。每一个用户名字空间中,有一套独立的uid系统,这意味着每个空间中有各自的roo
t用户!也就意味着,在这个名字空间中,进程可以拥有任意权限(capabilities),包
括调用chroot切换根目录。而本来分隔的名字空间类似sandbox,不会带来太多安全问题
。但是,3.8内核允许普通用户创建名字空间!
对于CLONE_NEWUSER来说,它意味着创建的新进程需要独立的用户名字空间。
至于CLONE_FS标志,它表示创建的子进程要跟父进程共享文件系统属性,比如有相同的根目录...
独立与共享?!这隐隐约约就带来了坏味道!于是,隐患已经埋下,潘多拉的盒子即将被打开......
2.chroot, 罪恶之*根*
前面讲了,两个奇怪的标志一结合,立马带来了隐患。一个是独立的名字空间,一个是
共享的文件系统空间,如何让封印于盒子里的怪兽逃出生天呢。工具之一就是chroot!
chroot是在Linux系统中发挥根目录的切换作用的命令,它也带来系统的安全性等好处
。比如有玩过lfs或gentoo的同学都知道,当在host环境下搞定新环境的工具链后,一
条chroot命令就可以切换到新的环境,然后筚路蓝缕,开始打拼另一片天空。
如下:
/( / : 原来的根目录)
|
-----------------------
| | | |
bin lib home/lcx ......
|
newroot(/home/lcx/newroot : 新的环境的根目录)
|
-----------
| | |
bin lib usr .....
当执行:
chroot /home/lcx/newroot
就将原来的/home/lcx/newroot变为新环境的根目录。此时,原来的文件系统被掩盖。
压迫不再,曾经的旧民翻身,新的政治秩序铺开。
那此时如果还用原来的名号,会发生什么情况呢?很简单,发生了"坐标系平移"。比如
旧环境的/bin/su,将被映射成新环境的/home/lcx/newroot/bin/su;
旧环境的/lib/ld-linux.so.2,将被映射成/home/lcx/newroot/lib/ld-linux.so.2.
好,咱花开两朵,先表一枝。这一厢先按下,稍后再叙。
3.动态链接器ld-linux.so, 引狼入室
动态库顾名思义就是程序运行才加载依赖的库。在linux上,这是由一个叫ld-linux.s=
的家伙来执行加载动态库这项工作的。粗略地讲,ld-linux.so把程序加载与开始执行之
间活动,搜索程序用到的库,并映射到程序的进程空间,以及完成一些符号解析等dirty
job, 然后,才把程序流控制权交到程序入口点开始执行。
此外,关于ld-linux.so还有一点需要强调, 它拥有与所执行程序相同的权限。意思就是
,当程序以有效用户ID(euid)为root的ID运行时,那ld-linux也以root权限运行.
那ld-linux.so在哪? 它不应该包含在程序的可执行文件中。因为,如果系统有上千个
要用到动态库的程序, 哪岂不是有上千份拷贝.
其实, ld-linux.so是在系统中。在程序运行时,它首先被加载,然后由它加载动态库。
$ ls /lib | grep ld-linux (或ls /lib64 | grep ld-linux)
可以看到,存在/lib/ld-linux.so.2(我系统32位的,纯64位系统会发现它在/lib64中)。
由它的路径与名称看来,它本身就是一个库(用file命令查它户口,发现它是shared
object, ELF格式中的一种:共享对象,其它库文件同样也是一这种格式)。
这里面隐含着,系统能加载ld-linux.so, 说明系统知道这个ld-linux.so的路径,它藏在哪呢,就在程序执行映象里:
$ readelf -l /bin/ls
以上命令读取ls程序的可执行映象,能发现输出结果中出现有Requesting program
interpreter:/lib/ld-linux.so.2的字样。
这里面,它作了一个假设: 我要找的动态链接器ld-linux.so它位于*根目录*下的lib目
录中。它硬编码了路径! 万一运行过程中这个根目录变了呢,这路径不就指到别的地方
去了?! 存在这种可能吗? 可能! 这就是这个exploit的妙处所在。
4. 打开魔盒
上面说的这个exploit妙在,它巧妙地在程序运行过程中,改变了根路径!
改变了根路径后,它执行了一个setuid程序su(运行ls -l /bin/su, 你会看到它的权限
是这样的:-rwsr-xr-x, 其中发现有一位变成了s。这个程序就叫setuid程序。至于这个
s的含义,涉及到真实用户ID与有效用户ID的关系,网上有详细解说,读者可以在网上找
到相关方面文章), 而这个su在运行前是先要运行ld-linux.so来加载动态库。
前面说了, ld-linux.so路径是硬编码成/lib/ld-linux.so.2的。但是, 现在根路径已
经变了! 所以, 此时的/lib/ld-linux.so.2已经不是指向真正的ld-linux.so了。它现
在指向了改变后的根目录下的lib/ld-linux.so.2。而这个位置, 放的就是exploit程序
自身! 这当然不是凑巧的,而是在改变根路径过程中使用了技巧而实现。
所以, 现在相当于又运行了exploit一次。不过, 这次的有效用户ID(EUID)是0。因为
前面说它执行了一个setuid的程序su。而在第3节说过, 执行ld-linux.so时, 它拥有跟
su一样的权限: 即EUID为0用户,也就是root的权限。这就是为什么要选用一个setuid权
限的程序的原因。
然后, 重新运行的exploit进程做了啥呢? 它把自己的uid设为0, 即让自己成为root用
户, 然后, 再运行一次bash。此时, 这个shell就是一个root权限的shell了。僭权成功
!!!
这就是这个exploit大概的思路, 至于是如何改变根路径的, 是重头戏, 有兴趣的读者
可以继续往下看。下面是对这个exploit更详细的讲解.
********************* 分隔线 *********************
它是这么做的:
我们先设定这个exploit程序叫evil, 它的路径不妨设为/home/lcx/evil。然后我们进
入到/home/lcx目录,执行
$ ./evil
我们以普通用户运行这个程序。一切都还显得是风平浪静。我们称此时的进程为A。
A莫名其妙地做了这几个动作.
1. 读取/proc/self/exe获取自己的路径,也就是/home/lcx/evil(这不是多此一举,因为
程序自身并不知首自己的路径,而即使我们把程序放别的地方,它还是能准确找
己的巢)。
2. fork了一个进程B, 然后它就去睡觉, 间隔1秒醒来看evil这个程序文件的用户
ID是否变成root了。当然, 现在还不是,所以它去睡觉~~~
创建出来的B干嘛呢?
1. 在当前目录(/home/lcx)创建了一个chroot, 也就是/home/lcx/chroot。
2. 在chroot下再建了两个目录,是为/home/lcx/chroot/bin和
/home/lcx/chroot/lib。
3. 在/hom/lcx/chroot/bin目录下, 它生成一个链接/home/lcx/chroot/bin/su,
指向原来/bin/su。
4. 在/hom/lcx/chroot/lib目录下, 它生成一个链接
/hom/lcx/chroot/lib/ld-linux.so.2, 但是, 它指向evil自己:/home/lcx/evil。
5. 调用pipe API生成一个管道准备通信。和谁通信?这个稍后说。
不妨以图示说明:
/
|
------------------------
| | |
bin lib home/lcx ------
| | | |
su ld-linux.so chroot evil
^ | ^
| ------- |
| | | |
| bin lib |
| | | |
|______ su ld-linux.so____ |
觉得跟上面介绍chroot一节时画的图有些类似? 没错, chroot要登场了, 前面这些莫名
其妙的动作不过是为舞台布景!
不过, 我们现在还是普通用户, 是没有root权限执行chroot的(是的,evil程序就是要获
取root权限, 但现在还不是)。但是,没有权限也要创造权限, 方法就是利用clone的
CLONE_NEWUSER, 生成一个新的子进程C。前面说过,它生成的子进程将在一个新的用户
命名空间里, 它可以做任何事, 包括chroot。
但是, 不要忘了, 现在我们是在子进程C的新的名字空间中, 再闹得天翻地覆也没用。
所以, 另一个标志出来救场了, 就是CLONE_FS。利用它我们可以巧妙地把魔鬼释放到盒
子外面! 前面说了这个标志是让子进程与父进程共享文件系统的属性的。那么, 当我们
在子进程里chroot后, 改变了根目录。既然是共享, 父进程的根目录也被改变了!
进程C做了什么好事呢?
1. C从管道中读取一个字符。我们前面说过B创建了一个管道。管道是Linux中父子进
程通信的一种方法。当一端读的时候,如果没数据,会被阻塞。所以,C从管道中读
取什么信息不重要,关键是,C必须得等到它的父进程B往里面写入数据。
那B写入数据前做了啥?
- 它创建了UID映射,简言之。它写了/proc/${Pc}/uid_ma文件, 把B的uid映射到
到C的uid。这里${Pc}是指C的pid, 它是从clone返回的。
- 然后往管道中写了个字符。
- 调用waitpid()等待它的子进程C。
所以,等c读到这个字符时,实际上uid映射已经完成了。有什么用?接着看。
2. C用setuid()把自己uid设为root(记住此时C是在自己的名字空间中,所以是
允许它这么做的)。由于前面作了uid映射,所以此时B的uid也是root!
3. 当然,C最后调用chroot()切换了根目录!
前面说过, A每睡1秒就醒来看自己的uid是不是变成root了。现在,终于变成root了!
于是,A开心地再一次执行了evil这个程序(这是第2次运行这个程序)。此时,它的有效
用户ID(euid)是0,于是,它把/lib/ld-linux.so.2的权限设为04755。这个值表示,
这个程序现在是一个setuid程序了. 前面说过,C切换了根目录,并且CLONE_FS标志是
表示共享文件系统属性的,所以,这时修改的ld-linux.so.2其实就是evil!(参见上图)
。也就是, evil从一个普通的程序变成一个setuid程序! 然后,这个进程退出。
此时,C完成使命,它也退出。
另一方面,在管道中写入数据后就调用waitpid()等待C的B终于等到C退出, 它运行了一
个setuid程序su(前面通过uid映射,B此时的uid是0了,所以它能运行su)。如前面所说
, 这个su要调用/lib/ld-linux.so.2, 而当前根目录已经变为/home/lcx/chroot, 所以
,此处调用的是/home/lcx/chroot/lib/ld-linux.so.2。而前面已经将/home/lcx/chroo
t/lib/ld-linux.so.2链接到evil自身(见图).
所以,此时运行的还是evil自身.(这是evil这个程序第3次被运行了!)
不同的是此时运行的evil已经是一个setuid程序, 运行它时euid就是0! 还有,要注意,
现在是第三次运行eveil, 它已经是在chroot环境外了(受chroot影响的只有C和B)。
然后, evil把自己的uid设为0, 再运行bash。这是一个有root权限的bash, 并且是在
chroot环境外, 僭权成功!
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!