首页
社区
课程
招聘
[原创]CVE-2017-1000367 分析与复现
2017-6-7 08:50 12088

[原创]CVE-2017-1000367 分析与复现

2017-6-7 08:50
12088

这个漏洞是在5.30号正式公布的,国内有很多文章有关于该漏洞的预警,也就是介绍下影响范围,版本之类的信息,而且基本都是一份的各种转载。在freebuf上有一个比较详细的介绍,但是也主要是以翻译为主,并未讲的很透彻。

http://www.freebuf.com/vuls/136156.html

因此准备对这个漏洞进行分析,并依据github上公布的PoC对该漏洞复现。

主要解决两个问题

1. 造成该漏洞的原因是什么? 补丁如何修补

2. 利用该漏洞能够达到怎样的效果

漏洞成因与补丁修复

原因是在Linux执行sudo操作的时候,需要获取进程的tty设备号,而获取的地方是在/proc/[pid]/stat文件下。stat文件的详细描述在http://blog.csdn.net/cybertan/article/details/7596633 这篇博客中有很详细的描述。

其中我们比较关心的是两点,一是这里面的值都是一项一项的,并且每一项都是使用空格进行分隔开。

二是第二项表示 应用程序(进程)的名字,是用小括号包含,并且允许空格的存在;第七项是tty_nr,就是前面提到需要获取的tty设备号。

漏洞成因就在获取tty设备号的时候:

https://bugzilla.redhat.com/attachment.cgi?id=1282109 (补丁前后对比的代码)

使用的函数为get_process_ttyname,关键代码如下:

 	len = getline(&line, &linesize, fp);
 	fclose(fp);
 	if (len != -1) {
-	    /* Field 7 is the tty dev (0 if no tty) */
-	    char *cp = line;
-	    char *ep = line;
-	    const char *errstr;
-	    int field = 0;
-	    while (*++ep != '\0') {
-		if (*ep == ' ') {
-		    *ep = '\0';
-		    if (++field == 7) {
-			dev_t tdev = strtonum(cp, INT_MIN, INT_MAX, &errstr);
-			if (errstr) {
-			    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
-				"%s: tty device %s: %s", path, cp, errstr);
[......]
-		    cp = ep + 1;

基本流程为从 stat文件下读取一行(tty设备号在第一行),然后按照空格取值,当遇到第七个空格的时候,那么前面那个记录的cp指针指向的就是tty设备号了,然后将其拿出来即可获取。而成功的前提在于前面只有用于分隔的空格,项中是没有空格的,而第二项的进程名中是允许包含空格的,而这一项是我们可以控制的。

所以我们可以通过给第二项添加空格,来控制第七项的值,因此我们能够随意控制tty设备号。这是第一个问题点所在。

所以对应的补丁就修补了查询方式:

+	    /*
+	     * Field 7 is the tty dev (0 if no tty).
+	     * Since the process name at field 2 "(comm)" may include spaces,
+	     * start at the last ')' found.
+	     */
+	    char *cp = strrchr(line, ')');
+	    if (cp != NULL) {
+		char *ep = cp;
+		const char *errstr;
+		int field = 1;
+
+		while (*++ep != '\0') {
+		    if (*ep == ' ') {
+			*ep = '\0';
+			if (++field == 7) {
+			    dev_t tdev = strtonum(cp, INT_MIN, INT_MAX, &errstr);
+			    if (errstr) {
+				sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+				    "%s: tty device %s: %s", path, cp, errstr);
+			    }
+			    if (tdev > 0) {
+				errno = serrno;
+				ret = sudo_ttyname_dev(tdev, name, namelen);
+				goto done;
+			    }
+			    break;

主要的添加代码,在对第七项的查询时,通过进程名项使用小括号包着的特点,从')'往后开始查询。

看到前面链接中的代码,其实还补了另外一处:

 static char *ignore_devs[] = {
     "/dev/fd/",
+    "/dev/mqueue/",
+    "/dev/shm/",
     "/dev/stdin",
     "/dev/stdout",
     "/dev/stderr",

这就和后面的利用有关系了,获取设备号后处理流程,当获取到一个不为0的设备号后,即认为是存在tty设备的,此时将调用sudo_ttyname_dev函数依据设备号去搜索指定的设备名。搜索顺序是首先在search_devs数组中包含的路径下搜索,若不存在,将在/dev路径下进行广度优先搜索。详细可见:https://github.com/Distrotech/sudo/blob/master/src/ttyname.c 的sudo_ttyname_dev(dev_t rdev)函数,代码比较简单。

我在centos 7上查看了dev文件夹下的权限情况。

[root@localhost dev]# ls -ld
drwxr-xr-x. 20 root root 3280 Jun  5 02:14 .
[root@localhost dev]# ls -ld shm/
drwxrwxrwt. 2 root root 180 Jun  5 04:30 shm/
[root@localhost dev]# ls -ld mqueue/
drwxrwxrwt. 2 root root 40 Jun  5  2017 mqueue/

其中 shm和mqueue文件夹是普通用户可写,那么也就意味着当我们设置为一个不存在的tty设备号的时候,通过对/dev文件夹进行搜索时,能够触碰到我们自己创建的文件。

所以第二个修补位置就是添加了两个忽视搜索的路径,即shm和mqueue文件夹不放置在搜索路径下。


漏洞影响和复现

freebuf上给的标题为

SELinux曝新安全漏洞:用户执行sudo命令可获取root权限

其实很多时候不是很明确sudo和root之间的区别,尤其是在ubuntu上默认用户安装的时候,sudo之后的用户基本上和root是一个权限。

但是在包含SELinux的Linux系统中,sudo获取的权限和root的差异很明显,尤其是在使用centos的时候差异很明显。

详细可以参考:http://www.jb51.net/LINUXjishu/12713.html

简单描述为,普通用户使用sudo执行命令流程是当前用户切换到root,然后以root的身份执行命令,执行完成后再次回到当前用户。

权限问题:普通用户的sudo权限是root给定的,root决定普通用户能够使用sudo执行哪些操作。具体的设置在/etc/sudoers文件下,如添加一条

## Allows members of the users group to shutdown this system
# %users  localhost=/sbin/shutdown -h now
## Read drop-in files from /etc/sudoers.d (the # here does not mean a comment)
#includedir /etc/sudoers.d
centos ALL=(ALL) NOPASSWD: /usr/bin/sum

那么centos用户能够执行sudo,但是只能执行sum命令

[centos@localhost ~]$ sudo vim /etc/passwd
[sudo] password for centos: 
Sorry, user centos is not allowed to execute '/bin/vim /etc/passwd' as root on localhost.localdomain.
[centos@localhost ~]$ sudo sum /etc/passwd
11634     3
[centos@localhost ~]$

废话少说了,入正题:漏洞效果怎样?

因为是通过sudo命令对get_process_ttyname函数执行的,因此利用该漏洞前提是能够拿到一个用户的sudo权限,而在使用了SELinux的系统中,会将标准输入输出(stdout, stderr, stdin)信息写入到文件之中,这个文件是当前进程所对应的虚拟终端的文件。(使用sudo -r unconfined_r能够触发)(这里因为不是很了解,描述比较模糊,详细可参考SELinux的一些处理)。

因此当我们能够控制这个文件,就能够控制对任意文件的写入,因为这个写入操作也是在root权限下,所以能够对敏感文件写入,如/etc/sudoers。

所以该漏洞的利用效果为,能够将一个拥有sudo权限的用户提升到root权限。

因为github找到了PoC,能够实现任意文件的写入,并且也测试成功,因此将依据该PoC来介绍下如何提权。

https://github.com/c0d3z3r0/sudo-CVE-2017-1000367/blob/master/sudopwn.c

mkdir("/dev/shm/_tmp", 0755);
symlink("/dev/pts/57", "/dev/shm/_tmp/_tty");
symlink("/usr/bin/sudo", "/dev/shm/_tmp/     34873 ");
[......]
execlp("/dev/shm/_tmp/     34873 ", "sudo", "-r", "unconfined_r", "/usr/bin/sum", "--\nHELLO\nWORLD\n", NULL);

首先是创建一下符号链接了,先看第二个,第一个的作用将在后面描述。因为将/dev/shm/_tmp/     34873 链接到sudo了,所以执行它的时候也会调用漏洞函数get_process_ttyname,使用execlp执行,那么就能够把我们的34873写入到第七项tty设备号中了,下次获取的时候就是通过34873来调用sudo_ttyname_dev来查询了,因为这个是我们写入的一个不存在的tty设备号,那么在search_devs[]数组指定的路径中是找不到的,因此需要去/dev文件夹下搜索,此时添加一个消息反馈,当搜索到我们创建的文件夹/dev/shm/_tmp的时候给予消息,即

wd = inotify_add_watch( fd, "/dev/shm/_tmp", IN_OPEN | IN_CLOSE_NOWRITE );

当接受到消息的时候,打开文件夹,将对里面进行搜索,那么将会触发到我们设置的第一个符号链接,链接到"/dev/pts/57",这个也是不存在,但是符号链接,等同于Linux下的ln -s命令,链接双方只要有一个变化,另一个也会发生变化,那么本来链接到空,触发第一个符号链接时将创建"/dev/pts/57"(这个在centos下使用ln -s测试成功)。

此时接着是一个循环执行openpty,尝试获取一个可用的虚拟终端,当其创建之后,那么一定能够获取到,并给当前进程使用,也就是前面提到的SELinux 将会把标准输入输出写入到这里。因此在该文件搜索结束之后,触发CLOSE,把链接的文件修改了我们需要控制的文件,那么当有标准输入输出的时候将会输出到这个文件下。(这里描述有误 感谢@XiaoHuaMe)

以下为添加修改:

当打开/dev/shm/_tmp 文件夹时,触发消息,将进入父进程,并终止子进程;此时使用openpty循环获取可用的虚拟终端。前面对于openpty的理解存在错误,假设/dev/pts目录下只有0和1,那么此时应该都在被占用状态,下一个可用的将会是2,可以在openpty后面加一个while(1);发现在/dev/pts目录下创建了2,那么此时将进入while循环。

while(strcmp(pts_path,"/dev/pts/57")){
						openpty(&master, &slave, &pts_path[0], NULL, NULL);
					};

然后会继续出现3,4,直到57的存在。(58是因为我新开了一个终端查看pts)

[centos@localhost ~]$ ls /dev/pts/
0   11  14  17  2   22  25  28  30  33  36  39  41  44  47  5   52  55  58  8
1   12  15  18  20  23  26  29  31  34  37  4   42  45  48  50  53  56  6   9
10  13  16  19  21  24  27  3   32  35  38  40  43  46  49  51  54  57  7   ptmx

还有另一个点,device name为 34873,那么需要查找的pty为57(这里并未找到权威的说明,望大家指正^^)

作者的描述如下:

and a symlink "/dev/shm/_tmp/_tty"
  to a non-existent pty "/dev/pts/57", whose device number is 34873;

此时已经创建了57,那么唤醒子进程,继续搜索,将按照符号链接找到57,并返回设备名为对应的符号链接(/dev/shm/_tmp/_tty)。

此时再修改符号链接指向,而SELinux将按照这个符号链接的指向文件进行标准输入输出的写入。


这里利用的是stderr;因此能够达到任意文件写入。

if ( event->mask & IN_OPEN ) {
					//kill(pid, SIGSTOP);
					while(strcmp(pts_path,"/dev/pts/57")){
						openpty(&master, &slave, &pts_path[0], NULL, NULL);
					};
					//kill(pid, SIGCONT);
					break;
				}else if ( event->mask & IN_CLOSE_NOWRITE ) {
					//kill(pid, SIGSTOP);
					unlink("/dev/shm/_tmp/_tty");
					symlink("/etc/motd", "/dev/shm/_tmp/_tty");
					//kill(pid, SIGCONT);
					state = 1;
					break;
				}

主要模糊点,是在SELinux所默认OPEN写入的文件的问题。作者的描述为:

because relabel_tty() (in src/selinux.c) calls open(O_RDWR|O_NONBLOCK)
on his tty and dup2()s it to the command's stdin, stdout, and stderr.



参考链接

http://www.openwall.com/lists/oss-security/2017/05/30/16

https://www.sudo.ws/alerts/linux_tty.html

https://github.com/jihadLkmaty218/CVE-2017-1000367/blob/master/sudopwn.c

https://bugzilla.redhat.com/show_bug.cgi?id=1453074

https://github.com/Distrotech/sudo/blob/master/src/ttyname.c

http://blog.csdn.net/cybertan/article/details/7596633

http://www.jb51.net/LINUXjishu/12713.html

openpty

http://blog.sina.com.cn/s/blog_8b3afa210100t9v3.html




[培训]《安卓高级研修班(网课)》月薪三万计划,掌 握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
点赞1
打赏
分享
打赏 + 1.00雪花
打赏次数 1 雪花 + 1.00
 
赞赏  CCkicker   +1.00 2017/06/19
最新回复 (2)
雪    币: 33
活跃值: (26)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
XiaoHuaMe 2017-6-9 22:00
2
0
我觉得作者  “当接受到消息的时候,打开文件夹,将对里面进行搜索,那么将会触发到我们设置的第一个符号链接,链接到"/dev/pts/57",这个也是不存在,但是符号链接,等同于Linux下的ln  -s命令,链接双方只要有一个变化,另一个也会发生变化,那么本来链接到空,触发第一个符号链接时将创建"/dev/pts/57"(这个在centos下使用ln  -s测试成功)。,这是有些出入的,不应该是在遍历到那个链接的时候才去/dev/pts/57,而是在还没有遍历的时候进程就已经被阻塞住了,此时就是已经存在/dev/pts/57了  因为已经存在了,所以就是不是遍历所导致的dev/pts/57,而是由于这个函数openpty(&master,  &slave,  &pts_path[0],  NULL,  NULL);才会不停的创建,所以先要把sudo遍历的进程阻塞住,然后创建了/dev/pts/57  然后sudo进程被启动,这个时候因为通过设备号找设备名字,所以/dev/shnm/_tmp/_tty的设备号就是34873所以匹配到了就结束,然后get_tty_name返回的设备名字就是软连接的名字  这个时候因为selinux的机制,在close的时候把这个软连接指向一个我们需要改的一个文件,这样就是可以把输入输出写入到我们普通用户没有办法写的文件中区,从而达到提权的目的。
雪    币: 148
活跃值: (148)
能力值: ( LV6,RANK:140 )
在线值:
发帖
回帖
粉丝
icepng 2 2017-6-9 23:34
3
0
谢谢指正
游客
登录 | 注册 方可回帖
返回