سرریز بافر زمانی اتفاق میافتد که یک برنامه یا فرآیند تلاش میکند تا حجم دادههای بیشتری را نسبت به ظرفیت بافری که برای نگهداری آن اختصاص داده شده است در یک بلوک حافظه با طول ثابت، یا همان بافر بنویسد. بافرها حاوی مقدار مشخصی از دادهها هستند. هر داده اضافی، مقادیر داده را در آدرسهای حافظه مجاور بافر مقصد بازنویسی میکند. اگر برنامه شامل بررسی محدودههای کافی برای پرچم گذاری یا حذف دادهها در هنگام ارسال بیش از حد به بافر حافظه باشد، میتوان از این نوع سرریز جلوگیری کرد.
حمله سرریز بافر چیست و چگونه کار میکند؟
بهره برداری از سرریز بافر به مهاجم اجازه میدهد تا یک فرآیند را کنترل یا خراب کند یا متغیرهای داخلی آن را تغییر دهد. سرریز بافر همیشه در فهرست نقاط ضعف رایج (CWE) و ۲۵ خطای خطرناک نرمافزار SANS در رتبه بالایی قرار دارد. یک سرریز بافر کلاسیک به عنوان CWE-120 در فرهنگ لغت انواع ضعف CWE مشخص شده است. علیرغم اینکه این مسئله به خوبی درک شده است، سرریزهای بافر همچنان باعث ایجاد مشکل در نرم افزارهای فروشندگان بزرگ و کوچک میشود.
سرریز بافر میتواند به طور سهوی یا زمانی که یک عامل مخرب باعث آن شود، رخ دهد. یک عامل تهدید میتواند ورودی با دقت ساخته شده را – که به عنوان کد دلخواه شناخته میشود – به یک برنامه ارسال کند. برنامه سعی میکند ورودی را در بافری ذخیره کند که اندازهاش برای ورودی کافی نیست. اگر دادههای اضافی روی حافظه مجاور نوشته شود، هر دادهای را که قبلا وجود داشته است، بازنویسی میکند.
دادههای اصلی در بافر شامل نشانگر بازگشت تابع مورد سوء استفاده یا همان آدرسی است که فرآیند باید در مرحله بعدی به آن برود. با این حال، مهاجم میتواند مقادیر جدیدی را برای اشاره به آدرس مورد نظر خود تنظیم کند. مهاجم معمولا مقادیر جدید را در مکانی قرار میدهد که فایلهای بهره برداری در آن قرار دارد. این تغییر، مسیر اجرای فرآیند را تغییر میدهد و کنترل را به کد مخرب مهاجم منتقل میکند.
برای مثال، فرض کنید برنامهای منتظر است تا کاربران نام خود را وارد کنند. به جای وارد کردن نام، هکر یک دستور اجرایی را وارد میکند که از اندازه پشته بیشتر است. دستور معمولا چیزی کوتاه است. به عنوان مثال، در یک محیط لینوکس، دستور معمولا EXEC(“sh”) است، که به سیستم میگوید یک پنجره خط فرمان را باز کند که به عنوان root shell در حلقههای لینوکس شناخته میشود.
با این حال، سرریز کردن بافر با یک دستور اجرایی به این معنی نیست که دستور حتما اجرا خواهد شد. مهاجم باید یک آدرس بازگشتی که به دستور مخرب اشاره میکند را مشخص کند. در این حالت کارکرد برنامه به دلیل سرریز شدن پشته تا حدی مختل میشود. سپس سعی میکند با رفتن به آدرس برگشتی بازیابی شود، اما آدرس برگشتی به دستوری که هکر مشخص کرده است تغییر داده شده است. هکر باید آدرسی که دستور مخرب در آن قرار دارد را بداند.
برای این که نیاز به دانستن آدرس واقعی دور زده شود، فرمان مخرب اغلب توسط دستورالعملهای کامپیوتری NOP، نوعی اشارهگر، در دو طرف قرار میگیرد. Padding در هر دو طرف تکنیکی است که در مواقعی که محدوده دقیق حافظه ناشناخته است استفاده میشود. اگر آدرسی که هکر مشخص میکند در هر جایی از padding قرار گیرد، دستور مخرب اجرا میشود.
زبانهای برنامه نویسی مانند C و C++ هیچ حفاظتی در برابر دسترسی یا بازنویسی دادهها در هیچ بخشی از حافظه خود ندارند. در نتیجه، آنها در برابر حملات اضافه بار بافر آسیب پذیر هستند. در این حالت هکرها میتوانند با ساختارهای برنامه نویسی رایج دستکاری مستقیم حافظه را انجام دهند.
زبانهای برنامه نویسی مدرن مانند سی شارپ، جاوا و پرل احتمال خطاهای کدنویسی ایجاد آسیب پذیریهای سرریز بافر را کاهش میدهند. با این وجود، سرریز بافر میتواند در هر محیط برنامه نویسی که در آن دستکاری مستقیم حافظه مجاز است اتفاق بیفتد، چه از طریق نقص در کامپایلر برنامه، کتابخانههای زمان اجرا یا ویژگیهای خود زبان.
انواع حملات سرریز بافر
تکنیکهای بهره برداری از آسیب پذیریهای سرریز بافر بر اساس سیستم عامل (OS) و زبان برنامه نویسی متفاوت است. با این حال، هدف همیشه دستکاری حافظه رایانه برای منحرف کردن یا کنترل اجرای برنامه است. سرریزهای بافر بر اساس محل بافر در حافظه پردازشی دسته بندی میشوند. آنها عمدتا سرریزهای مبتنی بر stack یا سرریزهای مبتنی بر heap هستند. هر دو در حافظه دسترسی تصادفی (RAM) دستگاه قرار دارند. برخی از انواع حملات سرریز بافر شامل موارد زیر است:
سرریز بافر مبتنی بر stack یا حمله بیش از حد بافر stack
پشته دادهها را در یک ساختار آخرین ورودی و اولین خروجی نگهداری میکند. این یک فضای پیوسته در حافظه است که برای سازماندهی دادههای مرتبط با فراخوانی تابع، از جمله پارامترهای تابع، متغیرهای محلی تابع و اطلاعات مدیریتی، مانند فریم و نشانگرهای دستورالعمل، استفاده میشود. به طور معمول، پشته خالی است تا زمانی که برنامه مورد نظر به ورودی کاربر، مانند نام کاربری یا رمز عبور نیاز داشته باشد. در آن مرحله، برنامه یک آدرس حافظه بازگشتی به پشته مینویسد و سپس ورودی کاربر در بالای آن قرار میگیرد. هنگامی که پشته پردازش میشود، ورودی کاربر به آدرس بازگشتی مشخص شده توسط برنامه ارسال میشود.
با این حال، یک پشته دارای اندازه محدود است. برنامه نویسی که کد را توسعه میدهد باید فضای خاصی را برای پشته رزرو کند. اگر ورودی کاربر بیشتر از مقدار فضای ذخیره شده برای آن در پشته باشد و برنامه تأیید نکند که ورودی مناسب است، پشته سرریز خواهد شد. این به خودی خود مشکل بزرگی نیست، اما زمانی که با ورودیهای مخرب ترکیب شود، به یک حفره امنیتی بزرگ تبدیل میشود.
حمله سرریز بافر مبتنی بر هیپ
هیپ یک ساختار حافظه است که برای مدیریت حافظه پویا استفاده میشود. برنامه نویسان اغلب از هیپ برای تخصیص حافظهای استفاده میکنند که اندازه آن در زمان کامپایل مشخص نیست، در جایی که مقدار حافظه مورد نیاز آنقدر زیاد است که در پشته جا نمی شود یا حافظه برای استفاده در فراخوانی تابع در نظر گرفته شده است. حملات مبتنی بر هیپ فضای حافظهای را که برای یک برنامه یا فرآیند در نظر گرفته شده است، پر میکند. آسیبپذیریهای مبتنی بر هیپ، مانند باگ روز صفر که در اوایل سال جاری در Google Chrome کشف شد، به سختی قابل بهرهبرداری هستند، بنابراین از حملات پشتهای نادرتر هستند.
حمله سرریز عدد صحیح
اکثر زبانهای برنامه نویسی حداکثر اندازه را برای اعداد صحیح تعریف میکنند. هنگامی که از این اندازهها تجاوز میشود، نتیجه ممکن است باعث خطا شود یا ممکن است نتیجه نادرستی در محدوده طول اعداد صحیح نشان دهد. حمله سرریز اعداد صحیح زمانی رخ میدهد که یک عدد صحیح در یک عملیات حسابی استفاده شود و نتیجه محاسبه مقداری بیش از حداکثر اندازه عدد صحیح باشد. به عنوان مثال برای ذخیره عدد ۱۹۲ به ۸ بیت حافظه نیاز است. اگر فرآیند ۶۴ را به این عدد اضافه کند، پاسخ ۲۵۶ در حافظه اختصاص داده شده جای نمیگیرد، زیرا به ۹ بیت نیاز دارد.
حمله رشتههای قالب
مهاجمان با استفاده نادرست از توابع کتابخانه قالببندی رشتهای، مانند printf و sprintf، برای دسترسی و دستکاری سایر فضاهای حافظه، نحوه جریان کاری برنامه را تغییر میدهند.
حملات سرریز یونیکد
این حملات از حافظه بیشتر مورد نیاز برای ذخیره یک رشته در قالب یونیکد نسبت به کاراکترهای کد استاندارد آمریکایی برای تبادل اطلاعات (ASCII) استفاده میکنند. آنها را میتوان در برابر برنامههایی استفاده کرد که انتظار دارند تمام ورودیها از نویسههای ASCII باشد.
چگونه از حملات سرریز بافر جلوگیری کنیم
چندین راه برای جلوگیری از وقوع حملات سرریز بافر وجود دارد، از جمله پنج مورد زیر:
۱) از حفاظتهای زمان اجرا سیستم عامل استفاده کنید. اکثر سیستمعاملها از حفاظت در زمان اجرا مانند موارد زیر استفاده میکنند، تا موفقیت حملات سرریز بافر را سختتر کنند:
- تصادفیسازی طرحبندی فضای آدرس، یا ASLR، بهطور تصادفی موقعیتهای فضای آدرس حوزههای داده کلیدی یک فرآیند را مرتب میکند. این شامل پایه فایل اجرایی و موقعیتهای پشته، هیپ و کتابخانهها میشود. این رویکرد پرش قابل اعتماد به یک عملکرد خاص در حافظه را برای مهاجم دشوار میکند.
- Data Execution Prevention مناطقی از حافظه را بهعنوان اجرایی یا غیرقابل اجرا علامتگذاری میکند. این امر مانع از آن میشود که مهاجم قادر به اجرای دستورالعملهای نوشته شده در یک منطقه داده از طریق سرریز بافر باشد.
- حفاظت از بازنویسی کنترل کننده استثناهای ساختاریافته برای جلوگیری از حملاتی طراحی شده است که از تکنیک بازنویسی با کنترل کننده استثنای ساختاریافته استفاده میکنند، که شامل استفاده از سرریز بافر مبتنی بر پشته است.
۲) دستگاهها را بهروز نگاه دارید. فروشندگان وصلهها و بهروزرسانیهای نرمافزاری را برای رفع آسیبپذیریهای سرریز بافر که کشف شدهاند، منتشر میکنند. بین کشف آسیبپذیری و ایجاد و استقرار پچ هنوز یک دوره ریسک وجود دارد.
۳) از اصل حداقل امتیاز (POLP) پیروی کنید. به کاربران و برنامههای کاربردی فقط باید مجوزهایی داده شود که برای انجام کارهای خود یا انجام وظایف محول شده به آنها نیاز دارند. پیروی از رویکرد POLP احتمال وقوع حمله سرریز بافر را کاهش میدهد. در مثال حمله سرریز پشته در بالا، پنجره خط فرمان که باز شده است با همان مجموعه مجوزهایی اجرا میشود که برنامه مورد حمله اجرا شده است. برنامه مورد حمله هر چه امتیازات کمتری داشته باشد، مهاجم کمتری خواهد داشت. در صورت امکان، فقط به کاربران و برنامهها دسترسیهای موقت بدهید و پس از تکمیل کار، آنها را غیرفعال کنید.
۴) از زبانهای برنامه نویسی با حافظه ایمن استفاده کنید. رایجترین دلیلی که حملات سرریز بافر کار میکنند این است که برنامهها در مدیریت تخصیص حافظه و اعتبارسنجی ورودی از مشتری یا سایر فرآیندها ناکام هستند. برنامههای توسعهیافته در C یا C++ باید از توابع کتابخانه استاندارد خطرناکی که کرانبندی نشدهاند، مانند gets، scanf و strcpy اجتناب کنند. در عوض، آنها باید از کتابخانهها یا کلاسهایی استفاده کنند که برای اجرای ایمن رشته و سایر عملیات حافظه طراحی شدهاند. بهتر است از زبان برنامه نویسی استفاده کنید که احتمال سرریز بافر را کاهش میدهد، مانند جاوا، پایتون یا سی شارپ.
۵) اعتبارسنجی دادهها برنامههای کاربردی موبایل و وب که در داخل توسعه مییابند همیشه باید ورودیها و دادههای کاربر از منابع غیرقابل اعتماد را تأیید کنند تا اطمینان حاصل شود که در محدوده آنچه انتظار میرود قرار دارند و از مقادیر بیش از حد طولانی ورودی جلوگیری میکنند. هر خطمشی امنیتی برنامهای باید قبل از استقرار به آزمایش آسیبپذیری برای اعتبارسنجی ورودی بالقوه و آسیبپذیریهای سرریز بافر نیاز داشته باشد.
جمع بندی
سرریز بافر میتواند تبدیل به یک مشکل امنیتی در سیستم یک سازمان یا کسب و کار باشد. هکرها میتوانند با این نقطه ضعف حملات سرریز بافر را طراحی و اجرا کنند. حملات سرریز بافر انواع مختلفی دارند که هرکدام با استفاده از روشی سعی در ایجاد اختلال در روند اجرای برنامه ها دارند. میتوانید با رعایت چند نکته ریسک وقوع حمله سرریز بافر را به حداقل برسانید.