روابط عمومی شرکت ایدکو (توزیعکنندهی محصولات کسپرسکی در ایران)؛ با وجود آنکه کانتینرها محیطی ایزوله برای اجرای برنامهها فراهم میکنند، این ایزولهسازی در بسیاری از موارد بیش از حد ارزیابی میشود. کانتینرها با بستهبندی وابستگیها و ایجاد یک بستر ثابت برای اجرا، مزایای زیادی دارند؛ اما استفاده مشترک از هسته سیستمعامل میزبان، تهدیدات امنیتی جدی به همراه دارد. بر اساس تجربیات ما در ارائه خدمات ارزیابی نفوذ، مشاوره مرکز عملیات امنیت (SOC) و پاسخگویی به رخدادهای امنیتی، بارها با مشکلاتی در حوزه فقدان دید امنیتی نسبت به کانتینرها مواجه شدهایم. بسیاری از سازمانها، تمرکز خود را بر نظارت عملیاتی کانتینرها گذاشتهاند و توجه کمتری به تهدیدات امنیتی دارند. برخی نیز فاقد تخصص لازم برای پیکربندی صحیح لاگها هستند یا از پشتههای فناوری استفاده میکنند که توانایی ارائه دید مناسب نسبت به وضعیت کانتینرهای در حال اجرا را ندارند.
چنین محیطهایی که از مشکل ضعف «دید امنیتی» رنج میبرند، کار تحلیلگران تهدید و تیمهای پاسخگویی به حوادث را بسیار دشوار میسازند؛ زیرا در چنین شرایطی، تشخیص اینکه یک فرایند درون کانتینر اجرا شده یا مستقیماً روی میزبان فعال بوده، بهراحتی امکانپذیر نیست. این ابهام، تعیین منشأ واقعی حمله را پیچیده میکند و مشخص کردن اینکه نفوذ از کانتینر آغاز شده یا مستقیماً میزبان را هدف قرار داده، دشوار میشود. در این مقاله، قصد داریم توضیح دهیم که چگونه میتوان زنجیره اجرای فرایندها درون یک کانتینر در حال اجرا را تنها با استفاده از لاگهای اجرایی میزبان بازیابی کرد؛ موضوعی حیاتی که میتواند به تحلیلگران تهدید و تیمهای پاسخگویی در تعیین علت اصلی نفوذ کمک شایانی کند.
برای بررسی مؤثر رخدادهای امنیتی و شکار تهدیدها در محیطهای کانتینری، ابتدا باید دقیقاً بدانیم کانتینرها چگونه ساخته میشوند و چگونه کار میکنند. برخلاف ماشینهای مجازی که هرکدام سیستمعامل جداگانهای دارند، کانتینرها محیطهایی ایزولهشده در سطح فضای کاربریهستند که هسته سیستمعامل میزبان را به اشتراک میگذارند. این ایزولاسیون با استفاده از قابلیتهایی مانند namespaceها، cgroupها، فایلسیستمهای ترکیبی، قابلیتهای لینوکس و سایر ویژگیهای لینوکس پیادهسازی میشود.
به همین دلیل، هر پردازشی که داخل یک کانتینر اجرا میشود، در واقع روی میزبان اجرا شده، اما در یک فضای نام مجزا قرار دارد. تحلیلگران تهدید و پاسخدهندگان به رخدادها معمولاً به لاگهای اجرای روی میزبان متکی هستند تا بفهمند چه فرآیندهایی اجرا شده و چه دستوراتی وارد شدهاند. این روش در شرایطی که ابزارهای نظارتی مخصوص کانتینر وجود ندارد، دید خوبی از آنچه گذشته فراهم میکند. با این حال، گاهی اوقات برخی تنظیمات لاگ ممکن است اطلاعات حیاتی مانند namespace، cgroup یا syscallها را ثبت نکنند. در چنین شرایطی، به جای اینکه صرفاً به این لاگهای ناقص اکتفا کنیم، میتوان با درک زنجیره اجرای پردازشها در کانتینر — از دید میزبان — این شکاف اطلاعاتی را پر کرد. کاربران نهایی از ابزارهای خط فرمان مانند Docker CLI، kubectl و سایر ابزارها برای ایجاد و مدیریت کانتینرها استفاده میکنند. در پشت صحنه، این ابزارها با یک موتو) ارتباط برقرار میکنند که نقش واسطه را بین کاربر و یکزمان اجرایسطح بالامانند containerd یا CRI-O ایفا میکند. این زمان اجراهای سطح بالا خود به زمان اجراهای سطح پایینتری مانند runc که رایجترین آنهاست متکی هستند تا کارهای اصلی و ارتباط با هسته لینوکسرا انجام دهند.
این ارتباط شامل تخصیص منابعی مانند cgroups، namespaces و سایر قابلیتهای لینوکس برای ایجاد یا از بین بردن کانتینرها میشود. این اقدامات بر اساس یک بستهیا اصطلاحاً باندلانجام میشود که توسطزمان اجرایسطح بالا و با توجه به آرگومانهای ارائهشده توسط کاربر تهیه میشود. این باندلدر واقع یک پوشهی مستقل و کامل است که پیکربندی کانتینر را مطابق با استاندارد OCI Runtime Specificationتعریف میکند. این بسته معمولاً شامل دو بخش اصلی است:
- دایرکتوری rootfs: که به عنوان سیستم فایل ریشهی کانتینر عمل میکند. این دایرکتوری با استخراج و ترکیب لایههای تصویر کانتینر ساخته میشود؛ معمولاً با استفاده از یک فایلسیستم ترکیبی مانند OverlayFS.
- فایل config.json: که پیکربندی زمان اجرای OCI را توصیف میکند و شامل اطلاعاتی دربارهی فرآیند اجرا، mountها و سایر تنظیمات لازم برای ایجاد کانتینر است.
نکتهی مهم در استفاده از runc این است که این ابزار دارای دو حالت اجرای متفاوت است: حالت Foregroundو حالت Detached. ساختار نهایی درخت فرآیندها بسته به حالتی که runc در آن اجرا شده متفاوت خواهد بود. در حالت Foreground، فرآیند runc در پیشزمینه باقی میماند و به عنوان والد اصلی فرآیند کانتینر عمل میکند؛ بهویژه برای مدیریت ورودی و خروجی (stdio) بهمنظور تعامل مستقیم کاربر با کانتینر در حال اجرا. در حالت Detached، برخلاف حالت Foreground، دیگر خبری از یک فرآیند بلندمدت runc نیست. بهمحض اینکه runc کانتینر را ایجاد میکند، از اجرای خود خارج میشود و مسئولیت مدیریت ورودی/خروجی (stdio) به فرآیند فراخوانواگذار میشود؛ که در بیشتر موارد، این فرآیند یکی از زماناجراهای سطح بالا مانند containerd یا CRI-O است. وقتی با استفاده مستقیم از runc یک کانتینر را در حالت Detached اجرا میکنیم، runc آن را ایجاد کرده و بلافاصله خارج میشود. در نتیجه، والد،فرآیند کانتینر، فرآیند شماره ۱ سیستم (systemd) خواهد بود. اما اگر همین کار را با استفاده از Docker CLI انجام دهیم، میبینیم که والد فرآیند کانتینر دیگر PID 1 نیست، بلکه یک shim process است. در معماریهای مدرن، ارتباط بین Runtimeهای سطح بالا و سطح پایین از طریق یک فرآیند واسط به نام shim انجام میشود. این فرآیند واسط این امکان را فراهم میکند که کانتینرها مستقل اززمان اجرای سطح بالا اجرا شوند. یعنی حتی اگر containerd یا CRI-O از کار بیفتد یا ریاستارت شود، کانتینر به کار خود ادامه میدهد.
shim علاوه بر این، مسئول مدیریت stdio کانتینر است، بنابراین کاربران میتوانند بعدها به کانتینر در حال اجرا متصل شوند؛ مثلاً با استفاده از دستور docker exec -it <container>. همچنین، shim میتواند خروجیها (stdout و stderr) را به فایلهای لاگ هدایت کند، که بعداً از طریق فایلسیستم یا دستوراتی مثل kubectl logs <pod> -c <container>قابل مشاهده هستند. وقتی با Docker CLI یک کانتینر را در حالت Detached ایجاد میکنیم، زمان اجرای سطح بالامثلاً containerd یک فرآیند shim اجرا میکند که وظیفهاش تنها اجرای runc برای ساخت کانتینر است. runc پس از ساخت کانتینر بلافاصله خارج میشود. برخلاف حالتی که خودمان مستقیماً runc را اجرا میکردیم که در آن صورت فرآیند کانتینر به PID 1 منتقل میشد، در اینجا shim خودش را به عنوان یک subreaper ثبت میکند تا فرآیندهای فرزند (کانتینر) یتیم نشده و به درستی مدیریت شوند.
در لینوکس، یک subreaper فرآیندی است که میتواند بهجای init (PID 1) وظیفهی سرپرستی فرآیندهای یتیم را برعهده بگیرد. این ویژگی به shim اجازه میدهد تا درخت فرآیندهای خود را به خوبی مدیریت کرده و در پایان، پاکسازی کند. این قابلیت در نسخهی جدید shim V2 پیادهسازی شده و در پیادهسازیهای مدرن containerd بهصورت پیشفرض فعال است. اگر به پیام راهنمای (help message) فرآیند containerd-shim-runc-v2نگاه کنیم، متوجه میشویم که این برنامه شناسهی کانتینررا بهعنوان یکی از آرگومانهای خط فرمان میپذیرد، که از آن با عنوان id of the task یاد میکند. برای تأیید این موضوع، میتوانیم آرگومانهای خط فرمان مربوط به فرآیندهای containerd-shim-runc-v2در حال اجرا را بررسی کرده و آنها را با کانتینرهای فعال مقایسه کنیم.
تا اینجا توانستهایم فرآیندهای درون کانتینر را از دید میزبانشناسایی کنیم. در معماریهای مدرن، معمولاً یکی از دو فرآیند زیر بهعنوان والد (پیشنیاز) فرآیندهای کانتینری دیده میشود:
- یک فرآیند shim، در حالتی که کانتینر در حالت Detached اجرا شده باشد.
- یک فرآیند runc، در حالتی که کانتینر به صورت Interactive در حالت Foreground اجرا شده باشد.
همچنین میتوان از آرگومانهای خط فرمان فرآیند shim برای تشخیص اینکه کدام فرآیند متعلق به کدام کانتینر است، استفاده کرد. با اینکه دنبال کردن فرآیندهای فرزند یک shim گاهی میتواند سریع ما را به هدف برساند، اما این کار همیشه ساده نیست — مخصوصاً وقتی تعداد زیادی فرآیند واسط بین shim و فرآیند نهایی (مثلاً یک فرآیند مخرب) وجود دارد. در چنین شرایطی، میتوانیم از رویکرد پایینبهبالا استفاده کنیم: از فرآیند مشکوک یا مخرب شروع کرده، سلسلهمراتب والدهای آن را دنبال کنیم تا به فرآیند shim برسیم. این کار به ما کمک میکند تأیید کنیم که فرآیند مورد نظر در یک کانتینر در حال اجراست. پس از آن، تصمیم میگیریم که کدامیک از فرآیندها را برای بررسی رفتارهای مشکوک یا مخرب، دقیقتر تحلیل کنیم. از آنجایی که کانتینرها معمولاً با حداقل وابستگیها اجرا میشوند، مهاجمان اغلب سعی میکنند با دسترسی به Shell، دستورات را مستقیم اجرا کنند یا وابستگیهای موردنیاز بدافزار خود را نصب نمایند. به همین دلیل، شِلهای داخل کانتینر نقطهای بسیار مهم برای شناسایی رفتارهای مخرب هستند. اما دقیقاً فرآیندهای shell در محیطهای کانتینری چه رفتارهایی دارند؟
بیایید نگاه دقیقتری به یکی از فرآیندهای کلیدی shell در این محیطها بیندازیم.
نحوه اجرای دستورات در BusyBox و Alpine
در این گزارش، رفتار کانتینرهای مبتنی بر BusyBox مورد بررسی قرار گرفته است. همچنین از کانتینرهای مبتنی بر Alpine بهعنوان نمونهای از تصاویر پایهای که برای پیادهسازی بسیاری از ابزارهای اصلی لینوکس به BusyBox متکی هستند، استفاده شده است. هدف از این کار، نشان دادن نحوه عملکرد این نوع کانتینرها در اجرای دستورات با حفظ سبکی و کمحجم بودن است. لازم به ذکر است که تصاویر Alpine که به ابزارهای غیر ازBusyBox وابسته هستند، از محدوده این بررسی خارج شدهاند.
BusyBox جایگزینهایی مینیمالیستی برای بسیاری از ابزارهای رایج یونیکس ارائه میدهد و آنها را در قالب یک فایل اجرایی کوچک ترکیب میکند. این رویکرد امکان ایجاد کانتینرهای سبک با اندازه تصویر بسیار کاهشیافته را فراهم میسازد. اما فایل اجرایی BusyBox چگونه عمل میکند؟
BusyBox دارای پیادهسازی اختصاصی خود از ابزارهای سیستمی است که تحت عنوان «اپلت»شناخته میشوند. هر اپلت به زبان C نوشته شده و بهعنوان بخشی از کد منبع، در مسیر busybox/coreutils/ذخیره میشود. بهعنوان مثال، ابزار catدر یونیکس دارای پیادهسازی اختصاصی با نام cat.cدر این مسیر است. در زمان اجرا، BusyBox یک جدول اپلت ایجاد میکند که نام هر اپلت را به تابع متناظر آن نگاشت میکند. این جدول برای تعیین اینکه کدام اپلت باید اجرا شود، بر اساس آرگومان خط فرمان ورودی، مورد استفاده قرار میگیرد. این سازوکار در فایل appletlib.cتعریف شده است.
نحوه اجرای دستورات غیر اپلت در BusyBox و بررسی رفتار آن در کانتینر
زمانی که یک دستور اجرا شده درون کانتینر، ابزاری را فراخوانی میکند که در میان اپلتهای پیشفرض BusyBox وجود ندارد، BusyBox از متغیر محیطی PATHبرای یافتن محل ابزار مورد نظر استفاده میکند. پس از شناسایی مسیر، BusyBox آن ابزار را بهصورت یک فرآیند فرزند از فرآیند BusyBox اجرا میکند. این سازوکار پویا در اجرای دستورات، برای درک کامل نحوه عملکرد BusyBox در محیط کانتینر حیاتی است. اکنون که با نحوه عملکرد فایل اجرایی BusyBox آشنا شدیم، به بررسی رفتار آن درون یک کانتینر میپردازیم. بهعنوان نمونه، هنگام اجرای دستور shدر چنین کانتینرهایی چه اتفاقی رخ میدهد؟
اجرای sh در کانتینرهایBusyBox و Alpine
در هر دو نوع کانتینر، اجرای دستور shبرای دسترسی به محیط shell، در واقع یک فایل باینری مستقل به نام shرا فراخوانی نمیکند. بلکه همان فایل اجرایی BusyBoxاجرا میشود.
در کانتینرهای BusyBox:
- فایل /bin/shدر واقع همان فایل اجرایی /bin/busyboxاست.
- این موضوع را میتوان با دستور ls -liبررسی کرد. این دستور شماره inode فایلها را نمایش میدهد. اگر شماره inode هر دو فایل یکسان باشد، مشخص است که هر دو به یک فایل فیزیکی اشاره دارند.
- همچنین میتوان با محاسبه مقدار MD5 hashاین دو فایل، یکسان بودن آنها را تأیید کرد.
- اجرای دستور /bin/sh --helpنیز بنری با نام BusyBox را نمایش میدهد که نشان میدهد BusyBox مستقیماً اجرا شده است.
- فایل /bin/shبه صورت یک لینک نمادینبه /bin/busyboxتعریف شده است.
- بنابراین، اجرای دستور shدر واقع منجر به اجرای فایل BusyBox از طریق این لینک میشود.
- این موضوع را میتوان با اجرای دستور readlink -f /bin/shتأیید کرد که مسیر نهایی فایل لینکشده را نشان میدهد.
در کانتینرهای Alpine:
در نتیجه، چه در کانتینرهای BusyBox و چه در Alpine، اجرای بسیاری از دستورات — از جمله sh — در واقع به معنای اجرای فایل اجرایی BusyBox است. این ساختار فشرده و مجتمع یکی از دلایل اصلی سبکوزن بودن این کانتینرها و محبوبیت آنها در محیطهای محدود منابع بهشمار میرود.
اجرای دستورات در کانتینرهای BusyBox و Alpine و پیامدهای امنیتی آن
در کانتینرهای مبتنی بر BusyBox یا Alpine، تمامی فرمانهای پوسته یا بهصورت مستقیم توسط فرآیند BusyBox اجرا میشوند، یا بهعنوان فرآیندهای فرزند تحت مدیریت BusyBoxراهاندازی میگردند. این فرآیندها در فضای نام جداگانه اجرا میشوند که بر بستر هسته مشترک سیستمعامل میزبان عمل میکند و بدین شکل کانتینرسازیرا فراهم میسازد.
پیامدهای امنیتی و تحلیل از دید شکار تهدید
از منظر امنیتی، وجود فرآیندهای پوستهای غیرمعمول در سیستمعامل میزبان — مانند فرآیند BusyBox در یک سیستم مبتنی بر Debian یا RedHat — میتواند نشانهای از فعالیت مشکوک باشد که نیاز به بررسی بیشتر دارد. چرا باید یک پوسته BusyBox روی سیستمعاملی که به طور طبیعی از bash یا dash استفاده میکند، فعال باشد؟ با ترکیب این یافته با اطلاعات قبلی، میتوان تأیید کرد که اگر فرآیند والدیک فرآیند runcیا shimباشد، بهاحتمال زیاد BusyBox در درون یک کانتینر اجرا شده است. این شناخت را میتوان برای تشخیص منشأ هر فرآیند دیگری که داخل کانتینر اجرا میشود نیز به کار گرفت.
اهمیت این دانش در تحلیل رفتارهای مشکوک
توانایی تشخیص اینکه یک فرآیند در درون یک کانتینر اجرا شده، برای تحلیلگران امنیتی و شکارچیان تهدید حیاتی است؛ چراکه کمک میکند رفتارهای غیرعادی را از عملیات عادی میزبان تفکیک کنند. این موضوع بهویژه هنگام تحلیل لاگهای اجرای فرآیندهادر سیستمهای لینوکسی اهمیت دارد. برای نمونه، نصب ابزارهایی مانند Docker CLI در سیستم میزبان رفتاری طبیعی تلقی میشود، اما انجام همین کار در درون یک کانتینر غیرمعمول و مشکوک است. در یکی از پروژههای «ارزیابی دستکاری» مشخص شد که عامل تهدید برای اجرای عملیات استخراج رمزارز، ابزار Docker CLI را درون یک کانتینر نصب کرده تا مستقیماً با APIهای dockerd ارتباط برقرار کند. این اقدام با هدف کنترل راحتتر منابع و فرار از نظارت انجام شده بود.
ابزارهای امنیتی و چالشها
برخی ابزارهای امنیتی مانند Kaspersky Container Security بهطور خاص برای پایش فعالیتهای داخل کانتینر طراحی شدهاند و قادر به شناسایی رفتارهای مشکوک هستند. از سوی دیگر، ابزارهایی مانند Auditdلاگهای سطح کرنل را با استفاده از قوانین از پیش تعریفشده جمعآوری میکنند؛ شامل فراخوانیهای سیستمی، دسترسی به فایلها و فعالیتهای کاربر. اما باید توجه داشت که قوانین اینگونه ابزارها معمولاً برای محیطهای سنتی طراحی شدهاند و بهینهسازی نشدهاند تا فعالیتهای کانتینری را از فعالیتهای میزبان تفکیک کنند. این موضوع کار تحلیلگران را در شناسایی دقیق رفتارهای تهدیدآمیز دشوارتر میسازد. در جریان یکی دیگر از تحقیقات، با رویدادی جالب و مشکوک مواجه شدیم؛ نام فرآیند systemdبود، اما مسیر فایل اجرایی آن بهجای مسیرهای رایج، به شکل غیرمعمولی برابر با /.redtailتعیین شده بود. برای شناسایی منشأ این فرآیند، از همان روش پیشین استفاده کردیم: ردگیری زنجیره والدینتا رسیدن به ریشه اصلی اجرا.
این عدم تطابق بین نام فرآیند و مسیر اجرایی آن، یکی از نشانههای کلاسیک استتار بدافزارهامحسوب میشود؛ جایی که عامل تهدید سعی میکند با تقلید از فرآیندهای سیستمی شناختهشده، در محیط میزبان پنهان بماند.نکته مهمی که میتوانیم از آن بهره ببریم این است که هر کانتینر داکر همواره توسط یک فرآیند runcایجاد میشود که بهعنوان موتور اجرایی سطح پایین کانتینر عمل میکند. پیام راهنمای runc، آرگومانهای خط فرمانی که برای ایجاد، اجرا یا راهاندازی کانتینر استفاده میشوند را نمایش میدهد.پیگیری این رویدادها به شکارچیان تهدید و پاسخدهندگان به رخدادها کمک میکند تا شناسه (ID) کانتینر هدف را شناسایی و هرگونه نقطه ورود غیرعادیرا کشف کنند. نقطه ورود کانتینر، فرآیند اصلی آن است که توسط runc ایجاد میشود.
نتیجهگیری
امروزه محیطهای کانتینری بخش جداییناپذیری از شبکههای اکثر سازمانها شدهاند، چرا که امکان استقرار ساده و بستهبندی وابستگیها را فراهم میکنند. اما متأسفانه اغلب توسط تیمهای امنیتی و تصمیمگیرندگان نادیده گرفته میشوند، بهدلیل درک نادرستی که از ایزولاسیون کانتینرها وجود دارد. این موضوع میتواند به بروز شرایط نامطلوب منجر شود، بهخصوص زمانی که این کانتینرها دچار نفوذ شوند و تیم امنیتی دانش یا ابزارهای کافی برای واکنش به موقع، پایش یا حتی شناسایی اولیه تهدیدها را نداشته باشد. روشی که در این مطلب مطرح شد، یکی از راهکارهایی است که ما معمولاً در خدمات ارزیابی نفوذ و پاسخ به رخدادها به کار میبریم، زمانی که قصد داریم تهدیدات را در لاگهای تاریخی اجرای میزبان که از دید کانتینری مشکل دارند، شناسایی کنیم. با این حال، برای شناسایی به موقع تهدیدات مبتنی بر کانتینر، ضروری است که سیستمهای خود را با راهکارهای قوی پایش کانتینری، مانند Kaspersky Container Security، مجهز و محافظت کنید.
کسپرسکی آنلاین (ایدکو)
کسپرسکی اسم یکی از بزرگترین شرکتهای امنیتی و سازنده آنتی ویروس است که برخی از کاربران اشتباهاً این شرکت و محصولات آنتی ویروس آن را با عناوینی نظیر کسپرسکای،کاسپرسکی، کسپراسکای، کسپراسکای، و یا کاسپراسکای نیز میشناسد. همچنین لازم به ذکر است مدیرعامل این شرکت نیز یوجین کسپرسکی نام دارد.