عنصر
اصلی در برنامهنویسی امن با زبانهای مختلف برنامهنویسی، مستندسازی خوب و
استفاده از استانداردهای قابل اجرا است. استانداردهای کدنویسی، برنامه
نویسان را ترغیب به پیروی از مجموعهای متحدالشکل از قوانین و راهنماییها
میکند که بر اساس نیازمندیهای پروژه و سازمان تعیین شده است، نه بر اساس
سلایق و مهارتهای مختلف برنامهنویسان. به محض تعیین استانداردهای مذکور،
می توان از آن به عنوان معیاری برای ارزیابی کدهای منبع، چه به صورت دستی و
چه به صورت اتوماتیک استفاده کرد.
از استانداردهای معروف در این زمینه میتوان به استانداردCERT برای کدنویسی امن اشاره کرد که یک سری از قوانین و پیشنهادات را برای کدنویسی امن با زبانهای برنامهنویسی C، C++
و جاوا ارائه میدهد. هدف از این قوانین و پیشنهادات، حذف عادتهای
کدنویسی ناامن و رفتارهای تعریف نشده است که منجر به آسیبپذیریهای قابل
سوءاستفاده میشود. به کارگیری استانداردهای مذکور منجر به تولید سیستمهای
با کیفیت بالاتر میشود که در برابر حملات بالقوه، پایدارتر و مقاومتر
هستند.
در مقاله "آشنایی با استاندارد CERT برای برنامهنویسی امن"، کلیات استاندارد CERT در زمینه مزبور را توضیح دادیم و در سری مقالههای برنامهنویسی امن به زبان C
به صورت تخصصیتر شیوه برنامهنویسی امن با این زبان را مورد بررسی قرار
میدهیم. قابل ذکر است که در این استاندارد 89 قانون و 134 پیشنهاد برای
برنامهنویسی امن با زبان C
ارائه شده است که در این سری مقالات، مهمترین آنها را که در سطح یک قرار
دارند، شرح خواهیم داد. برای کسب اطلاعات بیشتر در مورد سطحبندی قوانین و
پیشنهادات به مقاله "آشنایی با استاندارد CERT برای برنامه نویسی امن" مراجعه فرمایید. در مقاله حاضر به پیشنهادات و قوانین ارائه شده سطح اول در مورد ورودی - خروجی خواهیم پرداخت.
30. FIO01-C – در استفاده از توابعی که از نام فایل برای شناسایی استفاده میکنند، مراقب باشید.
بسیاری
از آسیبپذیریهای امنیتی مرتبط با فایلها از آنجا ناشی میشود که برنامه
سعی در دسترسی به فایلی نادرست دارد، زیرا نام فایل همانند یک طناب نازک
به فایل اصلی وصل شده است. نام فایل هیچ گونه اطلاعاتی را در مورد طبیعت شئ
فایل فراهم نمیکند. علاوه بر این اتصال نام فایل به شئ فایل، هر بار که
نام فایل در یک عملیات استفاده میشود، دوباره اعتبار سنجی می شود. برعکس
نام فایل، توصیف کننده های فایل (file descriptor)
و اشاره گرها به فایل، توسط سیستم عامل به فایل اصلی در لایه های پایینی
اتصال پیدا می کنند. دسترسی به فایل توسط توصیف کننده و یا اشاره گر به جای
نام فایل، درجه اطمینان بیشتری را فراهم می آورد. به همین دلیل، توصیه
میشود، هر جا که ممکن است فایل ها از طریق اشاره گرها و یا توصیف کننده ها
مورد دسترسی قرار گیرند. توابع زیر تنها از نام فایل برای تأیید هویت فایل
استفاده می کنند و در استفاده از آنها باید دقت لازم را به عمل آورد:
· Remove()
· Rename()
· Fopen()
· Freopen()
در زیر یک نمونه برنامه نشان داده شده است که با این قانون همخوانی ندارد. فایلی با نام file_name باز شده، پردازش شده، بسته و پاک می شود. با این وجود احتمال دارد که شئ فایلی که توسط file_name در تابع remove() مشخص شده همان شئ فایلی که در تابع fopen() مشخص شده، نباشد.
برای
اصلاح کد مذکور، از لحاظ برنامه نویسی کار خاصی نمی توان انجام داد، به جز
اینکه مطمئن شد که فایل در یک دایرکتوری امن باز شده و حق دسترسی به گونه
ای است که از دسترسی افراد غیر مجاز به فایل مذکور جلوگیری به عمل می آورد.
31. FIO30-C – ورودی کاربر را از رشته های دارای قالب خارج کنید.
هرگز
هیچ تابع قالب دار ورودی- خروجی را با یک رشته دارای قالب که از کاربر
دریافت شده است، فراخوانی نکنید. یک مهاجم که به صورت جزئی و یا
کامل بتواند محتوای رشته های دارای قالب را کنترل کند، می تواند یک پروسه
آسیب پذیر را از کار بیندازد، محتوای پشته را مشاهده کند، محتوای حافظه را
مشاهده کند و یا بر روی قسمت دلخواهی از حافظه بنویسد و در نهایت کد دلخواه
را با حق دسترسی پروسه آسیب پذیر اجرا کند.
توابع
خروجی دارای قالب، نیز خطرناک هستند زیرا بسیاری از برنامه نویسان نسبت به
توانایی های آنها ناآگاه هستند. برای مثال، آنها می توانند یک مقدار صحیح
را در یک آدرس مشخص با استفاده از تبدیل نوع %n بنویسند.
در زیر کدی را می بینید که از این قانون تبعیت نکرده است. در اینجا یک تابع به نام incorrect_password()
نشان داده شده است که در زمان تأیید هویت، در صورتی که نام کاربری وارد
شده پیدا نشود و یا کلمه عبور صحیح نباشد، فراخوانی می شود. این تابع نام
کاربر را به صورت یک رشته که انتهای آن کاراکتر null است، می پذیرد. نام این متغیر user
است. این مثال کاملاً نشان دهنده داده ای است که از یک منبع غیر قابل
اعتماد یعنی کاربر تأیید نشده دریافت می شود. تابع مذکور یک پیغام خطا را
ایجاد می کند که نهایتاً در stderr قرار گرفته و با استفاده از تابع fprintf() در خروجی چاپ می شود.
این تابع اندازه پیغام را محاسبه کرده و سپس به صورت داینامیک به آن فضا اختصاص می دهد. سپس پیغام را با استفاده از تابع snprintf() در فضای تخصیص داده شده می سازد. در اینجا امکان ایجاد سرریز عدد صحیح بررسی نشده است، زیرا فرض گردیده که اندازه متغیر user
کمتر یا مساوی 256 است. این اتفاق ممکن است در موارد مشابه دیگر نیز که
ساختن یک پیغام سخت است، رخ دهد و منجر به آسیب پذیری در رشته دارای قالب
که به عنوان آرگومان به fprintf() ارسال می شود، شود.
در زیر نسخه اصلاح شده کد مذکور را مشاهده می کنید که در آن به جای تابع fprintf() از تابع fputs() استفاده شده است که با msg همانند یک رشته دارای قالب برخورد نمی کند بلکه مستقیماً آن را به stderr می فرستد.
راه حل زیر نیز که ساده تر است، ورودی نامطمئن از کاربر را به عنوان آرگومان و نه به عنوان رشته قالب دار به fprintf() می فرستد و احتمال آسیب پذیری در رشته قالب دار را حذف می کند.