اول، مطمئن شوید که واقعاً حمله است
گرانترین اشتباه در یک قطعی، رسیدگیکردن به چیز اشتباه است. "سایت کند است" نشانهای است مشترک میان یک سیل واقعی، یک استقرار که یک کوئریِ بدون ایندکس را با خودش آورده، یک cron job که هر ساعت درست سرِ همان دقیقه شروع به خالیکردنِ پایگاهداده کرده، یک کراولر که جستوجوی وجهدارِ شما (faceted) را کشف کرده، لینکی که به صفحهٔ اولِ یک سایت بزرگ راه پیدا کرده، و دیسکی که پر شده است. هرکدام از اینها از نگاهِ یک مرورگر کاملاً یکسان بهنظر میرسند، و واکنشهای درست برایشان متقابلاً ناسازگارند: نمیخواهید مشتریهای واقعی را rate-limit کنید چون یک مایگریشن یک ایندکس را جا انداخته.
سه پرسش در کمتر از یک دقیقه اینها را از هم جدا میکند. آیا حجم ترافیک واقعاً غیرعادی است، یا ترافیک عادی است و این سرور است که کند شده؟ یک سیل خودش را بهشکل یک جهشِ پلکانی در بستهها یا درخواستها بر ثانیه نشان میدهد؛ یک استقرارِ بد، حجمِ درخواستِ عادی و زمانِ پاسخِ فروریخته نشان میدهد. آیا چیزی در یک ساعتِ گذشته از سمتِ شما تغییر کرده؟ پیش از فایروال، لاگِ استقرار را چک کنید — روی زیرساختهای کوچک، حوادثِ خودزده بهمراتب از حملهها بیشترند. آیا بار در کل سایت پخش شده، یا روی یک مسیر متمرکز است؟ سیلهای واقعی معمولاً بیتبعیضاند یا صفحهٔ اول را هدف میگیرند؛ یک endpointِ گرانقیمت که زیرِ ضربِ صد کلاینت است به سوءاستفاده نزدیکتر است تا به یک DDoS، و راهحلش هم بسیار ارزانتر است.
به اینها پاسخ دهید، بعد ادامه دهید. بقیهٔ این راهنما این فرض را دارد که پاسخ این بوده: حجم غیرعادی است، از سمتِ شما چیزی تغییر نکرده، و باکس دارد غرق میشود.
SP·02تریاژِ پنجدقیقهای
فقط دو حالتِ شکست وجود دارد که اهمیت دارند، و هرکدام پاسخِ مخالفِ دیگری را میطلبد. یا لوله پر است — بستهها سریعتر از آنچه آپلینک یا کرنلِ شما بتواند پردازش کند میرسند، و سرورِ شما پیش از آنکه هیچکدام از نرمافزارهایتان آنها را ببیند، ترافیک را از دست میدهد — یا لوله سالم است و اپلیکیشن از پا افتاده، چون درخواستهای خوشساخت سریعتر از آن میرسند که بتواند پاسخشان را بدهد. قاطیکردنِ این دو، ساعت را هدر میدهد: تنظیمِ nginx در برابرِ یک لینکِ اشباعشده هیچ فایدهای ندارد، و خریدِ پهنایباندِ بیشتر در برابرِ یک سیلِ درخواست هیچ فایدهای ندارد.
این دو را با مقایسهٔ دو عدد از هم تشخیص دهید. همزمان به شمارندههای اینترفیس و تقسیمِ CPU نگاه کنید. اگر بایتهای rx نزدیکِ سرعتِ پورتتان قفل شدهاند، اگر شمارندههای dropped یا overrun بالا میروند، و اگر زمان صرفِ soft interrupt میشود نه صرفِ اپلیکیشنِ شما، سیل روی لایهٔ 3 یا 4 است و یک مسئلهٔ ظرفیت است. اگر پهنایباند چیزِ غیرعادیای نشان نمیدهد اما استخرِ workerها اشباع شده، اتصالها صف کشیدهاند، و لاگِ access پر از درخواستهای بهظاهر معتبر است، این لایهٔ 7 است و یک مسئلهٔ فیلترینگ است.
سپس موردِ لایهٔ 3/4 را یک قدم جلوتر دستهبندی کنید، چون زیرگونهها رفتارِ متفاوتی دارند. یک سیلِ SYN بهشکلِ دهها هزار سوکتِ نیمهباز در SYN-RECV ظاهر میشود؛ کرنل وقتی syncookie روشن باشد این را خوب مدیریت میکند. یک سیلِ UDP یا تقویتشده (amplification) بهشکلِ حجمِ ورودیِ عظیم روی پورتهایی ظاهر میشود که حتی به آنها گوش هم نمیدهید — بازتابهای DNS، NTP، memcached، CLDAP — و هیچچیزی که خودتان اجرا کنید کمکی نمیکند، چون آسیب تا زمانی که بستهها به NIC شما برسند از قبل واردشده. یک سیلِ فرگمنتیشن یا بستهٔ خام بهشکلِ packets-per-second بالا با پهنایباندِ متوسط ظاهر میشود، که CPU را گرسنه نگه میدارد نه لینک را. پیش از دستزدن به فایلِ کانفیگ، بنویسید کدامیک را دارید.
چیزی که نمیتوانید از داخلِ باکس درست کنید
این همان بخشی است که بیشترِ مقالهها از آن میگذرند، و همان بخشی است که تعیین میکند ساعتِ شما مفید بوده یا نه. یک قاعدهٔ فایروال روی هدف، یک آپلینکِ اشباعشده را نجات نمیدهد. DROPِ iptablesِ شما روی ماشینِ انتهای لوله اجرا میشود — بسته از قبل از لینکِ ترانزیت گذشته، از قبل پهنایباندی را که پولش را میدهید مصرف کرده، و از قبل بستهٔ یک کاربرِ واقعی را جابهجا کرده است. دراپکردنِ محلیِ آن، اپلیکیشنِ شما را از هدررفتنِ سیکلها حفظ میکند، که ارزشش را دارد، اما پهنایباندِ شما را از هیچچیز حفظ نمیکند.
سقفِ صادقانه برای یک سرورِ تنها، تقریباً کوچکترینِ دو عدد است: سرعتِ پورتی که به آن وصل است، و تعدادِ بستهبرثانیهای که CPUاش میتواند دستهبندی کند. یک پورتِ 1 Gbps در 1 Gbps پر میشود، فارغ از اینکه ruleset شما چقدر شیک باشد، و یک سیلِ نهچندان بزرگ از بستههای کوچک میتواند چند هسته را روی مدیریتِ interrupt خسته کند، خیلی پیش از آنکه عددِ پهنایباند نگرانکننده به نظر برسد. پس از آن نقطه، تنها چیزی که کمک میکند دستگاهی است جلوترِ بالادست که ظرفیتش از حمله بیشتر است و ترافیک را پیش از آنکه اصلاً به لینکِ شما برسد، دراپ میکند. اسکرابینگ (scrubbing) دقیقاً همین است، و به همین دلیل است که تکتکِ ماشینهای ناوگانِ ما پشتِ 1.5 Tbps کاهشِ حملهٔ همیشهروشن قرار دارند، نه پشتِ یک فایروالِ بزرگتر.
نتیجهاش هم به همان اندازه اهمیت دارد: اگر هیچ محافظتِ بالادستی نداشته باشید، واکنشِ ارائهدهندهٔ شما به یک حملهٔ حجمیِ بزرگ این است که آدرسِ شما را null-route کند، چون جایگزین، افتدادنِ کیفیتِ همهٔ مشتریهای دیگرِ روی همان لینک است. این بدجنسی نیست، ریاضی است — و یعنی مهاجم با گرانکردنِ شما برنده میشود، نه با شکستنِ چیزی. از پیش دانستنِ اینکه آیا کاهشِ حمله در پلنِ شما گنجانده شده یا یک افزودنیِ پولی است که هرگز فعالش نکردهاید، یک بررسیِ پنجدقیقهای است که ارزشش را دارد همین امروز انجامش دهید، نه وسطِ حادثه. مالِ ما روی هر پلنی گنجانده شده، که تنها ترتیبی است که ساعتِ سه صبح کمک میکند.
SP·04لایهٔ 7: سیلی که شبیهِ ترافیک است
سیلِ لایهٔ اپلیکیشن سختتر است چون تکتکِ درخواستها بهتنهایی مشروعاند. یک سیلِ HTTP خوبساخته، دستدادنِ TCP را کامل میکند، TLS را مذاکره میکند، یک GET / معتبر با یک user agentِ باورپذیر میفرستد، و پاسخ را میخواند. هیچ بستهای در آن بدشکل نیست. چیزی که شما را میکشد ریاضی است: فرستادنِ یک درخواست تقریباً هیچ هزینهای برای مهاجم ندارد، اما برای شما هزینهٔ یک کوئریِ پایگاهداده، یک رندرِ template، و صد میلیثانیه از یک workerی دارد که دیگر به هیچکسِ دیگری سرویس نمیدهد.
نشانهها در همان لاگِ accessِ خودتاناند، و معمولاً وقتی دنبالشان بگردید، نه وقتی فقط نگاهشان کنید، آشکارند. رشتههای کوئریِ cache-buster — هزاران درخواست به /?1234567، هرکدام یک URLِ یکتا که هر کشی که دارید را دور میزند — رایجترین امضای تنها هستند. توزیعِ user-agentی بدون دنبالهٔ بلند: ترافیکِ واقعی آمیزهای شلوغ از صدها نسخهٔ مرورگر است، و یک سیل اغلب سه رشته است که میلیونبار تکرار شده، یا رشتهای که هیچ کاربرِ واقعیای از آن استفاده نمیکند. فیلدِ referrerی که همهجا یکسان است. درخواستهایی که کلاً از کنارِ static assetهای شما رد میشوند — یک مرورگرِ واقعی بعد از HTML، CSS، فونتها و تصویرها را میگیرد؛ یک کلاینتِ سیل فقط HTML را میخواهد و میرود. و توزیعِ منبعی که بیشازحد صاف است: یک باتنتِ پخششده روی دههزار آدرسِ خانگی که هرکدام دو درخواست در ثانیه میفرستند، تا وقتی متوجه نشوید نرخِ بهازای هر آدرس بهطرزِ مشکوکی یکنواخت است، شبیهِ محبوبیت بهنظر میرسد.
بعد یک گونهٔ دیگر هم هست که تقریباً به هیچ ترافیکی نیاز ندارد: حملهٔ کند (slow attack). چند صد اتصال که باز میشوند، هر بیست ثانیه یک هدر میفرستند و هرگز تمام نمیشوند، تمامِ workerهایی را که دارید اشغال میکنند درحالیکه نمودارِ پهنایباندِ شما صاف میماند. راهحل یک rate limit نیست — نرخِ درخواست ناچیز است — بلکه timeoutهای تهاجمی برای هدر و بدنه است، و دقیقاً به همین دلیل است که آنها در اولین بلوکِ کانفیگِ زیر میآیند، نه بهعنوانِ فکرِ بعدی.
SP·05چهار کنترل، بهترتیبِ تأثیر در هر دقیقه
زیرِ فشار، اول کاری را انجام دهید که بیشترین اهرم را دارد. ترتیبِ زیر دلبخواهی نیست؛ تقریباً نزولی است بر اساسِ اینکه هرکدام بهازای هر دقیقه از توجهِ شما چقدر بار را کم میکند.
- چیزِ ارزانی سرو کنید. یک میکروکشِ سیثانیهای جلوی اپلیکیشنِ شما، هزار درخواستِ یکسان در ثانیه را به یک برخوردِ origin و 999 خواندنِ حافظه تبدیل میکند. این بزرگترین اهرمِ تنها روی تقریباً هر سیلِ HTTP است، هزینهاش یک بلوکِ directive است، و برای ترافیکِ ناشناس تقریباً همیشه امن است.
proxy_cache_lockرا اضافه کنید تا یک cache miss یک گلهٔ رمنده (thundering herd) به سمتِ بکاند نفرستد. - همزمانی را سقف بگذارید و timeoutها را کوتاه کنید.
limit_connبهازای هر آدرس بهعلاوهٔ timeoutهای سفتوسختِ هدر، بدنه و keepalive، حملاتِ کند را کاملاً میکشد و جلوی هر کلاینتِ تکی را میگیرد که استخرِ workerِ شما را پارک کند. این کنترلی است که کمترین هزینه را برای کاربرانِ واقعی دارد. - Rate-limit کنید، با یک burst.
limit_reqبا یک burstِ معقول دقیق است اما تنظیمش کندتر است، و همان کنترلی است که اگر آن را از سرِ وحشت تنظیم کنید نه از سرِ baselineِ خودتان، false positive تولید میکند. پیش از آنکه بتوانید عددی انتخاب کنید باید نرخِ عادیِ درخواستبرثانیهٔ بهازای هر کلاینت را بدانید — و دقیقاً به همین دلیل است که پستمورتمِ انتهای این راهنما بیش از آنچه بهنظر میرسد اهمیت دارد. - مسدود کنید، محدود و با اکراه. دراپکردنِ شبکههای مشخص وقتی منابع متمرکزند جواب میدهد و وقتی متمرکز نیستند هیچ فایدهای ندارد. همچنین بد پیر میشود: هر بلاکی که در طولِ یک حادثه اضافه میکنید، ممکن است سه ماه بعد مشتریای باشد که بیسروصدا ردش میکنید. از یک ست با timeout استفاده کنید تا قانونها خودشان منقضی شوند.
توجه کنید چه چیزی در این فهرست نیست: بنکردنِ دستیِ آدرسهای IP تکتک، ریاستارتکردنِ مکررِ وبسرور، و غیرفعالکردنِ فایروال برای "دیدنِ اینکه کمک میکند یا نه". اولی برای اینکه در برابرِ یک منبعِ توزیعشده اثری داشته باشد بیشازحد کند است، دومی هر اتصالِ گرمی را که داشتید دور میریزد، و سومی همان راهی است که یک حادثه به یک نفوذ (compromise) تبدیل میشود.
SP·06با آن از ماشینی که دادههای شما را نگه میدارد نجنگید
هرکدام از کنترلهای بالا وقتی جایی غیر از باکسِ نگهدارندهٔ پایگاهدادهٔ شما اجرا شود، ارزشِ بیشتری دارد. اگر لبهٔ شما (edge) یک نودِ جداست، سیل روی ماشینی پایان مییابد که کارِ تماموقتش پایاندادن به سیلهاست: کش میکند، rate-limit میکند و با آدرسِ واقعیِ کلاینت در دست، دراپ میکند، و مبدأ (origin) فقط باقیماندهٔ کوچک و فیلترشده را روی یک تونلِ خصوصی میبیند. وقتی لبه از پا بیفتد، آن را در 15 min جایگزین میکنید و هیچچیز از دست نمیدهید، چون هیچچیزی رویش نیست. وقتی مبدأ از پا بیفتد، یک قطعی و یک ریستور در پیش دارید.
این جداسازی، همان دورزدنی را هم میبندد که بیشترِ کاهشِ حملهها را تزئینی میکند. اگر مبدأ همچنان یک شنوندهٔ عمومی داشته باشد، مهاجمی که آدرسش را پیدا کند — از طریقِ DNS منفعل، یک ورودیِ certificate transparency، یک رکوردِ MX یا یک پیشنمایشِ لینک — میتواند از کنارِ هر کنترلی که پیکربندی کردهاید نشانه برود و مستقیم به اپلیکیشن بزند. این آنقدر رایج است که ارزش دارد تا خلافش ثابت نشود، آن را حالتِ پیشفرضِ هر سایتِ "محافظتشدهای" در نظر بگیرید. ساختنِ نسخهای که دوام میآورد خودش یک راهنمای جداست: یک reverse proxyِ آفشور بدون هیچ شنوندهٔ عمومی روی مبدأ.
یک هشدار دربارهٔ وسطِ یک حادثه: این معماری است، نه کمکِ اولیه. برپاکردنِ یک لبه، جابهجاکردنِ DNS و بازسازیِ یک تونل درحالیکه زیرِ حملهاید، کاری دوساعته است که زیرِ فشار بد انجام میشود، و فقط تغییرِ DNS هم بهاندازهٔ همان TTLای که دارید اثر نمیکند. اگر دارید، از آن استفاده کنید. اگر ندارید، ساعت را با کنترلهایی که دارید پشتِ سر بگذارید، و آن را در هفتهٔ آرامِ بعدی بسازید — که دقیقاً همان زمانی است که هیچکس این کار را نمیکند.
SP·07روزی که شما بازتابدهندهاید، نه هدف
یک نسخهٔ دومِ این حادثه هم هست که در آن سرورِ شما قربانی نیست و هیچکس هم به شما نمیگوید. یک ریزالورِ باز، یک دیمنِ NTPِ درمعرض، یک memcachedِ بدونِ احراز هویت روی یک اینترفیسِ عمومی، یک پاسخدهندهٔ SSDP یا CLDAP داخلِ یک کانتینر — هرکدام از اینها به یک درخواستِ کوچکِ جعلی، پاسخی بسیار بزرگتر میدهد که به سمتِ کسِ دیگری نشانه رفته. از سمتِ شما، نشانهها وارونهاند: پهنایباندِ خروجی بالاست، ورودی متوسط است، اپلیکیشنِ شما خوب کار میکند، و اولین سیگنالِ واقعی یک اخطارِ سوءاستفاده یا یک پورتِ معلقشده است.
این بررسی یک دقیقه طول میکشد و جایش در همان رانبوک است، چون همان دستوری است که در طولِ تریاژ از قبل اجرایش کردهاید. ss -tulpn باید هیچچیزی را که خودتان عامدانه آنجا نگذاشتهاید، روی یک آدرسِ عمومی بایندشده نشان ندهد، و سرویسهای UDP سزاوارِ شکِ ویژهای هستند چون همانهاییاند که تقویت میکنند. یک ریزالورِ recursive باید فقط به localhost یا یک آدرسِ تونل بایند شده باشد؛ memcached و Redis اصلاً نباید هرگز از اینترنت قابلدسترس باشند؛ و هر کانتینری که با -p 0.0.0.0: یک پورت را منتشر کند، درست یک سوراخ در فایروالی که پیکربندی کرده بودید ایجاد کرده، چون Docker قوانینِ خودش را جلوترِ از قوانینِ شما مینویسد. آن موردِ آخر هر بار همه را غافلگیر میکند.
همین الگو، سیلهای خروجی از ماشینی را هم پوشش میدهد که از قبل نفوذ به آن انجام شده، که دلیلِ دیگرِ null-routeکردنِ ناگهانیِ یک آدرس توسطِ یک ارائهدهنده است. اگر نمودارِ خروجیِ شما بالاست و اپلیکیشنِ شما بیکار است، خواندنِ کانفیگ را متوقف کنید و بررسیِ پردازهها را شروع کنید — آن یک نفوذ است، نه یک مسئلهٔ ظرفیت، و واکنشِ درست بازسازیِ باکس از یک بکآپِ known-good است، نه فیلترکردنش.
SP·08بعد از اینکه متوقف شد: پستمورتم و جعبهابزارِ همیشهآماده
حملهها متوقف میشوند. معمولاً مهاجم حوصلهاش سر میرود، گاهی کاهشِ حمله آن را بیفایده میکند، و گاهی هم صرفاً یک اشتراکِ booterِ با مدتِ ثابت بوده که تمام شده. وسوسه در آن لحظه این است که همهچیز را دقیقاً همانطور که هست رها کنید و بروید بخوابید، و دقیقاً همینطور است که یک rate limitِ موقت، نه ماه بعد، به یک 429ِ دائمی، فراموششده و بیصدا برای یک کشورِ کامل تبدیل میشود. بیست دقیقه وقت بگذارید و حلقه را ببندید، حالا که تازه است.
ارزش دارد سه مصنوع (artefact) تولید کنید. یک baseline: درخواستهای عادیِ شما بر ثانیه، نرخِ عادیِ بهازای هر کلاینت، پهنایباندِ عادیِ شما در اوج. بدونِ این اعداد، هر محدودیتی که در حادثهٔ بعدی تنظیم کنید یک حدس است، و نیمی از آنها در همان جهتی اشتباه خواهند بود که به مشتریها آسیب میزند. یک فهرستِ بازگردانی (revert): هر چیزی که تغییر دادهاید، همراه با تاریخ و دلیل، تا کانفیگِ اضطراری بیسروصدا به کانفیگِ دائمی تبدیل نشود. یک رانبوک که چهار دستور طول دارد و جایی زندگی میکند که وقتی سایت خواب است بتوانید بهش دسترسی داشته باشید — نه روی سرور، و نه فقط توی سرِ خودتان.
سپس دو شکافِ ساختاری را ببندید، چون این لایه دقیقاً همینجای پشته مینشیند. کاهشِ حمله همان چیزی است که حجم را جذب میکند، یک لبه همان چیزی است که سیل را از دادههای شما دور نگه میدارد، و باکسِ پشتِ اینها هنوز هم باید درستوحسابی سختسازی شده باشد و بکآپهای قابلبازیابیِ خارج از سایت داشته باشد، چون حادثهٔ بعد از این ممکن است اصلاً یک سیل نباشد. سروری که از یک DDoS جان سالم بهدر میبرد و یک ماه بعد دیسکش را از دست میدهد، هرگز تابآور نبوده — دو بار شانس آورده بوده.
SP·09گامبهگام
-
01
پیش از آنکه چیزی را تغییر دهید، تأیید کنید
پیش از دستزدن به یک فایلِ کانفیگ، یک تصویرِ صادقانه از ماشین بهدست بیاورید. دنبالِ یک جهشِ پلکانی در بستهها یا درخواستها میگردید، و دنبالِ اینکه زمان کجا صرف میشود — اپلیکیشنی که soft interruptها CPUاش را گرسنه نگه داشتهاند، حادثهای کاملاً متفاوت است از اپلیکیشنی که منتظرِ یک پایگاهداده است.
# is the box alive, and where is the time going? uptime # load average against your core count vmstat 1 5 # 'in' and 'cs' high, 'id' near zero = packet work mpstat -P ALL 1 3 # %soft pinned on one core = interrupt saturation # is the pipe full, or just busy? ip -s link show eth0 # rx bytes, and the errors/dropped counters ethtool eth0 | grep -i speed # requests per second from your own log, minute by minute tail -n 20000 /var/log/nginx/access.log \ | awk -F'[][]' '{print $2}' | cut -d: -f2-4 | uniq -c | tail -5پیش از آنکه نتیجه بگیرید حمله است، لاگِ استقرارِ خودتان و جدولِ cronِ خودتان را چک کنید. قطعیهای خودزده روی یک VPSِ تنها از سیلها رایجترند، و از بیرون یک شکل بهنظر میرسند.
-
02
شکلِ ترافیک را در شصت ثانیه اندازه بگیرید
حالا دستهبندیاش کنید. سه پرسش: چند منبعِ متمایز، چه پروتکل و چه وضعیتی، و — اگر HTTP است — کدام مسیرها و کدام agentها. پاسخها تعیین میکنند سراغِ کدام کنترل بروید، و جمعآوریشان حدود یک دقیقه طول میکشد.
# top source addresses on the wire right now timeout 20 tcpdump -nn -i eth0 -c 20000 2>/dev/null \ | awk '{print $3}' | rev | cut -d. -f2- | rev \ | sort | uniq -c | sort -rn | head -20 # TCP state census: a wall of SYN-RECV is a SYN flood ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn # layer 7: talkers, paths, agents over the last 50k requests L=/var/log/nginx/access.log tail -n 50000 $L | awk '{print $1}' | sort | uniq -c | sort -rn | head -20 tail -n 50000 $L | awk '{print $7}' | sort | uniq -c | sort -rn | head -20 tail -n 50000 $L | cut -d'"' -f6 | sort | uniq -c | sort -rn | head -10نتیجه را در برابرِ نشانهها بخوانید: رشتههای کوئریِ یکتا روی یک مسیر، فهرستِ user-agentی بدون دنبالهٔ بلند، هیچ درخواستی برای static assetهای شما، یا نرخی بهازای هر آدرس که بهطرزِ عجیبی یکنواخت است. اگر پهنایباند روی پورتهایی بالاست که به آنها گوش نمیدهید، همینجا متوقف شوید — آن یک سیلِ حجمی است و مرحلهٔ شش تنها مرحلهای است که اهمیت دارد.
-
03
چیزِ ارزانی سرو کنید، و اتصالها را سقف بگذارید
اول بیشترین اهرم. یک میکروکشِ سیثانیهای، سیلی از درخواستهای ناشناسِ یکسان را به یک برخوردِ origin فروپاشیده میکند، و timeoutهای سفتوسخت حملاتِ کند را میکشند که یک rate limit نمیتواند ببیندشان. هر دو را در بلوکِ
httpبگذارید، بعد reload کنید نه restart، تا اتصالهای گرمِ خود را نگه دارید.# /etc/nginx/nginx.conf — http block limit_conn_zone $binary_remote_addr zone=perip:10m; limit_req_zone $binary_remote_addr zone=flood:20m rate=10r/s; client_header_timeout 10s; client_body_timeout 10s; send_timeout 10s; keepalive_timeout 20s; reset_timedout_connection on; proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=hot:64m max_size=2g inactive=10m use_temp_path=off;# the server block — cap concurrency, serve the cached copy limit_conn perip 20; location / { proxy_cache hot; proxy_cache_valid 200 301 302 30s; proxy_cache_lock on; proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; add_header X-Cache $upstream_cache_status; proxy_pass http://127.0.0.1:8080; }nginx -t && systemctl reload nginx curl -sI https://example.com/ | grep -i x-cache # want: HIT on the second call
-
04
کلاینتِ واقعی را rate-limit کنید، نه پروکسیِ خودتان را
اگر چیزی جلوی nginx نشسته باشد، هر درخواست از یک آدرس میرسد و یک محدودیتِ بهازای هر آدرس، بهجای مهاجم، پروکسی را throttle میکند — یا کلاً بنش میکند و سایت را درست وقتی از آن دفاع میکنید میخواباند. فقط به هدرِ forwardشده از آدرسِ پروکسی اعتماد کنید، هرگز از اینترنت، بعد محدودیت را اعمال کنید.
# /etc/nginx/conf.d/realip.conf — the tunnel or edge address only set_real_ip_from 10.66.0.1; real_ip_header X-Forwarded-For; real_ip_recursive off;
# burst absorbs bursty humans; nodelay keeps the page fast for them location / { limit_req zone=flood burst=20 nodelay; limit_req_status 429; } # the expensive paths get a much tighter bucket of their own location ~ ^/(search|login|register|api/) { limit_req zone=flood burst=5; limit_req_status 429; }بعد ببینید همین الان چه کردید:
tail -f /var/log/nginx/error.log | grep limiting. اگر آدرسهایی که محدود میشوند شبیهِ مشتریهای شما بهنظر میرسند، نرخ خیلی پایین است — بالایش ببرید. محدودیتی که کاربرانِ واقعی را میبندد، قطعیای است که خودتان ایجادش کردهاید. -
05
محدود مسدود کنید، و به هر بلاک یک انقضا بدهید
فقط وقتی ارزشِ انجامدادن دارد که مرحلهٔ دو منابعِ متمرکز نشان داده باشد. از یک ست استفاده کنید، نه هزار قانون — یک lookupِ
ipsetزمانِ ثابت دارد، یک زنجیرهٔ بلندِiptablesبرای هر بسته پیموده میشود و خودش تبدیل به یک denial of service میشود. به هر ورودی یک timeout بدهید تا اضطرارِ امروز به فهرستِ بلاکِ بیصدای سالِ بعد تبدیل نشود.ipset create flood hash:net timeout 3600 -exist iptables -I INPUT -m set --match-set flood src -j DROP # feed it from the census: /24s you actually verified, not guesses for n in 203.0.113.0/24 198.51.100.0/24; do ipset add flood $n -exist; done # SYN flood: let the kernel do the part it is good at sysctl -w net.ipv4.tcp_syncookies=1 sysctl -w net.ipv4.tcp_max_syn_backlog=8192 sysctl -w net.core.somaxconn=8192 ipset list flood | head -20 # keep a copy of this for the post-mortem
مقاومت کنید در برابرِ geo-blockکردنِ یک کشورِ کامل، مگر آنکه بتوانید نامِ مشتریهایی را که قطع میکنید ببرید. و هرگز برای آزمایشِ یک فرضیه
ufw disableنکنید: یک باکسِ بدونِ فایروال زیرِ حملهٔ فعال، دقیقاً همان راهی است که یک حادثهٔ پهنایباند به یک نفوذ (breach) تبدیل میشود. -
06
به لایهای اسکالیت کنید که واقعاً میتواند آن را جذب کند
اگر شمارندههای اینترفیس میگویند لینک اشباع شده، یا drops درحالیکه CPUِ شما بیکار است بالا میرود، شما به سقفِ هر کاری که میتوانید روی سرور انجام دهید رسیدهاید. آن قرائت را تأیید کنید، بعد بهجای ادامهٔ تنظیم، اسکالیت کنید.
# drops in the stack itself — second column is 'dropped' awk '{print strtonum("0x" $2)}' /proc/net/softnet_stat | paste -sd+ | bc # interface-level loss against link speed ip -s link show eth0 | sed -n '3,6p'وقتی اسکرابینگِ بالادستی برقرار باشد، معمولاً کاری برای انجامدادن نیست: تشخیص همیشهروشن است و سیل در شبکه، پیش از آنکه به پورتِ شما برسد، دراپ میشود — روی ناوگانِ ما این 1.5 Tbps ظرفیت است که جلوی هر پلنی نشسته، پس حادثه اغلب فقط بعداً و بهشکلِ یک نمودار قابلدیدن است. اگر حمله یک سیلِ HTTPِ خوبساخته باشد نه یک سیلِ حجمی، این همان موردی است که یک سپرِ L7 برایش ساخته شده، چون سیلهای درخواست در سطحِ بسته از کاربرها قابلتشخیص نیستند و باید بالاتر از آن قضاوت شوند. اگر اصلاً هیچ کاهشِ حملهای ندارید، گزینههای واقعبینانهٔ شما ایناند که پشتِ لبهای بروید که دارد، یا صبر کنید — و اولی را پیش از حملهٔ بعدی برنامهریزی کنید.
-
07
حلقه را ببندید: تأیید کنید، برگردانید، بعد بنویسیدش
از بیرونِ ماشین تأیید کنید، نه از یک شلِ رویش. بعد اقداماتِ اضطراری را عامدانه واگردانید، آنهایی را که همیشه ایدهٔ خوبی بودند نگه دارید، و عددها را ثبت کنید تا حادثهٔ بعدی از یک baseline شروع شود نه از یک حدس.
# from somewhere else entirely: is the site healthy for a normal user? curl -s -o /dev/null -w 'code=%{http_code} ttfb=%{time_starttransfer}s\n' \ https://example.com/ # did you leave a limit that is biting real people? grep -c 'limiting requests' /var/log/nginx/error.log # what is still blocked, and when does it expire? ipset list flood | head -30کش، timeoutها و syncookieها را نگه دارید — اینها بهبودهای دائمیاند. rate limitهای تهاجمی را به baselineِ اندازهگیریشدهٔ خودتان بهعلاوهٔ کمی فضا برگردانید، و بگذارید ورودیهای ipset خودشان منقضی شوند. بعد چهار دستورِ مرحلههای یک و دو را در یک رانبوک بنویسید که جایی غیر از همین سرور نگهداری میشود، کنارِ نرخِ عادیِ درخواستبرثانیه و پهنایباندِ اوجِ عادیِ خودتان. حملهٔ بعدی صرفاً به این دلیل که این دو عدد وجود دارند، حادثهای کوتاهتر خواهد بود.


