در این پست رایجترین اشتباهات برنامهنویسی که دولوپرها -کدآموزان تازهکار- مرتکب میشوند را بررسی خواهیم کرد. این اشتباهات منجر به بروز حفرههای امنیتی، درز اطلاعات و نفوذهای ناخواسته میشوند. هر کدام از این اشتباهات در زبانهای خاصی رایج میباشند؛ تعدادی در C و ++C و تعدادی در زبانهای دیگر مثل Java ،JavaScript ،Python و غیره (پشنهاد میکنیم موارد زیر را همواره در کدنویسی مد نظر داشته باشید تا اشتباهاتی از این دست در برنامهٔ شما اتفاق نیفتد. لازم به ذکر است که این لیست به ترتیب اهمیت نوشته شده است).
1. Buffer Overflow
بافر اورفلو زمانی رخ میدهد که دادهها خارج از محدودهٔ حافظه نوشته شوند و این اتفاق ممکن است به دلیل محاسبهٔ اشتباه در محل نوشتن دادهها رخ دهد. همچنین اگر بدون در نظر گرفتن اندازهٔ حافظه در آن دادهای ذخیره کنیم، ممکن است منجر به بروز این خطا شود:
char array[6] = "hello";
strcat(array, ", joe"); /* <- this="" line="" causes="" a="" buffer="" overflow="" code="">
صرفنظر از علت وقوع آن، Buffer Overflow یکی از متداولترین باگهای امنیتی برنامهها است که اکسپلویتهای معروف میتوانند با کمک آن، در برنامهٔ شما نفوذ کنند. برای نمونه اکسپلویتی به نام Morris Internet Worm در سال 1988، اکسپلویت W32/Nimda در سال 2001 و خطای Sendmail در سال 2003 از خطای بافر اورفلو استفاده کردهاند.
2. SQL Injection
SQL Injection تکنیکی است برای گنجاندن دستورات SQL در ورودی کاربر، طوری که این دستورات مستقیماً توسط دیتابیس اجرا شوند (برای کسب اطلاعات بیشتر، به مقالهٔ آشنایی با مفهوم SQL Injection در زبان PHP مراجعه نمایید). در صورتی که برنامهای این ضعف امنیتی را داشته باشد، مهاجم میتواند عملیات مخربی مثل حذف جداول، حذف دیتابیس، دزدی اطلاعات و بسیاری از عملیات خرابکارانهٔ دیگر را انجام دهد:
// The following is a parameter value with SQL injection
String username = "joe'; delete from user where username like '%";
Connection con = ...; // create connection to database
// When this statement is executed, all users are deleted from the database.
con.createStatement().execute("update user set logged_in = 1 where username = '" + username + "'");
یکی از عوامل کلیدی در ممانعت کردن از Injection، بررسی دقیق دیتای ورودی توسط کاربر است؛ نرمافزاری که ورودیهای کاربر و مجوزها را بررسی و صحت ورودیها را برای ارسال به سمت دیتابیس و اجرا تأیید میکند، تا حد زیادی از این حفرهٔ امنیتی در امان خواهد بود.
3. OS Command Injection
OS Command Injection زمانی رخ میدهد که ورودی کاربر بدون بررسی لازم برای اجرا، به سیستمعامل داده میشود. همچین اتفاقی ممکن است توسط یک برنامه برای استفاده از دستورات سیستمعامل اتفاق بیفتد.
وقتی یک برنامه ورودی کاربر را بدون بررسی لازم به سیستمعامل ارسال میکند، راه را برای نفوذگر باز میکند تا با استفاده از پیلودهای هوشمندانه، دستورات مخرب خود را بر روی سیستمعامل قربانی اجرا کند. این دستورات میتوانند شامل حذف فایل، سرقت اطلاعات، تغییر مجوز دسترسی به فایلها و مواردی از این دست باشند.
4. Integer Overflow یا Wraparound
خطای Integer Overflow زمانی اتفاق میافتد که شما مقداری بزرگتر از ظرفیت مجاز یک متغیر از نوع عدد صحیح، در آن ذخیره کنید. در چنین مواردی، مقدار مورد نظر به صورت ناقص در متغیر ذخیره میشود که این موضوع میتواند به نتایج غیر قابل پیشبینی ختم شود. برای مثال، یک متغیر نوع 2-byte unsigned short میتواند حداکثر مقدار 65535 را در خود ذخیره کند:
short a = 65530, b = 10;
short c = a + b;
// on my computer, c has the unexpected value: 4
حال فرض کنید که دو عدد دیگر از همین نوع مثل 65530 و 10 را باهم جمع کنیم و نتیجه را در متغیر مربوطه ذخیره کنیم. نتیجه یا همان عدد 65540، بیشتر از سقف مجاز متغیرمان شده است؛ به همین دلیل مقادیر ناخواستهای به صورت ناقص در حافظه ثبت میشود. در ادامه، وقتی از این متغیر در جای دیگری استفاده شود، مثلاً در اندیس یک آرایه، نتایج غیرمنتظرهای رخ خواهد داد.
5. Improper Validation of an Array Index
یکی دیگر از خطاهای رایج در نرمافزار، استفاده از اندیس نامناسب برای یک آرایه میباشد. این خطا زمانی اتفاق میافتد که از مقداری نامعتبر برای دسترسی به عناصر آرایه استفاده کنیم. وقتی شما به خارج از محدودهٔ دسترسی یک متغیر درخواست دسترسی داشته باشید، با یک خطای دسترسی حافظه مواجه میشوید (این مشکل با عنوان Segment Violation نیز شناخته میشود). وقتی آدرس حافظه خارج از آرایه باشد، اطلاعات شما در مکان نامعتبری از حافظه نوشته میشوند.
این خطاها بیشتر در زبانهای C و ++C اتفاق میافتد، اما در عین حال در هر زبانی محتمل است؛ حتی زبانهایی که مدیریت حافظهٔ خودکار دارند مانند Java ،JavaScript ،Python و غیره. تنها راهی که میتوان از بروز این قبیل خطاها جلوگیری کرد، بالا بردن دقت لازم در حین کدنویسی است.
6. Allocate Resources Without Limits
موضوع تخصیص حافظه در زبانهای C و ++C به علت اینکه مدیریت حافظهٔ آنها باید دستی توسط برنامهنویس کنترل شود، بسیار رایجتر است. تخصیص حافظه بدون در نظر گرفتن اندازهٔ حافظه میتواند منجر به خطا در عملیات تخصیص شود. وقتی نتیجهٔ تخصیص حافظه بررسی نشود و عملیات تخصیص به صورت مستقیم صورت پذیرد، فاجعه رخ خواهد داد!
این قبیل خطاها ممکن است در زبانهای Java ،JavaScript و Python که تخصیص حافظهٔ پویا (دینامیک) نیز دارند اتفاق بیفتد. برای مثال، در عملیات تخصیص آرایهها این خطا محتمل است؛ بنابراین در تخصیص آرایه در این زبانها باید دقت لازم صورت پذیرد.
دلیل دیگری که میتواند منجر به بروز این خطا شود، عدم بررسی لازم هنگام ایجاد فایل هندلها و کانکشن هندلها میباشد. رایجترین راهی که ممکن است این منابع به صورت غیرمعتبر مورد دسترسی قرار گیرند، هنگامی است که بعد از استفاده از آنها، به درستی بسته نشوند.
7. Expired Pointer Dereference
در زبانهایی مثل C و ++C، میتوان بعد از استفاده از حافظه آن را آزاد کرد. استفاده از اشارهگری که هنوز به قسمتی از حافظهٔ آزاد شده اشاره دارد، یک خطا است (این نوع خطا در حفرههای معروف گزارش شده مشهود است). بنابراین شما به عنوان برنامهنویس باید کنترلهای لازم را در کدتان انجام دهید تا از بروز آن جلوگیری شود.
8. Null Pointer Dereference
یک اشارهگر در صورتی که به درستی مقداردهی اولیه نشود، ممکن است مقداری تهی داشته باشد (یا بعد از آزادسازی حافظه). فراخوانی همچنین اشارهگری میتواند منجربه خطا شود (در جاوا اصطلاحاً به آن NullPointerException گفته میشود؛ برای آشنایی بیشتر با این خطا در زبان جاوا، به آموزش آشنایی با انواع Exception ها در زبان جاوا مراجعه نمایید). مانند جاوا، این خطا در C و ++C بسیار رایج است و قطعاً ممکن است در سایر زبانها هم اتفاق افتد. جهت جلوگیری از بروز این خطا، باید دقت لازم را در کدنویسی اعمال کنید.
9. Missing Initialization
متغیرهای لوکال (محلی)، متغیرهایی هستند که درون یک فانکشن (تابع) یا بلوک کد تعریف شدهاند و در انتهای بدنهٔ فانکشن نیز از دسترس خارج میشوند:
int pos;
char buffer[] = "hello world";
// this line may print garbage and/or may crash the program since pos is not initialized.
printf("Value of character at pos %d is: %c\n", pos, buffer[pos]);
مقداردهی مناسب این متغیرها بر عهدهٔ برنامهنویس است. استفاده از متغیر قبل از مقداردهی اولیه منجر به بروز خطای Missing Initialization میشود که قطعاً مشکل آفرین خواهد بود (یا چیزی مهلکتر).
10. Broken or Risky Cryptographic Algorithm
دنیای رمزنگاری دائما در حال تکامل است. چیزی که امروز پذیرفته شده است، ممکن است به زودی رد شود و افزایش قدرت پردازش کامپیوترها میتواند دلیل این تکامل باشد. پردازشی که سالها برای اجرا زمان میبرد، فردا ممکن است در عرض چند دقیقه انجام شود؛ یا ممکن است یک نفر کِرک یک الگوریتم را کشف نماید که آن الگوریتم را بلااستفاده خواهد کرد.
بنابراین شما باید دائما به پیشرفتهای رمزنگاری توجه داشته باشید و چنانچه برای الگوریتمهایی که استفاده کردهاید آسیبپذیری کشف شد، اقدام به بهروزرسانی کدهای خود نمایید (برای آشنایی بیشتر با تفاوتهای مابین اِنکریپشن و هَشینگ، به مقالهٔ چه تفاوتهایی میان Encryption و Hashing وجود دارد؟ مراجعه نمایید).
برای مثال، SHA-1 که یک الگوریتم هشینگ است، دیگر برای هَش اطلاعات استفاده نمیشود. در سال 2005، حملاتی علیه این الگوریتم شناسایی شد و در حال حاضر SHA-2 یا SHA-3 توصیه میشوند (برای کسب اطلاعات بیشتر، به مقالهٔ استفاده از الگوریتم SHA-1 ممنوع چراکه گوگل توانست آن را بشکند! مراجعه نمایید). بنابراین چنانچه در هر قسمتی از کدتان از SHA-1 استفاده کردهاید و برنامه هنوز استفاده میشود، نسبت به تعویض آن با الگوریتمهای جدید اقدام نمایید. در غیر این صورت این ریسک را بپذیرید که برنامه شما قابلنفوذ خواهد بود.