انجمن‌های فارسی اوبونتو

لطفاً به انجمن‌ها وارد شده و یا جهت ورود ثبت‌نام نمائید

لطفاً جهت ورود نام کاربری و رمز عبورتان را وارد نمائید

نویسنده موضوع: KRSI و آزمایشی برای اندازه گیری کارایی  (دفعات بازدید: 725 بار)

0 کاربر و 1 مهمان درحال مشاهده موضوع.

آفلاین prbzr

  • Newbie
  • *
  • ارسال: 10
مختصری درباره ی گذشته ی LSMها

در کرنل نسخه 5.4 هشت LSM وجود دارد - SELinux، SMACK، AppArmor، TOMOYO، Yama، LoadPin، SafeSetID، و .Lockdown همچنین LSMهایی مانند SARA و KRSI در دست توسعه هستند که ممکن است به زودی جای خود را در منابع هسته در کنار سایرین بگیرند. دلیل اینکه چرا چنین تنوعی برای LSM وجود دارد، این است که برخی یک مساله ی رایج نوعی را به روش های منحصر به فرد حل می کنند در حالی که برخی دیگر به مسائل خاص می پردازند. تنها آگاهی از این تفاوت ها می تواند درک ما را از ویژگی ها و چالش های امنیتی در سیستم های لینوکس افزایش دهد.

LSMها چیستند؟

یک LSM کدی است که مستقیماً در کرنل لینوکس کامپایل شده است و با استفاده از فریم ورک LSM، می تواند دسترسی یک فرآیند به اشیاء مهم کرنل (Kernel Objects) را رد کند. انواع اشیا محافظت شده شامل فایل ها، inodes، ساختارهای وظیفه (Task)، اعتبارنامه ها (Credentials) و اشیاء ارتباط بین پردازشی (Interprocess) می باشد. دستکاری این اشیاء نشان دهنده ی روش اصلی تعامل فرآیندها با محیط خود است و با مشخص کردن دقیق تعاملات مجاز، یک مدیر امنیتی می تواند سوء استفاده ی مهاجم از نقص موجود در یک برنامه را برای حمله به مناطق دیگر سیستم دشوارتر کند.

اولین و مسلماً رایج ترین مورد استفاده برای چارچوب LSM، اجرای سیاست های کنترل دسترسی اجباری, MAC(Mandatory Access Control) است. جای تعجب نیست که چندین رویکرد در مورد نحوه ی بهترین پیاده سازی MAC ها در هسته وجود دارد. در سال 2001، پیتر لوسکوکو از آژانس امنیت ملی یکی از اولین پیاده سازی ها را در اجالس کرنل لینوکس 2.5 ارائه کرد. جاناتان کوربت از اخبار هفتگی لینوکس پس از آن خاطرنشان کرد:

"شاید جالب ترین چیزی که از این بحث به دست آمد، مشاهده این بود که تعدادی از پروژه های مرتبط با امنیت کار مشابهی را انجام می دهند. همه دوست دارند روزی نتیجه ی کار را در هسته ی استاندارد ببینند. با این حال، در حال حاضر بسیار سخت است که بدانیم یک مکانیزم استاندارد لینوکس با امنیت بالا واقعاً چه شکلی باید باشد. بنابراین، توسعه دهندگان واقعاً دوست دارند پروژه های امنیتی را با هم ببینند و مجموعه ای از رابط های استاندارد را تعریف کنند که به همه ی آن ها اجازه می دهد به هسته متصل شوند، به طوری که کاربران بتوانند با بیش از یکی از آن ها آزمایش کنند. این پروژه ی آسانی به نظر نمی رسد، انتظار نداشته باشید که به این زودی آن را ببینید."
(Jonathan Corbet, Linux Weekly News)

چند سال بعد "مجموعه رابط های استاندارد" (Set of Standard Interfaces) به فریم ورک LSM تبدیل شد. از نسخه 5.4 تا کنون، فریم ورک شامل 224 نقطه هوک در سراسر کرنل، یک API برای ثبت توابع در این نقاط هوک و یک API برای رزرو حافظه مرتبط با اشیاء محافظت شده ی کرنل برای استفاده توسط LSMها است. با انتشار کرنل 2.6 در پایان سال 2003، هر دو فریم ورک LSM و SELinux برای استفاده از فریم ورک به جای پچ شدن مستقیم کرنل اصلاح شد (ادغام شدند). در حالی که جزئیات پیاده سازی LSM در طول زمان تغییر می کند، هدف اصلی اجازه دادن به کنترل دقیق بر دسترسی به اشیاء مهم کرنل است.

فریم ورک LSM کنونی به کاربر اجازه می دهد تا چندین LSM را در کرنل کامپایل کند که می توانند به طور همزمان روی هم اِستَک و استفاده شوند. نمودار زیر یک جریان فراخوانی را برای ()open نشان می دهد که در آن سه LSM هوک های ثبت شده دارند:

    1. یک فرآیند (Process) در فضای کاربر (user-space)، ()open را در مسیر فایل فراخوانی می کند.
    2. فراخوانی سیستم (System Call) ارسال می شود و رشته مسیر برای به دست آوردن یک شی فایل کرنل استفاده می گردد. اگر پارامترها نادرست باشند، یک خطا برگردانده می شود.
    3. مجوزهای فایل کنترل دسترسی اختیاری "عادی" بررسی می شود. آیا کاربر فعلی مجوز باز کردن فایل درخواستی را دارد؟ در غیر این صورت، سیستم کال خاتمه می یابد و یک خطا به کاربر برگردانده می شود.
    4. اگر DAC ها راضی باشند، فریم ورک LSM هر یک از هوک های file_open را برای LSM های فعال فراخوانی می کند. سیستم کال خاتمه مییابد و اگر یک هسته ی LSM خطایی را برگرداند، یک خطا به کاربر برمی گردد.
    5. در نهایت، اگر تمام بررسی های امنیتی انجام شود، فایل برای پردازش باز می شود و یک توصیف کننده فایل (file descriptor) جدید به فرآیند در فضای کاربر باز می گردد.


محل LMS در یک سیستم کال open

LSMهای اصلی، فرعی و انحصاری

با درک اولیه از چیستی LSM، می توانیم توجه خود را به آنچه که LSM های مختلف واقعا در کرنل انجام می دهند معطوف کنیم. برای شروع، اجازه دهید نگاهی به LSM" های اصلی" داشته باشیم. این LSMSها - SELinux، SMACK، AppArmor و TOMOYO - همگی پیاده سازی های MAC با خط مشی های قابل تنظیم هستند که از فضای کاربر بارگیری می شوند. آن ها مسائل مشابه را به روش های منحصر به فرد حل می کنند.
LSMهای اصلی قدیمی از نظر تاریخی نمی توانستند همزمان بارگیری شوند. زیرا طراحان اصلی فریم ورک LSM اجازه فعال کردن تنها یک LSM را می دادند. همه ی LSMهای اصلی فرض می کردند که به نشانگرهای زمینه ی امنیتی (Security Context Pointers) و شناسه های امنیتی (Security Identifiers) تعبیه شده در اشیاء کرنل محافظت شده، دسترسی انحصاری دارند. بنابراین، تنها یک LSM اصلی می تواند در هر زمان مورد استفاده قرار گیرد، که همانطور که در شکل زیر مشاهده می شود میتواند به عنوان یک گزینه در زمان کامپایل کرنل پیکربندی شود و یا به عنوان یک پارامتر در خط فرمان کرنل ارسال گردد.


فهرست پیکربندی کرنل برای انتخاب LSM اصلی پیش فرض

در ادامه ی روند، فریم ورک LSM برای حذف این تمایز بین LSMهای "اصلی" و "فرعی" تغییر و تحول می یابد. در حال حاضر روش ترجیحی برای تشخیص LSMها با پرچم LSM_FLAG_EXCLUSIVE است تا واضح تر شود که تمایز اصلی / فرعی واقعاً در مورد انحصار است. یک کاربر می تواند هر تعداد LSM را تا زمانی که فقط یکی از آن ها دارای مجموعه LSM_FLAG_EXCLUSIVE باشد را پیکربندی کند. در حال حاضر فقط SELinux، SMACK و AppArmor به عنوان انحصاری علامت گذاری شده اند، AppArmor هم به زودی به این مجموعه می پیوندد و سایرین نیز دنباله روی آن ها خواهند شد.

LSM های فرعی نه تنها چون به زمینه ی کمتری نیاز دارند برای استک شدن آسان تر هستند، بلکه اکثر سیاست های خود رامستقیماً در کد کدگذاری می کنند. LSM های فرعی به جای داشتن فایل های خط مشی که از فضای کاربر به عنوان بخشی از شروع سیستم بارگیری می شوند،معموالً فقط حاوی پرچم هایی برای فعال کردن/غیرفعال کردن گزینه ها هستند.

SELINUX (SECURITY ENHANCED LINUX) – امنیت پیشرفته ی لینوکس

SELinux ابتدا به عنوان بخشی از لینوکس 2.6 ادغام شد و پیاده سازی MAC پیشفرض در توزیع های لینوکس مبتنی بر RedHat است. به دلیل قدرتمند بودن و پیچیده بودنش معروف می باشد.
SELinux مبتنی بر ویژگی (attribute-based) است که به این معنی است که شناسه های امنیتی فایل ها در ویژگی های فایل توسعه یافته در سیستم فایل ذخیره می شوند. به عنوان مثال، می توانید از دستور ls -Z برای مشاهده ی زمینه ی امنیتی در /bin/bash استفاده کنید، که در مثال زیر system_u:object_r:shell_exec_t:s0 می باشد. چهار فیلد با دونقطه از هم جدا شده اند و معرف user:role:type:level هستند.
$ls -Z /bin/bash
-rwxr-xr-x. root root system_u:object_r:shell_exec _t:s0 /bin/bash

رایج ترین راه برای تعیین خط مشی ها در SELinux این است که مشخص کنید موارد مرتبط با  SELinux (یا کاربران در این مورد) مجاز به انجام چه اقداماتی بر روی اشیاء برچسب شده با یک نوع خاص هستند. با نگاهی دوباره به خروجی ls بالا، مجوزهای کالسیک DAC نشان می دهند که همه ی کاربران در سیستم مجاز به خواندن و اجرای bash هستند، اما با SELinux، یک مدیر امنیتی می تواند در فایل های سیاست گذاری، مواردی که مجاز به اجرا یا خواندن فایل هایی از نوع shell_exec_t هستند را مشخص کند؛ معقول به نظر می رسد در صورتی که وب سرور در برابر حمله ی اجرای کد از راه دور آسیب پذیر باشد یک مهندس امنیتی نخواهد به وب سرور اجازه ی اجرای یک شِل (Shell) را بدهد.
یکی از عوارض جانبی استفاده ی SELinux از ویژگی های توسعه یافته (extended attributes) این است که SELinux برای محافظت از اشیاء در سیستم های فایل که از ویژگی های توسعه یافته پشتیبانی نمی کنند - مانند نصب NFS قبل از  NFSv4 - مفید نیست.

(SIMPLIFIED MANDATORY ACCESS CONTROL) - SMACK کنترل دسترسی اجباری ساده شده

کنترل دسترسی اجباری ساده شده (SMACK)، مانند SELinux، یک پیاده سازی MAC مبتنی بر ویژگی است و دومین توسعه دهندگان LSM بود که در هسته ی لینوکس ادغام شد (به عنوان بخشی از نسخه 2.6.24). برخالف SELinux ،SMACK برای سیستم های تعبیه شده (Embedded Systems) و ساده تر برای مدیریت طراحی شده است. SMACK پیاده سازی پیش فرض MAC در لینوکس Automotive Grade و Tizen است.

APPARMOR

AppArmor یکی دیگر از پیاده سازی های MAC است که در ابتدا توسط Immunix توسعه داده شد و به عنوان بخشی از نسخه 2.6.36 در هسته ادغام شد. AppArmor اولین پیاده سازی MAC در سیستم های مبتنی بر دبیان است.
بارزترین تفاوت AppArmor و SELinux، علاوه بر کاهش ابزار و پیچیدگی، این است که مبتنی بر ویژگی نبوده بلکه مبتنی بر مسیر (path-based) است.
پیاده سازیهای مبتنی بر مسیر دارای مزایا و معایبی هستند. از جنبه ی مثبت، سیاست های مبتنی بر مسیرها می توانند از فایل ها در هر سیستم فایلی محافظت کنند زیرا ویژگی های توسعه یافته (Extended Attributes) برای ذخیره ی اطلاعات زمینه ی امنیتی (Security Context Information) مورد نیاز نیست. همچنین می توان قوانینی را برای فایل هایی که ممکن است هنوز وجود نداشته باشند تعیین کرد زیرا مسیر را می توان در پروفایل بدون نیاز به برچسب گذاری یک فایل یا دایرکتوری واقعی ذخیره کرد. بیشترین موارد منفی ذکر شده این است که به دلیل توانایی ایجاد پیوندهای متقن (Hard Links)، ممکن است چندین مسیر وجود داشته باشد که به یک فایل فیزیکی اشاره دارد. سیاست امنیتی برای یک فایل نوعی بسته به مسیری که برای دسترسی به آن استفاده می شود، می تواند متفاوت باشد که این ممکن است حفره های امنیتی غیرمنتظره ای را باز کند.

TOMOYO

TOMOYO، مانند AppArmor، یکی دیگر از پیاده سازیهای MAC مبتنی بر مسیر است و ابتدا به عنوان بخشی از Linux 2.6.30 ادغام شد. TOMOYO برای محافظت از سیستم های تعبیه شده با اجازه دادن به مدیران امنیتی برای ضبط تمام تعاملات فرآیندِ (Process Interactions) حالت کاربر در طول آزمایش طراحی شده است، که سپس می تواند برای ایجاد سیاست هایی استفاده شود که تعاملات را فقط به مواردی که در طول توسعه / آزمایش مشاهده می شوند محدود نماید. هنگامی که سیستم های محافظت شده با TOMOYO در دست کاربران غیرقابل اعتماد یا در محیط های متخاصم قرار می گیرند، فرآیندهای حالت کاربر باید تنها به انجام اقدامات مشاهده شده قبلی محدود شود و ایجاد خط مشی را ساده تر کند.

LOADPIN

LoadPin که در لینوکس 4.7 ادغام شده است، یک LSM "فرعی" است که تضمین می کند همه ی فایل های بارگذاری شده با کرنل (ماژول ها، سیستم عامل و غیره) از یک سیستم فایل منشا می گیرند، با این انتظار که چنین سیستم فایلی توسط یک دستگاه فقط خواندنی پشتیبانی می شود. . این به منظور ساده سازی سیستم های تعبیه شده است که به هیچ یک از زیرساخت های علامت زدن ماژول کرنل (Kernel  Module Signing) - بررسی اینکه آیا سیستم برای بوت شدن از دستگاه های فقط خواندنی پیکربندی شده است یا خیر - نیاز ندارند.
"امنیتی" که به سادگی قابل استفاده شدن است احتمالا بیشتر مورد استقبال قرار می گیرد؛ LoadPin می تواند فرآیند محافظت از کرنل در برابر ماژول های مخرب را برای انواع خاصی از سیستم های تعبیه شده ساده کند.


YAMA

Yama که در لینوکس 3.4 ادغام شده است، یک LSM می باشد که برای جمع آوری محدودیت های امنیتی DAC در سطح سیستم طراحی شده است که توسط کرنل اصلی مدیریت نمی شوند. در حال حاضر از کاهش حوزه ی (Scope) سیستم کالِ ()ptrace پشتیبانی می کند تا حمله ی موفقیت آمیز به یکی از فرآیندهای در حال اجرا کاربر نتواند از ptrace برای استخراج اطلاعات حساس از سایر فرآیندهایی که از جانب همان کاربر اجرا می شوند استفاده کند.

SAFESETID

SafeSetID، ادغام شده در لینوکس 5.1، یک LSM است که برای محدود کردن انتقال UID/GID از یک UID/GID معین به مواردی که توسط یک لیست سفید در سراسر سیستم تایید شده اند استفاده می شود.

شرح مورد استفاده SafeSetID در منابع کرنل نیازی به بازنویسی ندارد:
"این می تواند برای اجازه دادن به یک برنامه ی غیر ریشه ای (Non-root Program) برای انتقال به دیگر uid های نامعتبر بدون قابلیت های کامل CAP_SETUID استفاده شود. برنامه ی غیر روت برای انجام هر نوع انتقال همچنان به CAP_SETUID نیاز دارد، اما محدودیت های اضافی اعمال شده توسط این LSM به این معنی است که این نسخه "ایمن تر" از CAP_SETUID است زیرا برنامه ی غیر روت نمی تواند از مزیت CAP_SETUID برای انجام کارهای تأیید نشده (به عنوان مثال setuid به uid 0 یا ایجاد/وارد فضای نام کاربری جدید) استفاده کند. هدف سطح بالاتر، اجازه دادن به سندباکس مبتنی بر uid از سرویس های سیستمی بدون نیاز به ارائه CAP_SETUID در همه جا است تا برنامه های غیر روت بتوانند به uidهای با امتیاز کمتر (Even-lesser-privileged) کاهش یابند. این امر به ویژه زمانی مرتبط است که یک دیمون غیر ریشه (Non-root Daemon) - یک فرآیند پس زمینه که به درخواست های خدماتی مانند اسپول کردن چاپ و انتقال فایل رسیدگی می کند و در صورت عدم نیاز غیرفعال است - در سیستم باید اجازه داشته باشد که فرآیندهای دیگر را به عنوان uid های مختلف ایجاد کند، اما اینکه که به دیمون یک CAP_SETUID معادل ریشه ای داده شود اساسا نامطلوب است."
Documentation/admin-guide/LSM/SafeSetID.rst

LOCKDOW LSM

Lockdown که در لینوکس 5.4 ادغام شده است یک LSM می باشد که یک ویژگی «قفل کردن» را برای کرنل پیاده سازی می کند. وقتی lockdown فعال است، می توان از یک پارامتر خط فرمان کرنل جهت قفل کردن کرنل برای یکپارچگی (Integrity) یا محرمانگی (Confidentiality) استفاده کرد. وقتی lockdown روی یکپارچگی تنظیم می شود، ویژگی هایی که به فضای کاربران اجازه می دهند کرنل را تغییر دهد غیرفعال می شوند. این ویژگی ها عبارتند از: بارگذاری ماژول بدون عالمت (Unsigned Module Loading)، دسترسی بهdev/{mem,kmem,port}/، دسترسی به kexec ،/dev/efi_test،  تصاویر بدون علامت، Hibernation، دسترسی مستقیم PCI، دسترسی به پورت IO خام، دسترسی MSR خام، تغییر جداول ACPI، ذخیره سازی مستقیم PCMCIA CIS، پیکربندی مجدد پورت سریالIO، پارامترهای ماژول ناامن، mmio ناامن و دسترسی اشکال زدایی (Debugfs). وقتی lockdown روی محرمانه بودن تنظیم می شود، علاوه بر اینکه تمام حفاظت های یکپارچگی فعال می شوند، ویژگی هایی که به کاربر اجازه می دهد تا اطلاعات محرمانه ی بالقوه از یک کرنل در حال اجرا - مانند دسترسی /proc/kcore، استفاده از kprobes، استفاده از bpf برای خواندن RAM کرنل، استفاده ی ناایمن از perf و استفاده از  tracefs - را استخراج کند غیرفعال می گردد.

ویژگی قفل کردن را می توان از طریق فایل های خط مشی SELinux، AppArmor، SMACK یا TOMOYO پیاده سازی کرد، اما پیاده سازی آن در یک LSM پشته پذیر جداگانه با یک خطم شی استاتیک به این معنی است که می توان آن را بدون توجه به پیاده سازی MAC خاص در حال استفاده، در سراسر توزیع ها استفاده نمود.

نتیجه گیری

LSM ها برای جلوگیری از حمله به یک فرآیند طراحی نشده اند. شیوه های کدنویسی خوب، مدیریت پیکربندی و زبان های ایمن حافظه، ابزارهایی برای آن هستند. با این حال، حفاظت های ارائه شده توسط LSM ها زمانی که یک مهاجم از نقص های یکی از برنامه های در حال اجرا سوء استفاده می کند، به محافظت از سیستم شما در برابر هک شدن کمک خواهد کرد. آن ها می توانند یک لایه ی مهم در هر استراتژی دفاعی عمیق در سیستم های لینوکس باشند، و درک اینکه چه محافظت هایی ارائه می کنند، به درک بیشتر ما از سیستم هایی که نیاز به محافظت دارند و نحوه اجرای آن محافظت ها منجر خواهد شد.
« آخرین ویرایش: 20 تیر 1402، 11:38 ق‌ظ توسط دانیال بهزادی »

آفلاین prbzr

  • Newbie
  • *
  • ارسال: 10

‫‪Kernel‬‬ ‫‪Runtime‬‬ ‫‪Security‬‬ ‫‪Instrumentation‬‬
مدیران سیستم های محاسباتی با کارایی بالا (HPC) با وظیفه ی دلهره آور ایمن سازی داده های تحقیقاتی بدون به خطر انداختن حداکثر عملکرد و کارایی سیستم روبرو هستند.

آنها تحقیقات پیشرفته ای را تسهیل می کنند که به صورت قراردادی با ضرب الاجل های فشرده و الزامات امنیتی سختگیرانه داده همراه است. ارضای محدودیت های زمانی و امنیتی یک چالش مداوم است که اغلب نیازمند رویکردهای جدید برای مسائل قدیمی است. این نوشته به تشریح آزمایش ابزار امنیتی زمان اجرای کرنل (KRSI) و یک افزونه ی جدید کنترل دسترسی اجباری (MAC) در لینوکس می پردازد که به مدیران سیستم اجازه می دهد تا سیاست های MAC بسیار خاص و هدفمندی که به طور بالقوه از تأثیر عملکرد برنامه های افزودنی بزرگ MAC جلوگیری می نماید را برنامه ریزی کنند.

KRSI یک ماژول امنیتی لینوکس (LSM) است که به رویدادهای امنیتی کرنل مشابه SELinux و AppArmor متصل می شود، اما به جای ارائه ی یک افزونه ی بزرگ MAC، به مدیر اجازه می دهد تا برنامه های کوچک و ماژولاری را کامپایل و پیوست کند که کنترل می کنند آیا یک عمل مجاز است یا خیر. یک مدیر می تواند کد سفارشی خود را که دسترسی به فایل، فعالیت شبکه، اجرای فرآیند و بسیاری موارد دیگر را کنترل می کند، پیوست نماید.

این فناوری به طور بالقوه می تواند به عنوان یک LSM انتخابی در محاسبات با عملکرد بالا مورد استفاده قرار گیرد. این واقعیت که LSMها در سایت های HPC غیرفعال می شوند به اندازه ای رایج است که NIST عدم استفاده از آن ها برای امنیت HPC را در پیش نویس برنامه ی اقدامی ۶۱۰۲ خود گنجانده است. بسیاری از مدیران سیستم SELinux را به دلیل تأثیر منفی عملکرد آن بر روی معیارهای ساختگی (Synthetic Benchmarks) و برنامه های کاربردی واقعی غیرفعال می کنند.

تحقیق در مورد KRSI، ادامه ی تحقیقات پیشین در مورد کاوشگرهای BPF است. کاوشگرهای BPF، حمالات جزئی علیه سرورها را با تأثیر کمی بر کارایی سیستم شناسایی می کنند؛ با این حال، توانایی کاوشگرها برای کاهش حملات محدود است. در عوض، KRSI می تواند هم تشخیص و هم تقلیل حملات را توامان به ارمغان آورد.


بررسی فناوری

نوشتن برنامه های KRSI یک موضوع پیشرفته است. برای نزدیک شدن بیشتر به موضوع، مروری کوتاهی از فناوریهایی که KRSI بر اساس آنها ساخته شده است، ارائه می گردد.


BPF:

KRSI در نهایت با استفاده از (Berkeley Packet Filter) BPF انجام می شود. BPF اگرچه به طور سنتی به عنوان یک ابزار فیلتر شبکه شناخته می شود، اما اکنون یک زیرسیستم ردیابی (Tracing) برای لینوکس است.
مدیران سیستم با استفاده از BPF می توانند برنامه های ردیابی کوچکی را نوشته و آن ها را به مکان های مورد عالقه در سیستم عامل متصل کنند. برنامه ها را می توان به نقاط ردیابی (Tracepoint) تعریف شده یا توابع دلخواه، هم در کرنل و هم در فضای کاربر متصل کرد. هنگامی که یک تابع وارد یا خارج می شود، برنامه ی BPF می تواند داده های ارسال شده به تابع و داده های بازگشتی از تابع را مشاهده کند. اگرچه BPF عمدتاً برای تجزیه و تحلیل کارایی استفاده می شود، اما به عنوان یک ابزار ارزشمند برای نظارت بر امنیت نیز عمل می کند.
یک ردیاب به نام "bpftrace" ابزار انتخابی در تحقیقات قبلی ویلسون بود که نوشتن و پیوست برنامه های BPF را با ارائه یک سینتکس AWK مانند ساده کرد:
‫‪#!/usr/bin/bpftrace‬‬
probe1 /filter/ { action }
probe2, probe3 /filter/ { action }

سه جزء اصلی سینتکس bpftrace عبارتند از probe، filter و probe .action نقطه ی ردیابی یا تابعی که برنامه ی BPF در آن متصل می شود را مشخص، filter شرایط پردازش رویدادها را تعیین، و action، اقدامی که باید در هنگام شروع رویداد انجام شود را مشخص می کند.
متخصصان امنیت می توانند از bpftrace برای دستیابی به عمق قابل توجهی از دید (Visibility) در سیستم های لینوکس استفاده کنند. ویلسون اسکریپت های bpftrace را برای شناسایی ترافیک نرم افزار ارزهای دیجیتال، تلاش های افزایش امتیاز (Privilege Escalation Attempts)، تلاش های محوری شبکه (Network Pivot Attempts) و ایجاد پروکسی SSH ارائه کرد.
با وجود قابلیت های نظارتی گسترده، bpftrace در توانایی محدودی برای کاهش حملات دارد. این ابزار در بهترین حالت می تواند با ارسال سیگنال به یک فرآیند (Process) یا با ایجاد ناایمن یک Shell برای انجام یک عمل به یک رویداد پاسخ دهد.
ویلسون به این نتیجه رسید که تحقیقات آینده باید بر روی KRSI متمرکز شود، یک افزونه MAC در حال توسعه که موقعیت بهتری برای کاهش حملات با BPF دارد.

Linux Security Modules:

یکی دیگر از پیش نیازهای ضروری برای نوشتن برنامه های KRSI، درک نحوه عملکرد ماژول های امنیتی لینوکس است. افزونه های MAC در لینوکس به صورت LSM پیاده سازی می شوند و این شامل KRSI می شود.

فریم ورک LSM امکان گسترش مدل امنیتی لینوکس را در کرنل اصلی ایجاد کرد. پیش از آن، لینوکس محدود به کنترل دسترسی اختیاری یا (Discretionary Access Control - DAC) بود. پروژه هایی مانند Medusa ،RSBAC ،DTE و SELinux NSA که MAC را به لینوکس اضافه کردند، باید کرنل های سفارشی شده خود را حفظ می کردند.


معماری قبل از LSM که به کرنل های سفارشی برای MAC نیاز داشت.

حافظانِ (Maintainer) کرنل لینوکس در نهایت فریم ورک LSM را ایجاد کردند تا مسیری برای این پروژه های امنیتی سفارشی فراهم نمایند تا در کرنل اصلی لینوکس ادغام شوند. چند LSM در نهایت با هم ادغام شدند، اما تنها یکی از آنها می توانست در یک زمان فعال گردد.
دو بازیکن بزرگ در میان برنامه های افزودنی MAC لینوکس، SELinux برای توزیع های مبتنی بر Red Hat و AppArmor برای توزیع های مبتنی بر دبیان بودند. SELinux برای اعمال نوع (Type Enforcement) شناخته شده بود، که نحوه ی تعامل انواع خاصی از موارد با انواع خاصی از اشیاء را اعمال می کرد. AppArmor یک رویکرد جایگزین برای پایه گذاری سیاست های خود بر روی مسیرهای فایل های سیستمی را در پیش گرفت.



معماری ماژول های امنیتی لینوکس

چندین افزونه ی کمتر شناخته شده ی MAC نیز وجود داشت، از جمله Smack ، TOMOYO و Yama. از آنجایی که تنها یک LSM را می توانستند در یک زمان فعال کنند، این LSMهای کوچکتر اغلب از بین می رفتند. با این حال، از سال 2015، چندین LSM را می توان به طور همزمان بارگیری کرد.

هوک های LSM هنوز رابط رایجی برای استفاده توسط افزونه های MAC هستند. آن ها در فایل کد منبع کرنل لینوکس "include/linux/lsm_hook_defs.h/" فهرست شده اند. در زیر چند نمونه ورودی گزیده شده آورده شده است:
LSM_HOOK(int, 0, inode_permission, struct inode *inode, int mask)
LSM_HOOK(int, 0, bprm_check_security, struct linux_binprm *bprm)
LSM_HOOK(int, 0, socket_listen, struct socket *sock, int backlog)

()LSM_HOOK نوع بازگشت تابع، مقدار بازگشتی پیشفرض، نام هوک امنیتی و فهرست آرگومان های ارسال شده به هوک را مشخص می کند.

اولین خط گزیده ی بالا برای هوکی به نام inode_permission است. آرگومان های آن یک ساختار inode (که حاوی ابرداده برای یک فایل است) و یک عدد صحیح می باشد که ماسک مجوز (Permission Mask) را نشان می دهد. هنگامی که یک هوکِ LSM راه اندازی می شود، کنترل به یک یا چند LSM منتقل می گردد که آرگومان های ارسال شده به هوک را بررسی و سپس دسترسی را مجاز یا رد می کند.

فایل منبع لینوکس دیگر، اطلاعات مربوط به این هوک ها را تکمیل می‌کند. در زیر گزیده ای از         ‫‫‪/include/linux/lsm_hooks.h"‬‬‫"‬ برای هوک inode_permission نشان داده می شود که برای خوانایی کمی تغییر یافته است:
    • @inode_permission:
    • Check permission before accessing an inode. ...
    • @inode contains the inode structure to check.
    • @mask contains the permission mask.
    • Return 0 if permission is granted.

این ورودی نشان می دهد که هوکِ inode_permission می تواند قبل از اجازه ی دسترسی به یک inode، برای بررسی های مجوز اضافی استفاده شود. متخصصان امنیت می توانند به این دو فایل منبع رجوع کرده تا هدف و کاربرد هر هوکِ LSM در کرنل لینوکس را درک کنند.

Kernel Runtime Security Instrumentation

در سپتامبر 2019، KP Singh مجموعه ای از پچ های کرنلی را به لیست پستی کرنل لینوکس (Linux Kernel Mailing List) برای یک LSM جدید به نام "ابزار امنیتی زمان اجرای کرنل" (Kernel Runtime Security Instrumentation - KRSI) پیشنهاد کرد که به یک مدیر اجازه می دهد تا برنامه های BPF را به هوک های LSM مختلف متصل نماید و همچنین می تواند خطایی را برای مسدود کردن عملیات مورد نظر تزریق کند. از این رو برای مدیران این امکان به وجود آمد تا با کد دلخواه، سیاست های MAC خود را تعریف کنند.

استفاده ازهوکِ LSM با KRSI

این پچ ها چندین ویرایش را پشت سر گذاشتند و در مارس 2020 در کرنل اصلی لینوکس ادغام شدند. دو ماه بعد، نسخه 5.7 کرنل لینوکس اولین موردی بود که شامل KRSI می شد و در 31 مه 2020 منتشر شد.
برنامه های KRSI در محصولات Google برای کاهش حمالت مختلف، از جمله حمالت LD_PRELOAD استفاده می شوند.
متأسفانه، نمونه های کاربردی استفاده از KRSI در کرنل اصلی تقریباً وجود ندارد. نمونه های موجود در مجموعه ی پچ های KRSI به توابع کمکی خاصی بستگی داشت که هرگز در خط اصلی ادغام نشدند. یک ابزار منبع باز به نام Hawk استفاده از KRSI را نشان داد، اما فقط اجرای فرآیند را زیر نظر داشت.

ابزارهایی برای نوشتن برنامه‌های KRSI

سه مجموعه ابزار اصلی برای نوشتن برنامه های BPF وجود دارد: bpftrace، مجموعه کامپایلر (BPF Compiler Collection - BPF BCC)، و ابزارهای مرتبط با BPF که در کد منبع کرنل لینوکس ارائه شده اند.
قابل دسترس ترین ابزار از میان این سه ابزار، bpftrace است، اما هنوز از KRSI پشتیبانی نمی کند. یک درخواست pull برای این قابلیت وجود دارد، اما به دلیل وابستگی آن به یک تابع کمکی کرنل گمشده، هنوز ادغام نشده است. هنگامی که درخواست pull ادغام شود، نوشتن برنامه های KRSI بسیار ساده تر می گردد. مثال زیر یک مورد پایه ای استفاده از bpftrace برای بارگذاری یک برنامه ی KRSI است که از بارگیری سایر برنامه های BPF جلوگیری می کند:

bpftrace -e 'lsm:bpf { return -1234; }'

این برنامه به "bpf" هوک LSM متصل می شود، که بررسی اولیه برای همه ی سیستم های    ()bpf را انجام می دهد. مقدار بازگشتی با یک عدد صحیح غیر صفر، فرآیند فرخوانی ()bpf را لغو می کند و باعث می شود تمام تلاش های آتی برای فراخوانی ()bpf تا زمانی که برنامه آنلود نشود، شکست بخورد. به عبارت دیگر، این برنامه ی BPF، بارگذاری دیگر برنامه های BPF را مسدود می کند.
دومین ابزار انتخابی BCC است. خوشبختانه، از زمان انتشار نسخه 0.15.0 آن در ژوئن 2020، از KRSI پشتیبانی می کند. این نوشته بر BCC تکیه خواهد کرد.
نوشتن اسکریپت های BCC به طور قابل توجهی بیشتر از نوشتن اسکریپت های bpftrace است. دو نیمه برای هر اسکریپت BCC وجود دارد. نیمه ی اول، برنامه ی BPF است که در kernel-space بارگذاری می شود. این بخش به زبان C نوشته شده است. نیمه ی دوم اسکریپت user-space است که برنامه BPF را بارگیری، داده های نمونه برداری (Poll Data) را از آن دریافت و گزینه های مختلف خط فرمان را تسهیل می کند. این بخش به زبان پایتون نوشته شده است. اسکریپت پایتون می تواند برنامه C را درون خود جاسازی کند یا به عنوان یک فایل جداگانه به آن ارجاع دهد.
در ادامه یک اسکریپت BCC برای استفاده ازKRSI نوشته و آورده خواهد شد.


آفلاین prbzr

  • Newbie
  • *
  • ارسال: 10
استفاده از KRSI در BCC



الف. برنامه ی C برای Kernel-Space


برنامه ی BPF ابتدا در C پوشش داده می شود. نقش آن بررسی عملیاتی است که باعث اجرای هوک LSM شده است، داده های رویداد را به فضای کاربر ارسال می کند و تعیین می کند که آیا عملیاتی که موجب اجرای هوک LSM شده است مجاز است یا باید رد شود. ابتدا، برای استفاده از ساختارها و متغیرهای مناسب، فایل های هدر مورد نیاز باید گنجانده شوند.

#include "linux/fs.h"
#include "linux/errno.h"

سپس یک ساختار داده تعریف می شود که برخی از زمینه ها از هر رویداد را ذخیره می کند. در این حالت، UID، PID، مهر زمانی، شماره inode و نام فرمان مرتبط با رویداد را ذخیره می نماید. سپس ماکرو ()BPF_PERF_OUTPUT فراخوانی می شود که اجزای لازم از جمله ساختاری به نام رویدادها را تنظیم می کند تا برنامه داده‌ها را به فضای کاربر ارسال نماید.

struct data_t {
u32 uid;
u32 pid;
u64 ts;
unsigned long inode_num;
char comm[TASK_COMM_LEN];
};
BPF_PERF_OUTPUT(events);

سپس ماکرو LSM_PROBE است که مشخص می کند این برنامه به کدام هوک LSM متصل شود. نام هوک و آرگومان های صحیح با نگاه کردن به ورودی هوک LSM مربوطه در "include/linux/lsm_hook_defs.h/" تعیین می شود که قبلاً در مورد آن بحث شد. این برنامه به هوک LSM inode_permission متصل می گردد و می تواند ساختار inode و ماسک مجوز را بررسی کند.

LSM_PROBE(inode_permission, struct inode *inode, int mask) {

در ادامه کدی است که هنگام اجرای این هوک LSM اجرا می شود. ساختاری را برای داده‌های رویداد مقداردهی اولیه می‌کند و سپس تابع کمکی BPF به نام (bpf_get_current_uid_gid) را فراخوانی می‌کند و فقط بیت‌های کمتری که حاوی UID هستند را ذخیره می‌نماید. این یکی از توابع کمکی BPF است که کرنل برای برنامه های BPF مانند این مورد در دسترس قرار می دهد.
سپس یک رشته ی عجیب، UID_FILTER ظاهر می شود که به زبان C نیست. این یک مکان نگهدار (placeholder) است که توسط پایتون، بسته به گزینه هایی که به اسکریپت پایتون ارسال می شود برای جایگزینی منطق در برنامه، استفاده می گردد.

struct data_t data = {};
u32 uid = bpf_get_current_uid_gid();
UID_
FILTER

سپس برنامه با پرس و جو و ذخیره اطلاعات در مورد UID فعلی، PID، مهر زمانی، شماره inode و دستور، برای ارسال داده ها به فضای کاربر آماده می شود. به ساختار inode و هر آرگومان دیگری که بخشی از هوک LSM است دسترسی دارد.
data.uid = uid;
data.pid = bpf_get_current_pid_tgid();
data.ts = bpf_ktime_get_ns();
data.inode_num = inode->i_ino;
bpf_get_current_comm(&data.comm, sizeof(data.comm));

داده ها در یک بافر "رویدادها" قرار می گیرند که فضای کاربر می تواند به آن دسترسی داشته باشد. این روش "perf_submit" نامیده می شود زیرا به بافر خاصی به نام "perf ring buffer" وابسته است.

داده ها در یک بافر "رویدادها" قرار می گیرند که فضای کاربر می تواند به آن دسترسی داشته باشد. این روش "perf_submit" نامیده می شود زیرا به بافر خاصی به نام "perf ring buffer" بستگی دارد که معمولاً برای جمع آوری معیارهای عملکرد لینوکس از کرنل استفاده می شود. در نهایت، برنامه یک خطای مجوز را برمی‌گرداند و در نتیجه از دسترسی فرآیند به inode محروم می‌شود.

events.perf_submit(ctx, &data, sizeof(data));
return -EPERM;

اسکریپت پایتون برای User-Space


بخش پایتون برنامه، همان چیزی است که کاربر اجرا می کند. فقط بخش‌های کلیدی اسکریپت پایتون در اینجا توضیح داده شده است.

برنامه C که قبلاً توضیح داده شد در اسکریپت پایتون تعبیه شده است و به عنوان یک رشته چند خطی به متغیر "bpf_text" اختصاص داده می شود.

pf_text = """
#include "linux/fs.h"
#include "linux/errno.h"

اسکریپت پایتون مستقیماً برنامه C را قبل از کامپایل و بارگذاری تغییر می دهد. در قطعه ی زیر، پایتون رشته ی “UID_FILTER” را در برنامه ی C با یک دستور انشعاب جایگزین می‌کند که امکان دسترسی inode را برای همه ی UIDها - به جز موردی که به‌عنوان آرگومان به اسکریپت پایتون ارسال می‌شود - می‌دهد.

bpf_text = bpf_text.replace('UID_FILTER',
'if (uid != %s) { return 0; }' % args.uid)

سپس برنامه ی C با فراخوانی تابع ()BPF که بخشی از کتابخانه ی “bcc” پایتون است، کامپایل و در کرنل بارگذاری می شود.

b = BPF(text=bpf_text)

سپس اسکریپت پایتون می تواند با باز کردن بافر "رویدادها" که در برنامه ی C تعریف شده است به داده های رویداد دسترسی پیدا کند.

b["events"].open_perf_buffer(print_event)
while 1:
try:
b.perf_buffer_poll()   
except KeyboardInterrupt:
exit()

اسکریپت کامل:

#!/usr/bin/python
from bcc import BPF
from bcc.utils import printb
import argparse
import pwd

def parse_uid(user):
    try:
        result = int(user)
    except ValueError:
        try:
            user_info = pwd.getpwnam(user)
        except KeyError:
            raise argparse.ArgumentTypeError("{0!r} is not valid UID or user entry".format(user))
        else:
            return user_info.pw_uid
    else:
# Maybe validate if UID < 0 ?
        return result

examples = """examples:
./deny_inode_permission -u 1000 # deny all inode permissions for
UID 1000
"""

parser = argparse.ArgumentParser(description="Deny the specified user of any inode interactions", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=examples)
parser.add_argument("-u", "--uid", type=parse_uid, metavar='USER', required=True, help="trace this UID")
args = parser.parse_args()
bpf_text = """
#include <linux/fs.h>
#include <linux/errno.h>
struct data_t {
u32 uid;
u32 pid;
u64 ts;
unsigned long inode_num;
char comm[TASK_COMM_LEN];
};
BPF_PERF_OUTPUT(events);
LSM_PROBE(inode_permission, struct inode *inode, int mask) {
struct data_t data = {};

u32 uid = bpf_get_current_uid_gid();
UID_FILTER
data.uid = uid;
data.pid = bpf_get_current_pid_tgid();
data.ts = bpf_ktime_get_ns();
data.inode_num = inode->i_ino;
bpf_get_current_comm(&data.comm, sizeof(data.comm));
events.perf_submit(ctx, &data, sizeof(data));
return -EPERM;
}
"""
bpf_text = bpf_text.replace('UID_FILTER', 'if (uid != %s) { return 0; }' % args.uid)

b = BPF(text=bpf_text)
#b = BPF(src_file="mac_deny_inode_access.c")
# header
print("%-18s %-16s %-6s %-6s %-12s %-6s" % ("TIME(s)", "COMM", "UID",
"PID", "INODE", "MESSAGE"))
# process event
start = 0
def print_event(cpu, data, size):
    global start
    event = b["events"].event(data)
    if start == 0:
        start = event.ts
    time_s = (float(event.ts - start)) / 1000000000
    printb(b"%-18.9f %-16s %-6d %-6d %-12d %s" % (time_s, event.comm,
event.uid, event.pid,
        event.inode_num, b"Deny during inode_permission hook"))
# loop with callback to print_event
b["events"].open_perf_buffer(print_event)
while 1:
    try:
        b.perf_buffer_poll()
    except KeyboardInterrupt:
        exit()


اجرای اسکریپت نوشته شده


حال که اسکریپت برنامه ی ما آماده شده است، برای اجرای ابتدا آن را در یک فایل با نام KRSI.py ذخیره می نماییم. برای اجرای برنامه فقط کافیست که یک ترمینال در مسیر فایل ذخیره شده باز کرده و دستور با فرمت زیر را وارد نماییم:

sudo python3 KRSI.py -u UID  ↲

توجه داشته باشید UID، شناسه ی کاربری است که باید در اجرای دستور نوشته شده لحاظ گردد.

UID چیست؟

UID مخفف User Identifier است. UID شماره ای است که به هر کاربر لینوکس اختصاص داده می‌شود و معرف کاربر در کرنل لینوکس است. UID برای شناسایی کاربر در سیستم و برای تعیین اینکه کاربر می تواند به منابع سیستم دسترسی داشته باشد  یا خیر استفاده می شود. به همین دلیل است که شناسه ی کاربری باید منحصر به فرد باشد. می توانید UID ذخیره شده را در فایل etc/passwd/ را پیدا کنید. این همان فایلی است که از آن برای فهرست کردن همه ی کاربران در یک سیستم لینوکس استفاده می شود.

برای نمونه در تصویر زیر فیلد سوم نشان دهنده شناسه ی کاربری یا UID است:



از این رو برای یافتن UID به مسیری که گفته شده مراجعه کرده و متن فایل passwd را بررسی می نماییم:




همانطور که از تصویر بالا بر می‌آید، UDI مورد نظر ما ۱۰۰۰ می باشد. توجه داشته باشید که در اکثر توزیع های لینوکس، شناسه ی کاربری 1 تا500 معمولاً برای کاربران سیستم رزرو شده است و در اوبونتو و فدورا، UID برای کاربران جدید از 1000 شروع می شود.

اکنون که UDI را می‌دانیم دستور را اجرا می کنیم:


sudo python3 KRSI.py -u 1000  ↲

و نتیجه:




همانطور که در تصویر بالا مشاهده می‌شود، با اجرای برنامه ۴ هشدار و ۳ خطا گزارش شده است. ایراد کار کجاست؟



آفلاین prbzr

  • Newbie
  • *
  • ارسال: 10
رفع خطای اسکریپت نوشته شده

برای رفع خطای گزارش شده به مدت دو هفته جستجوهای زیادی در ریپازیتوری ها، فروم ها و وبسایت های مربوطه انجام و دلایل احتمالی که موجب بروز خطا شده‌اند بررسی گشت اما نتیجه‌ای حاصل نشد؛ در نهایت متوجه شدیم که دلیل چیز دیگریست!

مطمئن شوید که BPF LSM فعال است!


همیشه قبل از تلاش برای نوشتن یک برنامه ی BPF، باید از دو مورد مطمئن شوید:
   
 1) نسخه کرنل شما حداقل 5.7 است.
 2) BPF LSM فعال است.

برای بررسی مورد اول کافی است که از دستور زیر استفاده نماییم:

uname -r ↲

همانطور که از تصویر زیر بر می‌آید، نسخه ی کرنل ما 5.15.0 می باشد:



 و اما مورد دوم را می‌توان از طریق فایل lsm در مسیر sys/kernel/security/lsm/ بررسی
نمود. برای این منظور یک ترمینال باز کرده و دستور زیر را وارد می کنیم:

cat /sys/kernel/security/lsm ↲

خروجی چاپ شده بایستی حتماً شامل bpf باشد. این مورد را بررسی می کنیم:


مشاهده می‌شود که bpf در میان LSMها نیست. همانطور که پیشتر در شکل زیر توضیح داده شد، "ابزار امنیتی زمان اجرای کرنل" یا همان KRSI، یک LSM است که به یک مدیر اجازه می دهد تا برنامه های BPF را به هوک های LSM مختلف متصل نماید:


از تصویر بالا نیز چینن بر می‌آید که برای اجرای KRSI، نیاز است تا حتماً BPF به عنوان یک LSM فعال شده باشد.
اکنون که BPF در میان LSMها نیست، لازم است تا به صورت دستی با افزودن آن به پارامترهای پیکربندی کرنل فعال گردد. می توان این کار را با ویرایش پیکربندی GRUB در مسیر etc/default/grub/ و افزودن موارد زیر به پارامترهای کرنل انجام داد:

GRUB_CMDLINE_LINUX="lsm=[YOUR CURRENTLY ENABLED LSMs],bpf"


برای ویرایش پیکربندی GRUB از vim و مطابق دستور استفاده می کنیم:

sudo vi /etc/default/grub↲

پس از وارد کردن دستور و گذرواژه ی خود، صفحه ی ویرایش نمایش داده می شود. چنانچه اقدام به ویرایش در فایل نمایید، متوجه خواهید شد که به دلیل ReadOnly بودن فایل امکان ویرایش وجود ندارد. برای ویرایش یک فایل فقط خواندنی از طریق vim ابتدا !w: را تایپ کرده تا پس از فشردن کلید اینتر امکان ویرایش فایل فراهم شود. مطابق تصویر زیر LSMهای مورد نظر خود را در GRUB پیکیرندی می کنیم:



برای ذخیره و خروج !wq: را وارد می نماییم.

حالا پیکربندی GRUB را با  یکی از دستورات ذکر شده در زیر بازسازی می کنیم، هر یک از این دستورها ممکن است در یک توزیع  لینوکس در دسترس باشد و در توزیعی دیگر نباشد:

update-grub2 ↲
grub2-mkconfig -o /boot/grub2/grub.cfg ↲
grub-mkconfig -o /boot/grub/grub.cfg ↲

ما از دستور آخر استفاده کردیم:



و در آخر سیستم را راه اندازی مجدد (Reboot) می‌کنیم.

GRUB چیست؟

Grand Unified Bootloader (GRUB) یک برنامه ی کامل برای بارگذاری و مدیریت فرآیند بوت است که رایج ترین بوت لودر برای توزیع های لینوکس در پروژه ی GNU می باشد. بوت لودر اولین نرم افزاری است که هنگام راه اندازی کامپیوتر اجرا می شود. این برنامه کرنل سیستم عامل را بارگذاری می کند و سپس کرنل بقیه ی سیستم عامل را راه اندازی می نماید.

برای نمونه تصویر زیر نشان می‌دهد که هنگام بوت شدن سیستم، چه سیستم عامل هایی بر روی ماشین نصب و پیکربندی شده‌اند تا کاربر یکی از آن‌ها را انتخاب کند:



همانطور که مشاهده می شود، سه سیستم عامل بر روی ماشین ما وجود دارد: اوبنتو، فدورا و ویندوز.

با توجه به اینکه LSMها را در پیکربندی مربوط به GRUB تصریح و فعال کردیم، مشخص می‌شود که این LSM توسط بوت لودر و همزمان با بوت شدن ماشین فعال خواهند شد.


اجرای مجدد کدها:


حالا که از نسخه ی کرنل و فعال بودن BPF مطمئن شده ایم، کد نوشته شده را با استفاده از دستور زیر مجدداً اجرا می کنیم:

sudo python3 KRSI.py -u 1000  ↲

و این بار:



توجه مهم:

از آنجایی که بر روی ماشین ما سه سیستم عامل وجود دارد و با توجه به اینکه توزیع فدورا پیش از توزیع اوبنتو نصب و پیکربندی شده بود، فدورا به دلیل  تغییر و تنظیم اولیه ی پیکربندی GRUB، مانع از فعال کردن bpf از طریق روش بالا در توزیع اوبنتو می‌شد و اجازه ی دخل و تصرف در پیکربندی GRUB از طریق اوبنتو امکان‌پذیر نبود. همانطور که از نام GRUB نیز بر می آید، این بوت لودر برای تمام سیستم عامل ها (حتی ویندوز)‌ به صورت یکپارچه و مشترک عمل می کند. از این رو اکیدا پیشنهاد می‌شود این مهم در نظر گرفته شده و تا آنجا که میسر است از نصب بیش از یک توزیع لینوکس اجتناب گردد. با این توضیح و به دلیل عدم فعال شدن bpf در توزیع اوبنتو، از این پس آزمایش ما در توزیع فدورا ادامه خواهد یافت.



آفلاین prbzr

  • Newbie
  • *
  • ارسال: 10
آنالیز KRSI در HPC

ادامه ی این آزمایش به اندازه گیری توانایی برنامه های KRSI برای کاهش حملات در یک محیط HPC اختصاص دارد. یک محیط آزمایشی برای مقایسه کارایی سیستم در دو حالت یعنی در صورت فعال بودن KRSI و همچنین در صورت غیرفعال بودن آن در نظر گرفته خواهد شد.

اسکریپت های KRSI

پنج اسکریپت BCC نوشته شده است که از KRSI برای کنترل دسترسی اجباری استفاده می نمایند. کد منبع آنها در یک مخزن عمومی GitHub در دسترس است (Wilson، اکتبر 2020). اسکریپت ها برای پاسخگویی به سوالات زیر نوشته شده اند:

    • آیا KRSI می تواند کاربرانی را که فایل هایی با مجوزهای ناامن ایجاد می کنند مسدود کرده و گزارش دهد؟
    • آیا می تواند کاربرانی را که فایل های اجرایی غیرمجاز را اجرا می کنند مسدود کرده و گزارش دهد؟
    • آیا می تواند کاربرانی را که پروکسی های SSH ایجاد می کنند مسدود کرده و گزارش دهد؟
    • آیا می‌تواند کاربرانی را که به بخش‌های شبکه غیرمجاز می‌روند مسدود کرده و گزارش دهد؟
    • آیا می تواند تلاش های غیرمجاز برای خاتمه دادن به فرآیندها را مسدود و گزارش کند؟

همه اسکریپت‌ها داده‌های رویداد را در رابطه با فرآیندهایی که باعث اجرای آن‌ها می شود، ثبت می کنند. این داده ها شامل مهر زمانی، نام فرمان، UID، GID، و PID فرآیندها و همچنین هرگونه اقدام انجام شده (مجاز یا رد) می باشد. هر اسکریپت همچنین داده های اضافی را ثبت می نماید که منحصر به نوع رویدادیت که مدیریت می‌کند. اسکریپت‌ها گزارش‌ها را در قالب مقدار-کلید می نوسیند، اما برای بخش‌های بعدی، گزارش‌ها به فرمت سربرگ-ستون با مهر زمانی کوتاه شده برای خوانایی تنظیم خواهند شد.


mac_fileperms


اسکریپت mac_fileperms برای محدود کردن کاربران از تنظیم بیت‌های مجوز SUID (تنظیم UID) و WOTH (قابل نوشتن توسط دیگران) نوشته شده است.
بیت‌های SUID و WOTH اجزای قانونی مدل مجوزهای لینوکس هستند که توسط مهاجمان مورد سوء استفاده قرار می‌گیرند. تنظیم بیت SUID روی یک فایل به کاربر این امکان را می دهد که آن را با امتیازات مالک فایل اجرا کند. این قابلیت برای فایل‌های اجرایی مانند
“passwd” و “sudo” ضروری است، اما برنامه‌های مخرب نیز بیت SUID را روی فایل‌ها تنظیم می کنند تا از این روش به عنوان یک Trabdoor دائمی استفاده نمایند. بیت WOTH به هر کسی اجازه می دهد تا در یک فایل نوعی بنویسد که اغلب توسط کاربران برای اطمینان از کارکرد یک برنامه یا به منظور به اشتراک گذاری عمدی داده ها با همتایان، به اشتباه تنظیم می شود. مهاجمان می توانند فایل هایی که توسط هر کسی قابل نوشتن است را پیدا و از آنها سوء استفاده کنند.
اسکریپت mac_fileperms برنامه ها را به هوک های ال اس امِ "inode_create" و "path_chmod" متصل می نماید. این هوک ها به ویژه زمانی که یک فایل جدید ایجاد می شود یا مجوزهای فایل موجود تغییر کند، فعال می گردد.
برنامه های پیوست شده از تنظیم بیت های مجوز SUID و WOTH بر روی فایل های جدید یا اضافه شدن به فایل های موجود جلوگیری می کنند. این کار با بررسی حالت مجوزهای درخواستی یک فایل و unmask فرآیند انجام می شود. اگر درخواست بیت SUID یا WOTH  شناسایی شود، می تواند از ایجاد فایل یا به روز رسانی مجوزهای فایل جلوگیری نماید.

#!/usr/bin/python
#
# mac_fileperms     Restrict the addition of SUID and WOTH
#                   bits to new and existing files.
#
# 10-Oct-2020   Billy Wilson    Created this.

from bcc import BPF
from bcc.utils import printb
import argparse
import datetime
import pwd


def parse_uid(user):
    """
    Check if valid UID or username is provided
    """
    try:
        result = int(user)
    except ValueError:
        try:
            user_info = pwd.getpwnam(user)
        except KeyError:
            raise argparse.ArgumentTypeError(
                "{0!r} is not valid UID or user entry".format(user))
        else:
            return user_info.pw_uid
    else:
        # Maybe validate if UID < 0 ?
        return result


# Set up argument parser
examples = """examples:
    ./mac_fileperms -A                 # Allow all SUID/WOTH permission bit additions
    ./mac_fileperms -A -u 1000         # Allow only UID 1000 to add SUID/WOTH permission bits
    ./mac_fileperms -A -U 1000         # Allow all UIDs except 1000 to add SUID/WOTH permission bits
    ./mac_fileperms -D                 # Deny all SUID/WOTH permission bit additions
    ./mac_fileperms -D -u 1000         # Deny only UID 1000 from adding SUID/WOTH permission bits
    ./mac_fileperms -D -U 1000         # Deny all UIDs except 1000 from adding SUID/WOTH permission bits
"""

parser = argparse.ArgumentParser(
    description="Dynamically load a MAC policy for SUID/WOTH permission bits",
    formatter_class=argparse.RawDescriptionHelpFormatter,
    epilog=examples)
mode = parser.add_mutually_exclusive_group(required=True)
mode.add_argument("-A", "--allow", action="store_true",
    help="define an allow policy")
mode.add_argument("-D", "--deny", action="store_true",
    help="define a deny policy")
user_control = parser.add_mutually_exclusive_group()
user_control.add_argument("-u", "--user", type=parse_uid, metavar="USER",
    help="apply the policy to a specific user")
user_control.add_argument("-U", "--exclude-user", type=parse_uid, metavar="EXCLUDED_USER",
    help="exclude a specific user from the policy")

args = parser.parse_args()


# BPF program (in C)
bpf_text = """
#include <linux/fs.h>
#include <linux/fs_struct.h>
#include <linux/errno.h>

#define __LOWER(x) (x & 0xffffffff)
#define __UPPER(x) (x >> 32)

/*
 * Create a data structure for collecting event data
 */
struct data_t {
    char comm[TASK_COMM_LEN];
    u32 uid;
    u32 gid;
    u32 pid;
    unsigned short oldmode;
    unsigned short reqmode;
    unsigned short newmode;
    int umask;
    int allowed;
};

/*
 * Create buffers for sending event data to userspace
 */
BPF_PERF_OUTPUT(creat_events);
BPF_PERF_OUTPUT(chmod_events);

/*
 * Attach to the "inode_create" LSM hook
 */
LSM_PROBE(inode_create, struct inode *dir, struct dentry *dentry,
    umode_t mode) {

    u64 gid_uid;
    u64 pid_tgid;
    int umask;
    int allowed;
    struct fs_struct *fs;
    struct task_struct *task;

    /*
     * Get the umask of the current task
     */
    task = (struct task_struct *)bpf_get_current_task();
    bpf_probe_read_kernel(&fs, sizeof(fs), &task->fs);
    bpf_probe_read_kernel(&umask, sizeof(umask), &fs->umask);

    /*
     * Check if SUID or world-writable bits will be set
     */
    if ((mode & ~umask) & (S_ISUID | S_IWOTH)) {

        /*
         * Gather event data
         */
        struct data_t data = {};
        gid_uid = bpf_get_current_uid_gid();
        pid_tgid = bpf_get_current_pid_tgid();
        bpf_get_current_comm(&data.comm, sizeof(data.comm));
        data.uid = __LOWER(gid_uid);
        data.gid = __UPPER(gid_uid);
        data.pid = __UPPER(pid_tgid);
        data.oldmode = dentry->d_inode->i_mode;
        data.reqmode = mode;
        data.umask = umask;
        data.newmode = mode & ~umask;

        /*
         * Optional filters (Populated by Python script)
         */
        if (
        1
        UID_FILTER
        DEFAULT_ALLOW
        ) {
            data.allowed = 0;
            creat_events.perf_submit(ctx, &data, sizeof(data));
            return -EPERM;
        } else {
            data.allowed = 1;
            creat_events.perf_submit(ctx, &data, sizeof(data));
        }
    }
    return 0;
}

/*
 * Attach to the "path_chmod" LSM hook
 */
LSM_PROBE(path_chmod, const struct path *path, umode_t mode) {

    u64 gid_uid;
    u64 pid_tgid;
    int allowed;

    /*
     * Check if SUID or world-writable bits will be set
     */
    if (mode & (S_ISUID | S_IWOTH)) {
        struct data_t data = {};
        gid_uid = bpf_get_current_uid_gid();
        pid_tgid = bpf_get_current_pid_tgid();

        /*
         * Gather event data
         */
        bpf_get_current_comm(&data.comm, sizeof(data.comm));
        data.uid = __LOWER(gid_uid);
        data.gid = __UPPER(gid_uid);
        data.pid = __UPPER(pid_tgid);
        data.oldmode = path->dentry->d_inode->i_mode;
        data.newmode = mode;

        /*
         * Optional filters (Populated by Python script)
         */
        if (
        1
        UID_FILTER
        DEFAULT_ALLOW
        ) {
            data.allowed = 0;
            chmod_events.perf_submit(ctx, &data, sizeof(data));
            return -EPERM;
        } else {
            data.allowed = 1;
            chmod_events.perf_submit(ctx, &data, sizeof(data));
        }
    }
    return 0;
}
"""


# Populate filters in the BPF program, based on options passed to this script
if args.user:
    bpf_text = bpf_text.replace('UID_FILTER', '&& (data.uid ALLOW_OR_DENY %s)' % args.user)
elif args.exclude_user:
    bpf_text = bpf_text.replace('UID_FILTER', '&& (data.uid INVERSE %s)' % args.exclude_user)
else:
    bpf_text = bpf_text.replace('UID_FILTER', '')

if args.allow:
    bpf_text = bpf_text.replace('ALLOW_OR_DENY', '!=')
    bpf_text = bpf_text.replace('INVERSE', '==')

    #Force a universal allow if only -A is specified
    if not args.user and not args.exclude_user:
        bpf_text = bpf_text.replace('DEFAULT_ALLOW', '&& 0')

elif args.deny:
    bpf_text = bpf_text.replace('ALLOW_OR_DENY', '==')
    bpf_text = bpf_text.replace('INVERSE', '!=')

bpf_text = bpf_text.replace('DEFAULT_ALLOW', '')


# Compile the BPF program and attach it to the LSM hooks
b = BPF(text=bpf_text)


def print_creat_event(cpu, data, size):
    """
    Print event data when attempting to create an inode
    with SUID/WOTH bits.
    """
    event = b["creat_events"].event(data)
    print("%s type=create comm=%s uid=%d gid=%d pid=%d oldmode=%06o reqmode=%06o umask=%06o newmode=%06o action=%s" % (
        datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S"),
        event.comm,
        event.uid,
        event.gid,
        event.pid,
        event.oldmode,
        event.reqmode,
        event.umask,
        event.newmode,
        "allow" if event.allowed else "deny"))


def print_chmod_event(cpu, data, size):
    """
    Print event data when attempting to add SUID/WOTH bits
    to an inode.
    """
    event = b["creat_events"].event(data)
    print("%s type=chmode comm=%s uid=%d gid=%d pid=%d oldmode=%06o newmode=%06o action=%s" % (
        datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S"),
        event.comm,
        event.uid,
        event.gid,
        event.pid,
        event.oldmode,
        event.newmode,
        "allow" if event.allowed else "deny"))


# Setup callback functions for each buffer
b["creat_events"].open_perf_buffer(print_creat_event)
b["chmod_events"].open_perf_buffer(print_chmod_event)


# Poll for incoming events
while 1:
    try:
        b.perf_buffer_poll()
    except KeyboardInterrupt:
        exit()

برای اجرای و آزمایش کد، با استفاده از دستور زیر، از افزودن بیت های SUID یا WOTH توسط کاربر prbzr جلوگیری (Deny) می گردد:

  ↲ sudo python3 mac_fileperms.py -D -u prbzr 

این اسکریپت در حالت D- فراخوانی شده است تا دسترسی کاربر با نام prbzr برای افزودن بیت‌های مجوز SUID یا WOTH، چه از طریق ایجاد فایل یا چه از طریف اصلاح فایل، را مسدود کند. کاربران دیگر، محدودیتی نخواهند داشت.

کاربر ”prbzr” سعی می کند تا بیت های مجوز SUID را به یک فایل اضافه کند، اما hmod یک خطا بر می گرداند.

در ترمینال کاربر prbzr:



و نتیجه در ترمینال اجرای اسکریپت:



اسکریپت، داده های هر رویداد را ثبت می کند. نوع "chmod" درخواستی را برای افزودن بیت های مجوز SUID یا WOTH به یک فایل موجود نشان می دهد. این اقدام توسط کاربر "prbzr" به عنوان رد شده ثبت می گردد.

mac_suidexec


اسکریپت mac_suidexec اجرای فایل های SUID را محدود می کند در حالیکه اسکریپت قبلی از ایجاد آن ها جلوگیری می کرد.
فایل های اجرایی SUID در سیستم های لینوکس وجود دارند، اما به طور مرکزی، اجرای SUID پیوست شده به اسکریپت یک برنامه به هوک “bprm_check_security” محدود می گردد. این هوک زمانی فعال می شود که یک فرآیند نوپا برای اجرا از طریق سیستم کال های خانواده ی ()exec مهیا شود. هوک، ساختار linux_binprm که حاوی اطلاعاتی در مورد برنامه ی فراخوانی شده است (Drysdale) را نشان می دهد. حالت برنامه فراخوانی شده برای بیت SUID مورد بررسی قرار می گیرد که در صورت شناسایی، منجر به ثبت توسط اسکریپت شده و احتمالاً از اجرای آن جلوگیری می گردد.

#!/usr/bin/python
#
# mac_suidexec  Restrict the execution of SUID binaries.
#
# 10-Oct-2020   Billy Wilson    Created this.

from bcc import BPF
from bcc.utils import printb
import argparse
import datetime
import os
import pwd


def parse_uid(user):
    """
    Check if valid UID or username is provided
    """
    try:
        result = int(user)
    except ValueError:
        try:
            user_info = pwd.getpwnam(user)
        except KeyError:
            raise argparse.ArgumentTypeError(
                "{0!r} is not valid UID or user entry".format(user))
        else:
            return user_info.pw_uid
    else:
        # Maybe validate if UID < 0 ?
        return result


# Set up argument parser
examples = """examples:
    ./mac_suidexec -A                   # Allow execution of all SUID binaries
    ./mac_suidexec -A -u 1000           # Allow only UID 1000 to execute SUID binaries
    ./mac_suidexec -A -U 1000           # Allow all UIDs except 1000 to execute SUID binaries
    ./mac_suidexec -A -f /bin/passwd    # Allow only the /bin/sudo SUID binary to execute
    ./mac_suidexec -A -F /bin/sudo      # Allow all SUID binaries to execute except /bin/sudo
    ./mac_suidexec -D                   # Deny execution of all SUID binaries
    ./mac_suidexec -D -u 1000           # Deny UID 1000 from executing SUID binaries
    ./mac_suidexec -D -U 0              # Deny all UIDs except 0 from executing SUID binaries
    ./mac_suidexec -D -f /bin/chsh      # Deny the /bin/chsh SUID binary from executing
    ./mac_suidexec -D -F        # Deny all SUID binaries from executing except /bin/newgrp
"""

parser = argparse.ArgumentParser(
    description="Dynamically load a MAC policy for executing SUID binaries",
    formatter_class=argparse.RawDescriptionHelpFormatter,
    epilog=examples)
mode = parser.add_mutually_exclusive_group(required=True)
mode.add_argument("-A", "--allow", action="store_true",
    help="define an allow policy")
mode.add_argument("-D", "--deny", action="store_true",
    help="define a deny policy")
user_control = parser.add_mutually_exclusive_group()
user_control.add_argument("-u", "--user", type=parse_uid, metavar="USER",
    help="apply the policy to a specific user")
user_control.add_argument("-U", "--exclude-user", type=parse_uid, metavar="EXCLUDED_USER",
    help="exclude a specific user from the policy")
file_control = parser.add_mutually_exclusive_group()
file_control.add_argument("-f", "--file", metavar="FILE", dest="file_path",
    help="apply the policy to a file")
file_control.add_argument("-F", "--exclude-file", metavar="EXCLUDED_FILE",
    help="exclude a file from the policy")

args = parser.parse_args()


# BPF program (in C)
bpf_text = """
#include <linux/binfmts.h>
#include <linux/cred.h>
#include <linux/fs.h>
#include <linux/errno.h>

#define __LOWER(x) (x & 0xffffffff)
#define __UPPER(x) (x >> 32)

/*
 * Create a data structure for collecting event data
 */
struct data_t {
    char comm[TASK_COMM_LEN];
    u32 uid;
    u32 gid;
    u32 pid;
    u32 dev;
    unsigned long inode;
    unsigned short mode;
    int allowed;
};

/*
 * Create a buffer for sending event data to userspace
 */
BPF_PERF_OUTPUT(events);

/*
 * Attach to the "bprm_check_security" LSM hook
 */
LSM_PROBE(bprm_check_security, struct linux_binprm *bprm) {
   
    u64 gid_uid;
    u64 pid_tgid;
    unsigned short mode;

    /*
     * Check if executable has its SUID bit set
     */
    mode = bprm->file->f_inode->i_mode;
    if (mode & S_ISUID) {

        /*
         * Gather event data
         */
        struct data_t data = {};
        u64 gid_uid = bpf_get_current_uid_gid();
        u64 pid_tgid = bpf_get_current_pid_tgid();
        bpf_get_current_comm(&data.comm, sizeof(data.comm));
        data.uid = __LOWER(gid_uid);
        data.gid = __UPPER(gid_uid);
        data.pid = __UPPER(pid_tgid);
        data.dev = bprm->file->f_inode->i_sb->s_dev;
        data.inode = bprm->file->f_inode->i_ino;
        data.mode = mode;

        /*
         * Optional filters (Populated by Python script)
         */
        if (
        1
        UID_FILTER
        FILE_FILTER
        DEFAULT_ALLOW
        ) {
            data.allowed = 0;
            events.perf_submit(ctx, &data, sizeof(data));
            return -EPERM;
        } else {
            data.allowed = 1;
            events.perf_submit(ctx, &data, sizeof(data));
        }
    }
    return 0;
}
"""


# Populate filters in the BPF program, based on options passed to this script
if args.allow:
    if args.user:
        bpf_text = bpf_text.replace('UID_FILTER',
                                    '&& (data.uid != %s)' % args.user)
    elif args.exclude_user:
        bpf_text = bpf_text.replace('UID_FILTER',
                                    '&& (data.uid == %s)' % args.exclude_user)
    else:
        bpf_text = bpf_text.replace('UID_FILTER', '')

    if args.file_path:
        file_o = os.lstat(args.file_path)
        bpf_text = bpf_text.replace('FILE_FILTER',
                                    '&& !((data.dev == %s) && (data.inode == %s))' % (file_o.st_dev, file_o.st_ino))
    elif args.exclude_file:
        file_o = os.lstat(args.exclude_file)
        bpf_text = bpf_text.replace('FILE_FILTER',
                                    '&& (data.dev == %s) && (data.inode == %s)' % (file_o.st_dev, file_o.st_ino))
    else:
        bpf_text = bpf_text.replace('FILE_FILTER', '')

    # Force a universal allow if only '-A' is specified
    if not args.user and not args.exclude_user and not args.file_path and not args.exclude_file:
        bpf_text = bpf_text.replace('DEFAULT_ALLOW', '&& 0')

elif args.deny:
    if args.user:
        bpf_text = bpf_text.replace('UID_FILTER',
                                    '&& (data.uid == %s)' % args.user)
    elif args.exclude_user:
        bpf_text = bpf_text.replace('UID_FILTER',
                                    '&& (data.uid != %s)' % args.exclude_user)
    else:
        bpf_text = bpf_text.replace('UID_FILTER', '')

    if args.file_path:
        file_o = os.lstat(args.file_path)
        bpf_text = bpf_text.replace('FILE_FILTER',
                                    '&& (data.dev == %s) && (data.inode == %s)' % (file_o.st_dev, file_o.st_ino))
    elif args.exclude_file:
        file_o = os.lstat(args.exclude_file)
        bpf_text = bpf_text.replace('FILE_FILTER',
                                    '&& !((data.dev == %s) && (data.inode == %s))' % (file_o.st_dev, file_o.st_ino))
    else:
        bpf_text = bpf_text.replace('FILE_FILTER', '')

bpf_text = bpf_text.replace('DEFAULT_ALLOW', '')


# Compiler the BPF program and attach it to the LSM hook
b = BPF(text=bpf_text)


def print_event(cpu, data, size):
    """
    Print event data when a SUID binary is about to be
    executed.
    """
    event = b["events"].event(data)
    print("%s type=exsuid comm=%s uid=%d gid=%d pid=%d dev=%d inode=%lu mode=%o action=%s" % (
       datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S"),
           event.comm,
           event.uid,
           event.gid,
           event.pid,
           event.dev,
           event.inode,
           event.mode,
           "allow" if event.allowed else "deny"))


#setup callback function for the buffer
b["events"].open_perf_buffer(print_event)


# Poll for incoming events
while 1:
    try:
        b.perf_buffer_poll()
    except KeyboardInterrupt:
        exit()

 برای اجرای اسکریپت از دستورهای زیر استفاده می کنیم:
و پس از وارد کردن گذرواژه:

↲ sudo python3 mac_suidexec.py -D -u prbzr

در زیر نمونه ای از فراخوانی mac_suidexec و تأثیر آن بر سشن یک کاربر آورده شده است:

در ترمینال کاربر:




و خروجی اسکریپت:




همانطور که در تصویر بالا مشاهده می شود، با اجرای اسکریپت، کاربر prbzr پس از اجرای passwd که باید منجر به تغییر گذرواژه ی او گردد، از انجام این کار باز داشته شد.


آفلاین prbzr

  • Newbie
  • *
  • ارسال: 10
mac_sshlisteners


اسکریپت mac_sshlisteners از ایجاد تونل های پروکسی SSH جلوگیری و آدرس های IPv4 و IPv6 را مدیریت می کند.
اینکه SSH به کاربری اجازه می دهد از یک سرور به عنوان یک پروکسی برای دسترسی به منابع خارجی استفاده کند یک ویژگی ذاتی SSH است. از این ویژگی می توان برای انتقال داده به دستگاهی (یا همچنین دریافت داده از دستگاهی) که دسترسی مستقیم به اینترنت ندارد استفاده کرد.اگرچه این ویژگی را می توان در سرورهای SSH غیرفعال نمود، اما به طور پیش فرض فعال است و معمولاً به همین ترتیب باقی می ماند.
هر زمان که یک کاربر SSH سعی کند به یک سوکت گوش فرا دهد، نشان آن است که کاربر در تلاش برای انتقال ترافیک پراکسی از طریق سروری است که به آن دسترسی دارد.

اسکریپت mac_sshlisteners هم تونل‌های پراکسی را شناسایی می نماید و هم از آن ایجاد آن جلوگیری می کند. این کد یک برنامه را به هوک ال اس امی "socket_listen" متصل می نماید، تا هر زمان که فرآیندی سعی کند تا سوکت را به حالت LISTEN تغییر دهد، فعال شود. سوکت های IPv4 و IPv6 را بررسی می شوند تا اطمینان حاصل شود که یک کلاینت SSH سعی در گوش دادن به آن ها را ندارد.

#!/usr/bin/python
#
# mac_sshlisteners  Restrict SSH clients from listening on sockets.
#                   Supports IPv4 and IPv6.
#
# 10-Oct-2020   Billy Wilson    Created this.

from bcc import BPF
from bcc.utils import printb
import argparse
import datetime
import pwd
import socket
from socket import inet_ntop, AF_INET, AF_INET6
import struct


def parse_uid(user):
    """
    Check if valid UID or username is provided
    """
    try:
        result = int(user)
    except ValueError:
        try:
            user_info = pwd.getpwnam(user)
        except KeyError:
            raise argparse.ArgumentTypeError(
                "{0!r} is not valid UID or user entry".format(user))
        else:
            return user_info.pw_uid
    else:
        # Maybe validate if UID < 0 ?
        return result


def ip2long(ip):
    """
    Convert an IP string to a long
    """
    packedIP = socket.inet_aton(ip)
    return struct.unpack("!L", packedIP)[0]


def long2ip(num):
    """
    Convert a long to an IP string
    """
    return socket.inet_ntoa(struct.pack("!L", num))


# Set up argument parser
examples = """examples:
    ./mac_sshlisteners -A                 # Allow all SSH listeners
    ./mac_sshlisteners -A -u 1000         # Allow only UID 1000 to open SSH listeners
    ./mac_sshlisteners -D                 # Deny all SSH proxies
    ./mac_sshlisteners -D -u 1000         # Deny only UID 1000 from opening SSH listeners
"""

parser = argparse.ArgumentParser(
    description="Dynamically load a MAC policy for SSH listeners",
    formatter_class=argparse.RawDescriptionHelpFormatter,
    epilog=examples)
mode = parser.add_mutually_exclusive_group(required=True)
mode.add_argument("-A", "--allow", action="store_true",
    help="define an allow policy")
mode.add_argument("-D", "--deny", action="store_true",
    help="define a deny policy")
user_control = parser.add_mutually_exclusive_group()
user_control.add_argument("-u", "--user", type=parse_uid, metavar="USER",
    help="apply the policy to a specific user")
user_control.add_argument("-U", "--exclude-user", type=parse_uid, metavar="EXCLUDED_USER",
    help="exclude a specific user from the policy")

args = parser.parse_args()


# BPF program (in C)
bpf_text = """
#include <net/sock.h>
#include <linux/inet.h>
#include <linux/errno.h>

#define __LOWER(x) (x & 0xffffffff)
#define __UPPER(x) (x >> 32)
#define __SWAP32(x) ((x >> 24) & 0xff) | \
                    ((x >> 8)  & 0xff00) | \
                    ((x << 8)  & 0xff0000) | \
                    ((x << 24) & 0xff000000)

/*
 * Create a data structure for collecting event data
 */
struct data_t {
    char comm[TASK_COMM_LEN];
    u32 uid;
    u32 gid;
    u32 pid;
    unsigned short family;
    u16 protocol;
    unsigned short lport;
    long laddr4;
    unsigned __int128 laddr6;
    int allowed;
};

/*
 * Create buffers for sending event data to userspace
 */
BPF_PERF_OUTPUT(events);

/*
 * Attach to the "socket_listen" LSM hook
 */
LSM_PROBE(socket_listen, struct socket *sock, int backlog) {

    u64 gid_uid;
    u64 pid_tgid;
    int allowed;
    struct inet_sock *sockp;

    struct data_t data = {};

    /*
     * Examine IPv4 and IPv6 socket listen operations
     */
    sockp = (struct inet_sock *)sock;
    data.family = sock->sk->sk_family;
    if (data.family == AF_INET ||
        data.family == AF_INET6) {

        /*
         * Gather event data
         */
        gid_uid = bpf_get_current_uid_gid();
        pid_tgid = bpf_get_current_pid_tgid();
        bpf_get_current_comm(&data.comm, sizeof(data.comm));
        data.uid = __LOWER(gid_uid);
        data.gid = __UPPER(gid_uid);
        data.pid = __UPPER(pid_tgid);
        data.protocol = sock->sk->sk_protocol;
        data.lport = sock->sk->sk_num;
        if (data.family == AF_INET)
            data.laddr4 = __SWAP32(sock->sk->sk_rcv_saddr);
        else if (data.family == AF_INET6)
            bpf_probe_read_kernel(&data.laddr6, sizeof(data.laddr6), sock->sk->sk_v6_rcv_saddr.in6_u.u6_addr32);

        /*
         * "ssh" comm filter and optional filters (Populated by Python script)
         */
        if (
        (__builtin_memcmp("ssh", data.comm, sizeof("ssh")) == 0)
        UID_FILTER
        ) {
            data.allowed = 0;
            events.perf_submit(ctx, &data, sizeof(data));
            return -EPERM;
        } else {
            data.allowed = 1;
            events.perf_submit(ctx, &data, sizeof(data));
        }
    }
    return 0;
}
"""


# Populate filters in the BPF program, basedon options passed to this script
if args.user:
    bpf_text = bpf_text.replace('UID_FILTER', '&& (data.uid ALLOW_OR_DENY %s)' % args.user)
elif args.exclude_user:
    bpf_text = bpf_text.replace('UID_FILTER', '&& (data.uid INVERSE %s)' % args.exclude_user)
else:
    bpf_text = bpf_text.replace('UID_FILTER', '')

if args.allow:
    bpf_text = bpf_text.replace('ALLOW_OR_DENY', '!=')
    bpf_text = bpf_text.replace('INVERSE', '==')
elif args.deny:
    bpf_text = bpf_text.replace('ALLOW_OR_DENY', '==')
    bpf_text = bpf_text.replace('INVERSE', '!=')


# Compile the BPF program and attach it to the LSM hook
b = BPF(text=bpf_text)


def print_event(cpu, data, size):
    """
    Print event data for an IPv4/IPv6 socket listen attempt.
    """
    event = b["events"].event(data)
    print("%s type=listen comm=%s uid=%d gid=%d pid=%d proto=%d laddr=%s lport=%u action=%s" % (
        datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S"),
        event.comm,
        event.uid,
        event.gid,
        event.pid,
        event.protocol,
        long2ip(event.laddr4) if event.family == AF_INET else "[" + inet_ntop(AF_INET6, event.laddr6) + "]",
        event.lport,
        "allow" if event.allowed else "deny"))


# Setup callback function for the buffer
b["events"].open_perf_buffer(print_event)


# Poll for incoming events
while 1:
    try:
        b.perf_buffer_poll()
    except KeyboardInterrupt:
        exit()

در ادامه فراخوانی اسکریپت mac_sshlisteners و تأثیر آن بر کاربری که سعی می کند یک تونل پروکسی SSH باز کند نشان داده می شود.

برای ایجاد یک اتصال SSH ابتدا لازم است تا از طریق دستور زیر نسبت به نصب پکیج OpenSSH اقدام نماییم:

↲ sudo yum install openssh-server

پس از آنکه نصب پکیج موفقیت آمیز بود، با استفاده از دستور زیر SSH را فعال می کنیم:

↲ systemctl enable sshd

حالا با وارد کردن دستور زیر، یک ارتباط SSH آغاز می گردد:

↲ systemctl start sshd

برای مشاهده ی وضعیت ارتباط SSH ایجاد شده می توان از دستور زیر استفاده کرد:

↲ sudo systemctl status sshd


و اسکریپت خود را با وارد کردن دستور زیر اجرا می کنیم:

↲ sudo python3 mac_sshlisteners.py -D -U root

سپس در یک ترمینال کاربر prbzr از طریق دستور زیر، پورت پیش فرض و فعال در ارتباط SSH فعلی را به پورت ۹۹۹۹ منتقل (Divert) می کنیم:

↲ ssh -D 9999 0.0.0.0

و اینجاست که اسکریپت از انجام این کار توسط کاربر prbzr ممانعت می کند:



نتیجه ای که در ترمینال اسکریپت چاپ می شود:



mac_skconnections


اسکریپت mac_skconnections اتصالات سوکت را به مقصدهای خاصی محدود می کند. این کد   می تواند تلاش برای اتصال هر نوع سوکت را در نظر گیرد اما در این مورد فقط اتصالات سوکت IPv4 را بررسی می نماید. این اسکریپت با افزودن محدودیت‌های فایروال برای هر کاربر، در برابر تلاش‌های محوری (Pivot Attempts) محافظت می‌کند و این کار بدون تغییر پیکربندی فایروال مرکزی میزبان انجام می‌گردد.
این اسکریپت، صرف نظر از اینکه یک برنامه را به پروتوکل زیربنایی ال اس امِ mac_skconnections الحاق کرده است، هر گونه اتصال سوکت IPv4 را مدیریت می کند. به این ترتیب که یک برنامه را به هوک ال اس امی "socket_connect" متصل کرده و زمانی که فرآیندی در تلاش برای ایجاد یک سوکت باشد، فعال می گردد.

#!/usr/bin/python
#
# mac_socketconnections     Restrict IPv4 socket connections.
#                           No Unix or IPv6 socket support (yet).
#
# 10-Oct-2020   Billy Wilson    Created this.

from bcc import BPF
from bcc.utils import printb
import argparse
import datetime
import pwd
import socket
import struct


def parse_uid(user):
    """
    Check if valid UID or username is provided
    """
    try:
        result = int(user)
    except ValueError:
        try:
            user_info = pwd.getpwnam(user)
        except KeyError:
            raise argparse.ArgumentTypeError(
                "{0!r} is not valid UID or user entry".format(user))
        else:
            return user_info.pw_uid
    else:
        # Maybe validate if UID < 0 ?
        return result


def ip2long(ip):
    """
    Convert an IP string to a long
    """
    packedIP = socket.inet_aton(ip)
    return struct.unpack("!L", packedIP)[0]


def long2ip(num):
    """
    Convert a long to an IP string
    """
    return socket.inet_ntoa(struct.pack("!L", num))


# Set up argument parser
examples = """examples:
    ./mac_skconnections -A 192.168.15.0 -m 255.255.255.0 # Only allow socket connections to 192.168.15.0/24
    ./mac_skconnections -A 1.2.3.4 -u 1000               # Only allow UID 1000 to make socket connections to 1.2.3.4
    ./mac_skconnections -A 1.2.3.4 -U 1000               # Allow any UID except 1000 to make socket connections to 1.2.3.4
    ./mac_skconnections -D 10.1.2.3                      # Deny all socket connections to 10.1.2.3
"""

parser = argparse.ArgumentParser(
    description="Dynamically load a MAC policy for socket connections",
    formatter_class=argparse.RawDescriptionHelpFormatter,
    epilog=examples)
parser.add_argument("ip", type=str,
    help="the target IP for an allow/deny policy")
parser.add_argument("-m", "--mask", metavar="MASK", default="255.255.255.255",
    help="the subnet mask for an allow/deny policy")
mode = parser.add_mutually_exclusive_group(required=True)
mode.add_argument("-A", "--allow", action="store_true",
    help="define an allow policy")
mode.add_argument("-D", "--deny", action="store_true",
    help="define a deny policy")
user_control = parser.add_mutually_exclusive_group()
user_control.add_argument("-u", "--user", type=parse_uid, metavar="USER",
    help="apply the policy to a specific user")
user_control.add_argument("-U", "--exclude-user", type=parse_uid, metavar="EXCLUDED_USER",
    help="exclude a specific user from the policy")
args = parser.parse_args()


# BPF program (in C)
bpf_text = """
#include <net/sock.h>
#include <linux/inet.h>
#include <linux/errno.h>
#define __LOWER(x) (x & 0xffffffff)
#define __UPPER(x) (x >> 32)
#define __SWAP16(x) (x >> 8) | ((x << 8) & 0x00ff00)
#define __SWAP32(x) ((x >> 24) & 0xff) |     \
                    ((x << 8) & 0xff0000) |  \
                    ((x >> 8) & 0xff00) |    \
                    ((x << 24) & 0xff000000)
/*
 * Create a data structure for collecting event data
 */
struct data_t {
    char comm[TASK_COMM_LEN];
    u32 uid;
    u32 gid;
    u32 pid;
    u16 sk_protocol;
    long daddr;
    unsigned short dport;
    int allowed;
};
/*
 * Create a buffer for sending event data to userspace
 */
BPF_PERF_OUTPUT(events);
/*
 * Attach to the "socket_connect" LSM hook
 */
LSM_PROBE(socket_connect, struct socket *sock, struct sockaddr *address,
    int addrlen) {
    long filter_addr;
    long filter_netmask;
    u64 gid_uid;
    u64 pid_tgid;
    int allowed;
    struct sockaddr_in *addr_in;
    /*
     * IP subnet filter (Populated by Python script)
     */
    filter_addr = FILTER_ADDR;
    filter_netmask = FILTER_NETMASK;
    /*
     * Examine IPv4 socket connections only (for now)
     */
    if (addrlen == sizeof(struct sockaddr_in)) {
        addr_in = (struct sockaddr_in *)address;
        /*
         * Gather event data
         */
        struct data_t data = {};
        gid_uid = bpf_get_current_uid_gid();
        pid_tgid = bpf_get_current_pid_tgid();
        bpf_get_current_comm(&data.comm, sizeof(data.comm));
        data.uid = __LOWER(gid_uid);
        data.gid = __UPPER(gid_uid);
        data.pid = __UPPER(pid_tgid);
        data.sk_protocol = sock->sk->sk_protocol;
        data.daddr = __SWAP32(addr_in->sin_addr.s_addr);
        data.dport = __SWAP16(addr_in->sin_port);
        /*
         * Subnet filter and optional filters (Populated by Python script)
         */
        if (
        (data.daddr & filter_netmask) ALLOW_OR_DENY (filter_addr & filter_netmask)
        UID_FILTER
        ) {
            data.allowed = 0;
            events.perf_submit(ctx, &data, sizeof(data));
            return -EPERM;
        } else {
            data.allowed = 1;
            events.perf_submit(ctx, &data, sizeof(data));
            return 0;
        }
    }
    return 0;
}
"""


# Convert IP and subnet to long integers for the BPF program
ip = ip2long(args.ip)
mask = ip2long(args.mask)
bpf_text = bpf_text.replace('FILTER_ADDR', str(ip));
bpf_text = bpf_text.replace('FILTER_NETMASK', str(mask));


# Populate filters in the BPF program, based on options passed to this script
if args.user:
    bpf_text = bpf_text.replace('UID_FILTER', '&& (data.uid ALLOW_OR_DENY %s)' % args.user)
elif args.exclude_user:
    bpf_text = bpf_text.replace('UID_FILTER', '&& (data.uid INVERSE %s)' % args.exclude_user)
else:
    bpf_text = bpf_text.replace('UID_FILTER', '')

if args.allow:
    bpf_text = bpf_text.replace('ALLOW_OR_DENY', '!=')
    bpf_text = bpf_text.replace('INVERSE', '==')
elif args.deny:
    bpf_text = bpf_text.replace('ALLOW_OR_DENY', '==')
    bpf_text = bpf_text.replace('INVERSE', '!=')


# Compile the BPF program and attach it to the LSM hook
b = BPF(text=bpf_text)


def print_event(cpu, data, size):
    """
    Print event data for a socket connection.
    """
    event = b["events"].event(data)
    print("%s type=skconn comm=%s uid=%d gid=%d pid=%d proto=%d daddr=%s dport=%u action=%s" % (
        datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S"),
        event.comm,
        event.uid,
        event.gid,
        event.pid,
        event.sk_protocol,
        long2ip(event.daddr),
        event.dport,
        "allow" if event.allowed else "deny"))


# Setup callback function for the buffer
b["events"].open_perf_buffer(print_event)


# Poll for incoming events
while 1:
    try:
        b.perf_buffer_poll()
    except KeyboardInterrupt:
        exit()

در ادامه نمونه ای از فراخوانی اسکریپت mac_skconnections و نحوه ی تأثیر آن بر کاربر آورده خواهد شد.

ابتدا با دسترسی root، اسکریپت را با استفاده از دستور زیر اجرا می کنیم:

sudo python3 mac_skconnections.py -D 10.100.0.0 -m 255.255.0.0 -u prbzr

و سپس در ترمینال کاربر prbzr نسبت به ایجاد اتصالات اقدام می نماییم:



نتیجه در ترمینال اسکریپت:



همانطور که مشاهده می شود کاربر prbzr نسبت به ایجاد اتصالات سوکت IPv4 به زیرشبکه 10.100.0.0/16 اقدام کرده است. هنگامی که کاربر سعی می کند SSH را به مقصدی در آن زیرشبکه منتقل کند، این عملیات مجاز نیست در حالیکه تلاش برای مقصد دیگری خارج از زیرشبکه ی محدود شده با موفقیت انجام می شود. در نهایت کاربر یک netcat را به 10.100.4.5 در پورت 53 امتحان  می کند که توسط اسکریپت مسدود می گردد.

mac_killtasks


اسکریپت mac_killtasks سیگنال های فرآیند را محدود می کند و از آن برای محافظت از تمام اسکریپت های BCC در برابر خاتمه ی زودهنگام - چه توسط کاربران غیرمجاز و چه توسط روت – استفاده می شود. گرچه اینجا فقط سیگنال‌های SIGKILL و SIGTERM را مدیریت می گردد، اما می‌توان آن را برای کنترل هر سیگنالی توسعه داد.
ارسال سیگنال بخش اساسی لینوکس و سایر سیستم عامل های POSIX است. سیگنال ها می توانند به فرآیندها اطلاع دهند که یک رویداد رخ داده است. آنها همچنین می توانند فرآیندها را متوقف یا خاتمه دهند. کرنل آن ها را تولید می کند، اما سایر فرآیندهای سیستم می توانند درخواست کنند که کرنل از طرف آن ها سیگنال ارسال کند. بیش از سی سیگنال در لینوکس در دسترس است، اما دو سیگنال محدود شده توسط این اسکریپت عبارتند از: SIGTERM (از یک فرآیند بخواهد خود را خاتمه دهد) و SIGKILL (فرآیند را فوراً بکشد).
این اسکریپت به هوک ال اس امی "task_kill" متصل می شود؛ هر وقت سیگنالی بخواهد به یک فرآیند ارسال شود، شروع به کار می کند و ویژگی‌های (Attributes) فرآیند منبع و فرآیند هدف را بررسی کرده تا مشخص کند آیا سیگنال باید مجاز باشد یا خیر.

#!/usr/bin/python
#
# mac_killtasks  Restrict kill signals.
#
# 10-Oct-2020   Billy Wilson    Created this.

from bcc import BPF
from bcc.utils import printb
import argparse
import datetime
import os
import pwd


def parse_uid(user):
    """
    Check if valid UID or username is provided
    """
    try:
        result = int(user)
    except ValueError:
        try:
            user_info = pwd.getpwnam(user)
        except KeyError:
            raise argparse.ArgumentTypeError(
                "{0!r} is not valid UID or user entry".format(user))
        else:
            return user_info.pw_uid
    else:
        # Maybe validate if UID < 0 ?
        return result


# Set up argument parser
examples = """examples:
    ./mac_killtasks -A                   # Allow all SIGTERM/SIGKILL signals
    ./mac_killtasks -A -u root           # Only allow SIGTERM/SIGKILL signals from root
    ./mac_killtasks -D                   # Deny all SIGTERM/SIGKILL signals
    ./mac_killtasks -D -k                # Deny all SIGTERM/SIGKILL signals, including from kernel processes (dangerous)
    ./mac_killtasks -D -t 3333           # Deny all SIGTERM/SIGKILL signals sent to PID 3333
    ./mac_killtasks -D -e -t 3333        # Deny all SIGTERM/SIGKILL signals sent to PID 3333, and protect mac_killtasks from termination
    ./mac_killtasks -D -p 1111           # Deny all SIGTERM/SIGKILL signals sent from PID 1111
"""

parser = argparse.ArgumentParser(
    description="Dynamically load a MAC policy for restricting kill signals",
    formatter_class=argparse.RawDescriptionHelpFormatter,
    epilog=examples)
mode = parser.add_mutually_exclusive_group(required=True)
mode.add_argument("-A", "--allow", action="store_true",
    help="define an allow policy")
mode.add_argument("-D", "--deny", action="store_true",
    help="define a deny policy")
parser.add_argument("-k", "--kernel", action="store_true",
    help="Apply the policy to kernel signals as well (dangerous)")
parser.add_argument("-e", "--eternal", action="store_true",
    help="Block all kill signals against the process that loaded this policy")
user_control = parser.add_mutually_exclusive_group()
user_control.add_argument("-u", "--user", type=parse_uid, metavar="USER",
    help="apply the policy to a specific user")
user_control.add_argument("-U", "--exclude-user", type=parse_uid, metavar="EXCLUDED_USER",
    help="exclude a specific user from the policy")
pid_control = parser.add_mutually_exclusive_group()
pid_control.add_argument("-p", "--source-pid", metavar="PID",
    help="apply the policy to a source pid")
pid_control.add_argument("-P", "--exclude-source-pid", metavar="EXCLUDED_PID",
    help="exclude a source pid from the policy")
target_control = parser.add_mutually_exclusive_group()
target_control.add_argument("-t", "--target-pid", metavar="PID",
    help="apply the policy to a target pid")
target_control.add_argument("-T", "--exclude-target-pid", metavar="EXCLUDED_PID",
    help="exclude a target pid from the policy")

args = parser.parse_args()


# BPF program (in C)
bpf_text = """
#include <linux/cred.h>
#include <linux/sched.h>
#include <uapi/asm/signal.h>
#include <linux/errno.h>
#define __LOWER(x) (x & 0xffffffff)
#define __UPPER(x) (x >> 32)
/*
 * Create a data structure for collecting event data
 */
struct data_t {
    char comm[TASK_COMM_LEN];
    u32 uid;
    u32 gid;
    u32 pid;
    u32 targetuid;
    u32 targetpid;
    int signo;
    int allowed;
};
/*
 * Create a buffer for sending event data to userspace
 */
BPF_PERF_OUTPUT(events);
/*
 * Attach to the "task_kill" LSM hook
 */
LSM_PROBE(task_kill, struct task_struct *p, struct kernel_siginfo *info,
    int sig, const struct cred *cred) {
   
    u64 gid_uid;
    u64 pid_tgid;
    int signo;
    /*
     * Check if the signal is SIGKILL or SIGTERM
     */
    signo = info->si_signo;
    if (signo == SIGKILL || signo == SIGTERM) {
        /*
         * Gather event data
         */
        struct data_t data = {};
        u64 gid_uid = bpf_get_current_uid_gid();
        u64 pid_tgid = bpf_get_current_pid_tgid();
        bpf_get_current_comm(&data.comm, sizeof(data.comm));
        data.uid = __LOWER(gid_uid);
        data.gid = __UPPER(gid_uid);
        data.pid = __UPPER(pid_tgid);
        data.targetuid = p->cred->uid.val;
        data.targetpid = p->pid;
        data.signo = signo;
        /*
         * Optional filters (Populated by Python script)
         */
        KERNEL_FILTER
        ETERNAL_POLICY
        if (
        1
        UID_FILTER
        PID_FILTER
        TARGET_FILTER
        DEFAULT_ALLOW
        ) {
            data.allowed = 0;
            events.perf_submit(ctx, &data, sizeof(data));
            return -EPERM;
        } else {
            data.allowed = 1;
            events.perf_submit(ctx, &data, sizeof(data));
        }
    }
    return 0;
}
"""


# Populate filters in the BPF program, based on options passed to this script
if args.kernel:
    bpf_text = bpf_text.replace('KERNEL_FILTER', '')
else:
    always_allow_kernel = """
        /*
         * Allow all signals originating from the kernel
         */
        if ((long)info == 1 || SI_FROMKERNEL(info)) {
            data.allowed = 1;
            events.perf_submit(ctx, &data, sizeof(data));
            return 0;
        }
    """
    bpf_text = bpf_text.replace('KERNEL_FILTER', always_allow_kernel)

if args.eternal:
    eternal_policy = """
        /*
         * Only allow the parent process of mac_killtasks to send SIGKILL/SIGTERM to it
         */
        if (data.targetpid == %s && data.pid != %s) {
            data.allowed = 0;
            events.perf_submit(ctx, &data, sizeof(data));
            return -EPERM;
        }
    """ % (os.getpid(), os.getppid())
    bpf_text = bpf_text.replace('ETERNAL_POLICY', eternal_policy)
else:
    bpf_text = bpf_text.replace('ETERNAL_POLICY', '')

if args.user:
    bpf_text = bpf_text.replace('UID_FILTER', '&& (data.targetuid ALLOW_OR_DENY %s)' % args.user)
elif args.exclude_user:
    bpf_text = bpf_text.replace('UID_FILTER', '&& (data.targetuid INVERSE %s)' % args.exclude_user)
else:
    bpf_text = bpf_text.replace('UID_FILTER', '')

if args.source_pid:
    bpf_text = bpf_text.replace('PID_FILTER', '&& (data.pid ALLOW_OR_DENY %s)' % args.source_pid)
elif args.exclude_source_pid:
    bpf_text = bpf_text.replace('PID_FILTER', '&& (data.pid INVERSE %s)' % args.exclude_source_pid)
else:
    bpf_text = bpf_text.replace('PID_FILTER', '')

if args.target_pid:
    bpf_text = bpf_text.replace('TARGET_FILTER', '&& (data.targetpid ALLOW_OR_DENY %s)' % args.target_pid)
elif args.exclude_target_pid:
    bpf_text = bpf_text.replace('TARGET_FILTER', '&& (data.targetpid INVERSE %s)' % args.exclude_target_pid)
else:
    bpf_text = bpf_text.replace('TARGET_FILTER', '')

if args.allow:
    # Force a univeral allow if only '-A' is specified
    if not args.user and not args.exclude_user and not args.source_pid and not args.exclude_source_pid \
    and not args.target_pid and not args.exclude_target_pid:
        bpf_text = bpf_text.replace('DEFAULT_ALLOW', '&& 0')

    bpf_text = bpf_text.replace('ALLOW_OR_DENY', '!=')
    bpf_text = bpf_text.replace('INVERSE', '==')

elif args.deny:
    bpf_text = bpf_text.replace('ALLOW_OR_DENY', '==')
    bpf_text = bpf_text.replace('INVERSE', '!=')

bpf_text = bpf_text.replace('DEFAULT_ALLOW', '')

# Compiler the BPF program and attach it to the LSM hook
b = BPF(text=bpf_text)


def print_event(cpu, data, size):
    """
    Print event data when a kill signal is about to be
    sent.
    """
    event = b["events"].event(data)
    print("%s type=sgkill comm=%s uid=%d gid=%d pid=%d targetuid=%d targetpid=%d signo=%d action=%s" % (
       datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S"),
           event.comm,
           event.uid,
           event.gid,
           event.pid,
           event.targetuid,
           event.targetpid,
           event.signo,
           "allow" if event.allowed else "deny"))


#setup callback function for the buffer
b["events"].open_perf_buffer(print_event)


# Poll for incoming events
while 1:
    try:
        b.perf_buffer_poll()
    except KeyboardInterrupt:
        exit()

در ادامه نمونه ای از فراخوانی mac_killtasks برای محافظت از mac_fileperms و تأثیر آن بر روی کاربر root  نمایش داده می شود.

ابتدا، mac_fileperms را فراخوانی می کنیم تا فقط کاربر root بتواند فایل هایی با بیت های SUID یا WOTH ایجاد کند. شناسه ی فرآیند آن اسکریپت ذخیره می شود. سپس mac_killtasks فراخوانی خواهد شد تا هیچ فرآیندی نتواند سیگنال کشتن را به فرآیند mac_fileperms یا به فرآیند mac_killtasks که از آن محافظت می کند، ارسال کند.



در ترمینال دیگری، کاربر روت، PIDهای دو اسکریپت را جستجو می نماید و سعی می کند سیگنال SIGTERM را به mac_fileperms ارسال کند؛ که این تلاش با شکست مواجه می شود.





سپس کاربر روت تلاش ناموفقی برای ارسال سیگنال های SIGKILL به هر دو اسکریپت BCC دارد که این ها هم شکست می خورند. سرانجام، روت یک فرآیند خواب را ایجاد و یک سیگنال SIGTERM برای آن ارسال می کند که با موفقیت انجام و تمام فعالیت ها ثبت می گردد.






این اسکریپت دو گزینه جدید ارائه می نماید: --کرنل (-k) و --eternal (-e). گزینه --kernel برنامه ی پیوست شده را تغییر می دهد تا  علاوه بر سیگنال‌های درخواست شده توسط فرآیندهای فضای کاربر، سیگنال‌های منشأ گرفته از خود کرنل را نیز کنترل کند.

توجه:

پیامدهای مسدود کردن سیگنال‌ها از فرآیندهای فضای کرنل آزمایش نمی شود زیرا این گزینه به عنوان "خطرناک" در متن راهنمای اسکریپت علامت‌گذاری شده است. گزینه ی --eternal تضمین می‌کند که خود فایل اجرایی mac_killtasks توسط هیچ فرآیندی غیر از فرآیند والد آن قابل اجرا نمی باشد. اگر فرآیند والد خارج شود، هیچ چیز نمی تواند روند را از بین ببرد.


آفلاین prbzr

  • Newbie
  • *
  • ارسال: 10
محیط آزمایش:


برای بررسی فعال بودن KRSI در پیکربندی کرنل لینوکس، از دستورات زیر استفاده می نماییم؛ دستور اول:

   ↲ cat /boot/config-$(uname -r) | grep -i CONFIG_BPF
فهرست چاپ شده باید حتما شامل CONFIG_BPF_LSM=y باشد؛ این خط ابزار دقیق KRSI را فعال می کند:

 



دستور دوم:

  ↲ cat /boot/config-$(uname -r) | grep -i CONFIG_LSM

در فهرست چاپ شده باید حتما ال اس ام bpf فعال باشد؛ همانطور که در تصویر زیر مشاهده می شود، این خط فهرستی از LSMهای فعال (به همراه KRSI که با کلمه ی “bpf” در انتهای رشته نمایش داده شده است) را نشان می دهد:

 


و دستور سوم:

  ↲ cat /boot/config-$(uname -r) | grep -i CONFIG_BPF

که در فهرست چاپ شده باید حتما CONFIG_DEBUG_INFO_BTF=y باشد؛ این خط تضمین می کند که کرنل با نمادهای BPF Type Format (BTF) کامپایل شده است. بسیاری از ابزارهای BPF اکنون به گنجاندن نمادهای BTF در کرنل وابسته هستند. نمادها به تأیید کننده BPF درون کرنل کمک می کنند تا قبل از بارگذاری برنامه، بررسی های ایمنی دسترسی به حافظه را روی برنامه انجام دهد.

 


باینری pahole، بخشی از بسته "dwarves" نیز برای ساختن کرنل لینوکس با نمادهای BTF نیاز به نصب داشت.
سرورها نیز باید به همین ترتیب پیکربندی شوند تا در حد امکان شبیه یکدیگر باشند.
هشت گره ی محاسباتی از یک خوشه HPC برای آزمایش رزرو شدند. انتخاب کرنل با بوت PXE انجام می شود. هر گره محاسباتی یک سیستم فایل ریشه فقط خواندنی را از یک سرور مرکزی NFS نصب می کند.

اسکریپت حمله با مشخصات پایین


این اسکریپت، فعالیتی را شبیه سازی می کند که در آن، اسکریپت های BCC که پیشتر به آن ها اشاره شد، به طور تصادفی هر پانزده ثانیه اجرا شوند. این عملکردهای تصادفی از قرار زیراند:

• یک فایل قابل نوشتن ایجاد شود.
• مجوزهای قابل نوشتن  به یک فایل از پیش موجود اضافه گردد.
• بیت SUID  به یک فایل موجود اضافه شود.
• یک فایل اجرایی SUID اجرا گردد.
• برای باز کردن یک اتصال پروکسی SSH تلاش شود.
• برای ایجاد یک اتصال به یک مقصد راه دور از طریق TCP اقدام گردد.
• برای ایجاد یک اتصال به یک مقصد راه دور از طریق UDP تلاش شود.
• برای خاتمه دادن به اسکریپت های KRSI (با دسترسی root) اقدام شود.

این اسکریپت همه ی کارهای انجام شده را ثبت می کند تا امکان مقایسه بین گزارش‌های اسکریپت حمله و گزارش‌های اسکریپت‌های شناسایی فراهم شود. این اسکریپت در اسکریپت حمله و گزارش های اسکریپت های تشخیص گنجانده شده است.

#!/usr/bin/bash

MIN_SLEEP=1
MAX_SLEEP=15
NONROOT_USER=prbzr 
DMZ_IP=10.1.1.1

create_world_writable_file() {
    local tmpfile="/tmp/${RANDOM:-world_writable}"
    date +"%Y-%m-%d %H:%M:%S type=createwoth comm=touch
user=$NONROOT_USER"
su - $NONROOT_USER -c "umask 0000 && touch $tmpfile >/dev/null 2>&1" && rm -f $tmpfile >/dev/null 2>&1
}

add_world_writable_bit() {
    local tmpfile="$(mktemp)"
    date +"%Y-%m-%d %H:%M:%S type=addwoth comm=chmod user=$NONROOT_USER"
    su - $NONROOT_USER -c "chmod o+w $tmpfile >/dev/null 2>&1"
    rm -f $tmpfile >/dev/null 2>&1
}

add_suid_bit() {
    local tmpfile="$(mktemp)"
    echo 'whoami' > $tmpfile
    chown $NONROOT_USER $tmpfile
    date +"%Y-%m-%d %H:%M:%S type=addsuid comm=chmod user=$NONROOT_USER"
    sudo -u $NONROOT_USER chmod u+s $tmpfile >/dev/null 2>&1
    rm -f $tmpfile >/dev/null 2>&1
}

run_suid() {
    local choice=$(shuf -i 0-1 -n 1)
    case $choice in
        0)
            date +"%Y-%m-%d %H:%M:%S type=runsuid comm=sudo
user=$NONROOT_USER"
            sudo -u $NONROOT_USER sudo id >/dev/null 2>&1
            ;;
        1)
            date +"%Y-%m-%d %H:%M:%S type=runsuid comm=passwd
user=$NONROOT_USER"
            sudo -u $NONROOT_USER passwd -S >/dev/null 2>&1
            ;;
    esac
}

ipv4_connect_attempt() {
    local o1=192
    local o2=168
    local o3=10
    local o4=$(shuf -i 8-23 -n 1)
    local port=$(shuf -i 1-61000 -n 1)

    local choice=$(shuf -i 0-1 -n 1)
    case $choice in
        0)
            # tcp
            date +"%Y-%m-%d %H:%M:%S type=ip4_connect comm=bash
ip=$o1.$o2.$o3.$o4 proto=6 port=$port"
        timeout 15 sudo -u $NONROOT_USER bash -c "echo
>/dev/tcp/$o1.$o2.$o3.$o4/$port 2>/dev/null"
            ;;
        1)
            # udp
            date +"%Y-%m-%d %H:%M:%S type=ip4_connect comm=bash
ip=$o1.$o2.$o3.$o4 proto=17 port=$port"
        timeout 15 sudo -u $NONROOT_USER bash -c "echo
>/dev/udp/$o1.$o2.$o3.$o4/$port 2>/dev/null"
        ;;
    esac
}

ssh_proxy() {
    local choice=$(shuf -i 0-1 -n 1)
    local port=$(shuf -i 10000-60000 -n 1)
case $choice in
    0)
        local port_fwd_opt="-L $port:ubuntu.com:443"
        ;;
    1)
        local port_fwd_opt="-D $port"
        ;;
    esac
    date +"%Y-%m-%d %H:%M:%S type=ssh_proxy comm=ssh
port_fwd_options=$port_fwd_opt"
    sudo -u $NONROOT_USER ssh -n -o ConnectTimeout=5 -o
ConnectionAttempts=1 $port_fwd_opt $DMZ_IP 'id' >/dev/null 2>&1
}

kill_bcc_script() {

    local signo_choice=$(shuf -i 0-1 -n 1)
    local bcc_choice=$(shuf -i 0-4 -n 1)

    case $signo_choice in
    0)
        signo=9
        ;;
    1)
        signo=15
        ;;
    esac

    case $bcc_choice in
        0)
            bcc_script="mac_fileperms"
            ;;
        1)
            bcc_script="mac_suidexec"
            ;;
        2)
            bcc_script="mac_sshlisteners"
            ;;
        3)
            bcc_script="mac_socketconnections"
            ;;
        4)
            bcc_script="mac_killtasks" 
            ;;
    esac

    date +"%Y-%m-%d %H:%M:%S type=kill comm=pkill user=root signo=$signo
target=$bcc_script"
    pkill -f -$signo $bcc_script >/dev/null 2>&1
}

while :
do
    interval=$(shuf -i $MIN_SLEEP-$MAX_SLEEP -n 1)
    sleep $interval || exit 1
    choice=$(shuf -i 0-6 -n 1)
    case $choice in
    0)
        create_world_writable_file
        ;;
    1)
        add_world_writable_bit
        ;;
    2)
        add_suid_bit
        ;;
    3)
        run_suid
        ;;
    4)
        ipv4_connect_attempt
        ;;
    5)
        ssh_proxy
        ;;
    6)
        kill_bcc_script
        ;;
    esac
done
exit 0

پس از آنکه کدها را در فایلی با نام quiet_activity.sh ذخیره کردیم، این حمله را با استفاده از دستورات زیر اجرا می کنیم:
↲  sudo su
 ↲  bash quiet_activity.sh

همانطور که مشاهده می شود، جز در اقدام خاتمه دادن به اسکریپت KRSI که با دسترسی root اجرا می گردد، کاربر prbzr بقیه ی انواع حملات را انجام می دهد:

 


در این آزمایش یک ورک لود جهت اندازه گیریی کارایی یک نود اجرا می شود و سپس کارایی سیستم یکبار در صورت اجرای حمله ی آورده شده در بالا (یعنی با وجود KRSI) و بار دیگر در غیاب حمله (یعنی بدون دخیل بودن KRSI) اندازه گرفته خواهد شد.

آفلاین prbzr

  • Newbie
  • *
  • ارسال: 10
معیار اندازه گیری کارایی در این آزمایش


هنگامی که CPU، حافظه یا دستگاه‌هایIO  مورد مناقشه قرار می‌گیرند، بارهای کاری با افزایش تأخیر، تلفات توان عملیاتی و خطر مرگ OOM (out-of-memory kill) مواجه می‌شوند. بدون اندازه‌گیری دقیق چنین مشاجره‌ای، کاربران مجبور می‌شوند یا با امنیت بازی کنند و از منابع سخت‌افزاری خود کم استفاده کنند، یا تاس بیندازند و اغلب دچار اختلالات ناشی از تعهد بیش از حد شوند. معیار psi اختلالات ناشی از چنین بحران‌های منابع و تأثیر زمانی که بر بارهای کاری پیچیده یا حتی کل سیستم‌ها می‌گذارد را شناسایی و کمیت می‌کند. داشتن اندازه‌گیری دقیق از تلفات بهره‌وری ناشی از کمبود منابع به کاربران کمک می‌کند تا حجم کار را به سخت‌افزار اندازه‌گیری کنند - یا سخت‌افزار را بر اساس تقاضای بار کاری تهیه کنند. از آنجایی کهpsi  این اطلاعات را در زمان واقعی جمع می‌کند، سیستم‌ها را می‌توان به صورت پویا با استفاده از تکنیک‌هایی مانند کاهش بار، انتقال مشاغل به سایر سیستم‌ها یا مراکز داده، یا توقف استراتژیک یا کشتن کارهای دسته‌ای با اولویت پایین یا راه‌اندازی مجدد مدیریت کرد. این امکان به حداکثر رساندن استفاده از سخت افزار را بدون به خطر انداختن سلامت حجم کار یا خطر اختلالات عمده مانند مرگ OOM می دهد.


رابط فشار


اطلاعات فشار برای هر منبع از طریق فایل مربوطه در /proc/pressure/cpu،  و memory و io صادر می شود. قالب به این صورت است:
some avg10=0.00 avg60=0.00 avg300=0.00 total=0
full avg10=0.00 avg60=0.00 avg300=0.00 total=0

خط "some" سهم زمانی را نشان می دهد که در آن حداقل برخی از کارها در یک منبع معین متوقف می شوند.

خط "full" سهم زمانی را نشان می دهد که در آن همه وظایف غیرفعال در یک منبع معین به طور همزمان متوقف می شوند. در این حالت، چرخه‌هایCPU  واقعی به هدر می‌روند و حجم کاری که زمان طولانی را در این حالت صرف می‌کند به‌عنوان thrash در نظر گرفته می‌شود. این تأثیر شدیدی بر عملکرد دارد، و تشخیص این وضعیت از حالتی که برخی از وظایف متوقف شده است اما CPU هنوز در حال انجام کارهای سازنده است مفید است. به این ترتیب، زمان صرف شده در این زیرمجموعه از حالت استال به طور جداگانه ردیابی می شود و در میانگین های "full" صادر می شود.

CPU full  در سطح سیستم تعریف نشده است، اما از نسخه ‌ی 5.13 گزارش شده است، بنابراین برای سازگاری با قبل، روی صفر تنظیم شده است.

نسبت‌ها (بر حسب درصد) به‌عنوان روندهای اخیر در پنجره‌های ده، شصت و سیصد ثانیه‌ای ردیابی می‌شوند، که بینشی در مورد رویدادهای کوتاه‌مدت و همچنین روندهای میان‌مدت و بلندمدت می‌دهد. کل زمان توقف مطلق (us) نیز ردیابی و صادر می‌شود، تا امکان تشخیص جهش‌های تأخیر را فراهم کند که لزوماً در میانگین‌های زمانی یا میانگین روندها در بازه‌های زمانی سفارشی کاهش نمی‌یابد.

یا استفاده از اسکریپت زیر مقدار PSI برای CPU، Memory و IO  یکبار با وجود عملکرد KRSI و بار دیگر در غیاب عملکرد KRSI اندازه گیری شده است:

import numpy as np
import matplotlib.pyplot as plt
import re
import time
from statistics import mean
item='io'

param1="avg10"
param2="avg60"
param3="avg300"

condition="Under Low-Profile Attack"

spectParam1=[]
spectParam2=[]
spectParam3=[]

# function to add value labels
def addlabels(x,y):
    for i in range(len(x)):
        plt.text(i,round(y[i],6),round(y[i],6))


iteration= range(300)

for i in iteration:

 meminfo=open('/proc/pressure/'+item,'rt').readlines()
 p = re.compile('some avg10=(\d+.\d{2}) avg60=(\d+.\d{2}) avg300=(\d+.\d{2}) total=\d+\n')
 spectParam1.append(float(p.match(meminfo[0]).group(1)))
 spectParam2.append(float(p.match(meminfo[0]).group(2)))
 spectParam3.append(float(p.match(meminfo[0]).group(3)))
 time.sleep(1);

average1 = mean(spectParam1)
average2 = mean(spectParam2)
average3 = mean(spectParam3)

names=[param1,param2,param3]
values=[average1,average2,average3]


plt.figure(figsize=(10, 10))
addlabels(names, values)

plt.bar(names, values,color="red")
plt.xlabel("Averages (x Second Windows)")
plt.ylabel("Stall (Percentage)")

plt.suptitle(item +' Pressure Stall Information | '+condition)
plt.show()

"""
time = np.arange(0, len(iteration), 1)
fig, ax = plt.subplots()
line, = ax.plot(time, spectParam1, label=param1)


plt.title('meminfo Tracing')
plt.legend()
plt.show() """


آزمایش – cpu Pressure Stall Information




آزمایش – io Pressure Stall Information





آزمایش – memory Pressure Stall Information





مقایسه نتایج



همانطور که مشاهده می شود برای IO و Memory به اندازه ی +0.17 درصد و برای CPU به اندازه ی +0.23 درصد افت کارایی گزارش شده است. این نتیجه نشان می دهد که KRSI می تواند یک ابزار امنیتی بسیار مناسب برای استفاده در سایت های پردازشی با کارایی بالا محسوب شود بدون آنکه افت قابل توجهی در کارایی سیستم ایجاد نماید.

توسط پوریا بازیار
زیر نظر جناب آقای دکتر سید وحید ازهری