|=-----------------------------------------------------------------=|
|=-----=[ D O   N O T   F U C K   W I T H   A   H A C K E R ]=-----=|
|=-----------------------------------------------------------------=|
|=------------------------[ #3 File 0x02 ]-------------------------=|
|=-----------------------------------------------------------------=|
|=------------------=[CVE-2013-1858 exploit analysis]=-------------=|
|=-----------------------------------------------------------------=|
|=-------------------------=[ By JU   ]=---------------------------=|
|=-----------------------------------------------------------------=|

提要:

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.so
的家伙来执行加载动态库这项工作的。粗略地讲,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环境外, 僭权成功!