پیاده سازی روش تاخیر زمانی برای جلوگیری از حمله های Brute force

در این منبع چند راهکار منجمله راهکار تاخیر زمانی برای جلوگیری از حمله های Brute force مطرح شده:
https://www.owasp.org/index.php/Bloc…_Force_Attacks

بنظر شما روش تاخیر زمانی رو چطور پیاده سازی کنیم؟
در وهلهء اول بنظر میرسه که استفاده از یک sleep ساده راهگشا است، اما این روش در برابر حمله هایی که از چند Thread بصورت همزمان استفاده کنن ضعف داره.
برای روشن شدن مطلب مثال میزنم:
فرض کنید ما یک sleep(1) برای ایجاد محدودیت یک تلاش برای لاگین در هر ثانیه استفاده کردیم (راستی زمانش بنظر شما باید چقدر باشه؟ آیا یک ثانیه کافیه؟). ولی برنامه ای که مثلا 10 تا Thread رو همزمان (یا فرقی نمیکنه، بهرصورت با فاصلهء زمانی کوتاه) اجرا و تلاش برای لاگین میکنه بعد از این حدود 1 ثانیه 10 تا پسورد رو تست کرده و عملا سیستم امنیتی ما رو دور زده.
حالا ما نیاز به یه مکانیزم داریم تا در برابر روش مالتی ترد هم درست کار کنه.

———————–

در یک حملهء Brute force برای کشف پسورد کاربران، یک برنامه بصورت خودکار پسوردهای مختلف رو روی اکانت کاربران تست میکنه. یعنی درخواستهای HTTP با فواصل خیلی کوتاه (بسته به سرعت شبکه و محدودیت پهنای باند – مثلا سرعت اجرا در یک LAN میتونه خیلی بالاتر باشه) برای لاگین با پسوردهای مختلف ارسال میشن. باید توجه داشت که این درخواست ها در برنامه های مالتی ترد میتونن تقریبا همزمان باشن و برنامه نیازی نداره منتظر دریافت پاسخ درخواست قبلی بمونه تا درخواست جدیدی رو ارسال کنه. فرضا برنامه میتونه همزمان 100 درخواست رو ارسال کنه و هر ثانیه یک بار این کار رو انجام بده (فرضا زمان لازم برای ارسال و دریافت نتیجهء هر درخواست 1 ثانیه هست). البته همهء برنامه ها مالتی ترد نیستن، اما باید جلوی هردو نوع مالتی ترد و single thread رو بگیریم (اگر به بحثهای قبلی دقت کرده باشید، یک الگوریتم که جلوی single thread رو میگیره ممکنه در برابر مالتی ترد ضعف داشته باشه).

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

یکی دیگر از روشهای پایه و تقریبا بدون عوارض جانبی که میشه بکار برد روش تاخیر زمانی هست (البته بنده این اسم رو بهش میگم). البته امنیتی که این روش میده درحد خیلی بالای بعضی روشهای دیگه نیست، اما درعوض امکان سوء استفاده از اونهم خیلی کمتر هست.
در این روش ما کاری میکنیم که در بازهء زمانی مشخصی، مثلا یک ثانیه، فقط یک درخواست بتونه پردازش بشه، و درخواست های دیگه باید در صف انتظار بمونن تا درخواست های قبلی به ترتیب در بازهء زمانی خودشون اجرا بشن و نوبت برسه. یعنی عمدا یک دستور sleep در فرضا اسکریپت لاگین خودمون کار میذاریم که باعث بشه اجرای هر درخواست لاگین مثلا حداقل یک ثانیه طول بکشه، و از طرف دیگه نمیذاریم در این مدت درخواست دیگری بصورت همزمان اجرا بشه.
یعنی حتی اگر 1000 درخواست همزمان برای لاگین به یک سرور برسه، در هر ثانیه فقط یکی از اونها پردازش خواهد شد و به این صورت میشه سرعت ممکن برای اجرای حملات Brute force رو از ظرفیت واقعی سرور و محیط ارتباطی خیلی پایین تر آورد. هرچی سرعت کم بشه شانس موفقیت و نیز سماجت از جانب کرکر کمتر هست و پسوردهای ضعیف تری امن میمونن. مثلا پسوردی که خیلی ضعیف نیست اما قوی هم نیست و درحالت عادی میشه در ظرف چند روز اون رو با روش Brute force کرک کرد، به این شکل نیاز به زمان خیلی بیشتری برای کرک شدن خواهد داشت (مثلا 100 برابر).
پسوردهای کاملا قوی البته دربرابر حمله های Brute force بدون هیچ مکانیزم امنیتی هم امن هستن، اما خیلی از پسوردهایی که توسط افراد انتخاب میشن اونقدر قوی نیستن. پسوردها گرچه طولانی باشن اما اگر فرضا از عباراتی که در یک دیکشنری موجود باشن انتخاب شده باشن قوی نیستن. خیلی از برنامه های کرک کلمات داخل دیکشنری و لیست پسوردهای متداولی رو که در اختیار دارن تست میکنن (و شاید مقداری ترکیبات سادهء اونها رو مثل اضافه کردن ارقام به انتهای اونها).

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

این یه نمونه از سیستم تاخیر زمانی با استفاده از Lock file که تا اینجا درست کردم:

<?php

ignore_user_abort(true);
set_time_limit(60); // needed for testing case

for($i=0; $i<1024; $i++) echo " "; // needed for some browsers to start to show content incrementally

function echo_flush($msg) {
echo $msg;
@ ob_flush(); flush();
}

echo "<pre>";
echo_flush("start\n");

while(true) {
$i=0;
while(file_exists("lock")) {
echo_flush("lock file exists | ");
clearstatcache(); // very important for the following check
if((time()-filemtime('lock')) > 10) {
// lock file too old; seems orphaned!
// we should log this incident and probably send an email to admin - it can be the sign of a bug in some situations
echo_flush("\nlock file too old\ndeleting the lock file...\n");
unlink("lock");
echo_flush("lock file deleted\n");
break;
}
if(++$i%5==0) echo "\n"; // just for output formatting
sleep(1);
}
echo_flush("\nlock file does not exist: ".date('H:i:s')."\n");
echo_flush("attempting to create the lock file...\n");

sleep(5); // temporary sleep for testing the algorithm

if(@ fopen("lock", "x")) {
echo_flush("lock file created\n");
break;
}
else echo_flush("lock file creation failed\n"); // apparently, another request preempted in obtaining the lock
}

// login proccess
echo_flush("\nlogin process executed\n\n");
// login proccess
sleep(1); // this is the sleep that makes the needed time to make brute force attacks slow

echo_flush("deleting the lock file...\n");

sleep(5); // temporary sleep for testing the algorithm

if(file_exists("lock")) {
unlink("lock");
echo_flush("lock file deleted\n");
}
else echo_flush("Error: lock file does not exist!\nwatch for a bug in the algorithm.\n");

echo_flush("\nfinish: ".date('H:i:s'));

echo "</pre>";

?>

این کد رو در سه فایل با نامهای مختلف ذخیره کنید و هر سه فایل رو در مرورگر اجرا کنید (سعی کنید حداکثر ظرف چند ثانیه همهء صفحات رو باز کنید تا بین باز کردن صفحات بیش از حد فاصله نیفته و بشه کارکرد برنامه رو درحالت درخواستهای همزمان مشاهده کرد).
البته بنده دو دستور sleep اضافه با تاخیر 5 ثانیه ای به برنامه اضافه کردم تا اجرای دستورات برنامه ها اونقدری طول بکشه تا بشه شرایط درخواست های همزمان رو شبیه سازی و مشاهده کرد. در موقع استفادهء واقعی باید اون دو sleep رو حذف کرد و فقط دستور sleep یک ثانیه ای باقی بمونه (البته این زمان رو هم باید فکر کرد شاید بهتر باشه بیشتر یا کمترش کنیم).

ضمنا این کد فقط برای تست و نشون دادن طرز کار کلی الگوریتم هست. در شرایط واقعی احتمالا میخوایم که قفل بر اساس نام کاربری لاگین و احتمالا بعلاوه بر اساس IP باشه. یعنی نام فایل lock که برنامه ایجاد میکنه بر اساس نام کاربری باشه. اگر محدودیت بر اساس IP هم ایجاد کنیم، یک فایل lock به ازای هر IP هم خواهیم داشت که اسم این فایل مثلا همون آدرس IP خواهد بود. بنابراین برنامه مثلا اول قفل نام کاربری رو باید بدست بیاره، بعد از اینکه قفل نام کاربری رو گرفته قفل IP رو باید بدست بیاره و بعد از اجرا باید هر دو قفل رو آزاد کنه.
توجه کنید که اگر محدودیت بر اساس نام کاربری نباشه، مثلا 5 نفر که بصورت تقریبا همزمان اقدام به لاگین کنن، نفر آخری که نوبتش میرسه حدود 5 ثانیه منتظر مونده. پس اگر 50 نفر همزمان باشن 50 ثانیه و … .

ویرایش:
الان داشتم منبع رو نگاه میکردم، دیدم یک پارامتر رندوم رو وارد مقدار تاخیر زمانی کرده. و این نکته ای بود که بهش دقت نکرده بودم.
مثلا این خط از کد مثالش به زبان سی شارپ:

Thread.Sleep(rand.Next(minSeconds, maxSeconds) * 1000);

حالا اینکه مقدار minSeconds و maxSeconds باید در چه حدودی باشه اشاره ای نکرده. منم دنبال همین بودم که ببینم زمان پیشنهادی این منبع چقدر هست.
اما عوضش نکته ای که از قلم انداخته بودم رو دیدم که متغییر بودن زمان تاخیر هست.
دقیقا نمیدونم این زمان متغییر رندوم برای چیه و تا چه حدی مهمه. باید سرفرصت بیشتر روش فکر کنم. بهرحال گفتم فعلا مطلب تاپیک رو از نظر فنی کامل کنم. یعنی اگر ما بخوایم این سیستم رو پیاده سازی کنیم احتمالا باید میزان تاخیر زمانی رو هم بصورت متغییر و رندوم در یک بازه تعیین کنیم. بدیهی هست که هیچ کار سختی هم نیست؛ کد برناممون فقط چند خط و دستور بیشتر میشه.

—————

خوانندگان عزیز توجه داشته باشن که روش تاخیر زمانی که در اینجا معرفی شد به این معنی نیست که بنده این روش رو برای عموم یا بیشتر سایتها توصیه میکنم. این صرفا یکی از روشهای ممکن بود که مثل روشهای دیگه مزایا و معایب خودش رو داره و ضمنا چون از گستردگی بکارگیری اون اطلاعی ندارم و در عمل بصورت واقعی و گسترده تست و تحلیل نشده، نمیتونم درمورد اینکه خودش اشکال بزرگی ایجاد نکنه هیچ تضمین یااطلاعاتی بدم. بنده فقط خواستم یک پیاده سازی این روش و جوانب تئوریک عمدهء اون رو بررسی کنم. بقیش منوط به نیاز و تست و تجربهء عملی است.

پاسخ دهید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

*

شما می‌توانید از این دستورات HTML استفاده کنید: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>