پنهان کردن مبدأ واقعاً چه چیزی نصیبتان میکند
سه چیز، و ارزش دارد دربارهٔ آنها دقیق بود. اول، سیل حملات دیگر به ماشینی نمیرسد که توان جذبش را ندارد: یک VPS تنها یک آپلینک (uplink) با ظرفیت محدود دارد، و وقتی آدرسی که بستهها را میبلعد یک لبهٔ ساختهشده برای همین کار باشد نه همان باکسی که پایگاهدادهٔ شما را نگه میدارد، یک حملهٔ حجمی به مسئلهٔ مهندسیِ یک نفر دیگر تبدیل میشود. دوم، اپلیکیشن دیگر با دورزدنِ دفاعهای خودش قابلدسترس نیست — محدودیتهای نرخ، قوانین ضدربات، یک WAF و مسدودسازی جغرافیایی، همه را هرکسی که بتواند مستقیماً به مبدأ متصل شود بهسادگی دور میزند، و بیشتر کسانی که اینها را مستقر میکنند هرگز بررسی نمیکنند که آیا این کار هنوز ممکن است یا نه. سوم، آدرسی که محتوای شما را سرویس میدهد دیگر همان آدرسی نیست که دادههای شما را نگه میدارد، و همین جدایی است که باعث میشود یک شکایت سوءاستفاده، یک اسکن یا یک کاوش هدفمند، جایی ارزان و قابلجایگزین فرود بیاید.
و حالا نیمهٔ صادقانهاش. پنهانکردن یک مبدأ ناشناسی نیست — یک آدرس را پنهان میکند، نه یک شخص را، و ردِ پرداخت، ثبت دامنه و حسابِ پشتِ اینها، مسئلهای جداست که راهنمای مخصوص خودش را دارد. اپلیکیشن شما را وصله نمیزند: یک مبدأ که هیچکس نمیتواند پیدایش کند، بهمحض اینکه کسی پیدایش کند همچنان قابلاکسپلویت است، و مبدأها واقعاً پیدا میشوند. هیچچیزی را از ارائهدهندهٔ شما پنهان نمیکند، که طبق تعریف میداند کدام ماشین روی کدام آدرس پاسخ میدهد. با آن مثل یک لایه رفتار کنید که هزینهٔ یک حمله را بالا میبرد، لایهای روی باکسی که از قبل درستوحسابی سختسازی شده — نه جایگزینی برای هیچکدام.
SP·02هر راهی که IP مبدأ از آن نشت میکند
دلیل اینکه اینهمه مبدأِ «پنهانشده» واقعاً پنهان نیستند این است که آدمها یک کانال را میبندند و فرض میکنند بقیه هم به همان راه رفتهاند. اینطور نیست. این فهرستی است که ما مرور میکنیم، تقریباً به ترتیب اینکه هرکدام چند بار واقعاً کار دست کسی دادهاند:
- DNS تاریخی. جمعآورهای DNS منفعل مدتها پیش از آنکه پشت یک پروکسی بروید، رکورد A شما را ثبت میکردهاند. آدرسی که سال گذشته استفاده میکردید، یک جستوجوی دائمی، قابلجستوجو و رایگان است.
- شفافیت گواهی. هر گواهیِ عمومیِ مورداعتماد در لاگهای عمومیِ append-only منتشر میشود، همراه با هر نام میزبانی که پوشش میدهد. گواهیای که برای
origin.example.comصادر شده، این نام را به دنیا اعلام میکند، و رکورد A آن بقیهٔ کار را انجام میدهد. - سابدامینهایی که هرگز جابهجا نشدند.
mail،ftp،webmail،cpanel،dev،staging،vpn،monitor— دامنهٔ اصلی پشت CDN رفت و اینها همچنان به سمت همان باکس اشاره میکردند. - ایمیل. یک رکورد MX روی مبدأ، آدرس را مستقیماً لو میدهد؛ یک هدر
Received:در ایمیلی که اپلیکیشن شما فرستاده هم همین کار را میکند، و هرکسی میتواند آن را با یک فرم بازنشانیِ رمز عبور فعال کند. - درخواستهای خروجی. webhookها، دریافت آواتار، خواندن RSS، پیشنمایش لینک، بررسی بهروزرسانی، callbackهای OAuth. هرکدام آدرس مبدأ را به هرکس که سرِ دیگر را اداره میکند لو میدهد — و یک قابلیتِ پیشنمایش URL به یک مهاجم اجازه میدهد آن سرِ دیگر را خودش انتخاب کند.
- پاسخدادن روی آدرس لخت. اگر مبدأ همچنان به درخواستی بدون هدر
Hostمطابق، سایت شما را سرویس بدهد، اسکنرهای سراسر اینترنت از قبل آن را ایندکس کردهاند: هش favicon، عنوان صفحه، اثر انگشت گواهی و ترتیب هدرهای HTTP، همه قابلجستوجو هستند. - رکورد IPv6ای که فراموشش کردید. رکورد A به سمت پروکسی رفت؛ رکورد AAAA همچنان به خانه اشاره میکند.
- اپلیکیشنی که دربارهٔ خودش حرف میزند. URLهای absolute در تنظیمات یک CMS، ریدایرکت به یک نام میزبانِ داخلی، stack traceها، بنرهای
Server، source mapها، یک نقطهٔ پایانیِ وضعیت بدون احراز هویت.
توجه کنید بیشتر اینها چه وجه مشترکی دارند: آنها دائمی هستند. لاگهای گواهی append-only هستند و DNS منفعل یک بایگانی است. شما نمیتوانید آدرسی را که یکبار منتشر شده پس بگیرید — تنها میتوانید دیگر از آن استفاده نکنید، و دقیقاً به همین دلیل است که ترتیب کارها در ادامه اهمیت دارد.
SP·03دو شکل از لبه: یک CDN، یا باکسی که خودتان مالکش هستید
یک CDN تجاری ظرفیت anycastی به شما میدهد که هیچ سرور تکی نمیتواند با آن برابری کند، در دهها شهر، اغلب حتی روی یک پلن رایگان. بهایش این است که TLS روی زیرساختی terminate میشود که شما کنترلش نمیکنید: اپراتور متنِ سادهٔ (plaintext) شما را میبیند، میداند متعلق به کدام حساب است، و میتواند بر اساس همین دانش مجبور به اقدام شود یا صرفاً یک روز صبح تصمیم بگیرد که محتوای شما خوشآمد نیست. یک مشکل ظریفتر هم هست که مخصوص جلوبندهای مشترک است — اگر فایروال مبدأ شما بازهٔ آدرسهای منتشرشدهٔ آن CDN را مجاز کند، آنگاه هرکس دیگری که روی همان CDN حساب داشته باشد داخل allowlist شما قرار میگیرد و میتواند نام میزبان خودش را به سمت مبدأ شما نشانه بگیرد. این یک دورزدنِ واقعی است، نه نظری، و دقیقاً به همین دلیل است که origin pull با احراز هویت وجود دارد.
لبهای که خودتان اجرایش میکنید، بهای معکوس را دارد. هیچکس جز شما کلید خصوصی را در دست ندارد، باکس در حوزهٔ قضاییای مینشیند که خودتان عامدانه انتخابش کردهاید، و برای کوچکترین پلن $8.00 در ماه هزینه دارد — واقعاً یک خطای گردکردن است در برابر چیزی که از آن محافظت میکند. چیزی که به دست نمیآورید anycast است: یک سیل 200 Gbpsی، فارغ از اینکه پیکربندی nginx شما چقدر شیک باشد، آپلینک لبه را اشباع میکند، پس جذب در سطح شبکه باید از جایی دیگر بیاید. در مورد ما، آن جای دیگر 1.5 Tbps کاهش حملهٔ بالادست است که جلوی تکتک ماشینهای ناوگان قرار دارد، و همین است که یک لبهٔ خوداجراشونده را بهجای یک نقطهٔ شکستِ واحد، عملی میکند. این دو شکل با هم هم ترکیب میشوند: CDN جلو برای دسترسی و حجم، گرهٔ خودتان پشت آن برای همان بخشی که حاضر نیستید تحویل بدهید. بر اساس اینکه کدام شکست را ترجیح میدهید توضیح دهید، انتخاب کنید.
SP·04تونل همان بخشی است که مردم اشتباه میگیرند
راهاندازی رایج این است که مبدأ روی 0.0.0.0:443 گوش میدهد و یک فایروال آدرسهای پروکسی را در allowlist میگذارد. کار میکند، و همین ضعیفترین حلقهٔ کل طراحی است. Allowlistها دستخوش drift میشوند — بازههای منتشرشده تغییر میکنند و بهروزرسانی هیچوقت اعمال نمیشود؛ مشترکاند، پس روی یک CDN عمومی هر مشتری دیگری را هم میپذیرند؛ و درست از همان جهتی شکست میخورند که اهمیت دارد، چون مبدأ در تمام این مدت همچنان یک شنوندهٔ عمومیِ زنده است، منتظر یک پیکربندی غلط یا یک ufw disable در میانهٔ یک نشستِ عیبیابی.
نسخهای که دوام میآورد، دقیقاً برعکس این است: مبدأ اصلاً هیچ شنوندهٔ عمومیای ندارد. یک تونل WireGuard میان لبه و مبدأ برقرار میشود، وبسرور فقط به آدرس تونل بایند میشود، و رابط عمومی روی هر دو خانوادهٔ IP یک سیاست default-deny دارد، بدون هیچ استثنایی برای 80 یا 443. آنگاه دسترسپذیری دیگر قانونی نیست که کسی ممکن است فراموش کند تمدیدش کند — بلکه نبودِ یک مسیر است. WireGuard ابزار درستی برای این کار است چون یک ماژول کرنل با سطح حملهٔ بسیار کوچک است، در برابر اسکنرهای بدوناحرازهویت ساکت میماند (یک بستهٔ بدوناحرازهویت اصلاً هیچ پاسخی نمیگیرد، پس پورت UDP حتی بهنظر هم نمیرسد وجود داشته باشد)، و هزینهاش چند میکروثانیهٔ تکرقمی بهازای هر بسته است. اگر پیش از این یکی را راهاندازی نکردهاید، راهنمای WireGuard مبانی را پوشش میدهد؛ اینجا فقط به یک پیوند نقطهبهنقطه با دو همتا نیاز داریم.
یک قاعده پیش از شروع، و همان چیزی است که بعدازظهرتان را نجات میدهد: تمام این مدت یک نشست دومِ SSH را باز نگه دارید. قفلکردن SSH پشت تونلی که هنوز در حال پیکربندی دوبارهٔ آن هستید، دقیقاً همان راهی است که آدمها یک ماشین را از دست میدهند، و روی میزبانی که هیچ هویتی در پرونده ندارد، نه نردبان پشتیبانیای برای بالا رفتن وجود دارد و نه نشست کنسولی که کسی بتواند در اختیارتان بگذارد — راه بازگشت، یک استقرار دوباره و یک بازیابی است، که اگر بکآپهایتان بهروز باشد سریع است و اگر نباشد قطعی.
SP·05گواهیها، و لاگی که نامهای میزبان شما را منتشر میکند
شفافیت گواهی واقعاً چیز خوبی است که با کمال میل هفتهٔ شما را خراب میکند. هر گواهیای که یک CA عمومی صادر میکند به لاگهای append-onlyای فرستاده میشود که هرکسی میتواند در آنها جستوجو کند، و آن ورودی شامل تکتک نامهای درون گواهی است. برای origin.example.com یا direct.example.com یکی صادر کنید و همین نام میزبانی را که سعی داشتید تبلیغ نکنید، بهشکلی دائمی و ساختاریافته منتشر کردهاید. بدتر اینکه، عادتِ گذاشتنِ نامهای میزبانِ staging و مدیریتی در همان یک لیست SAN، یک تمدیدِ بیدقت را به یک نقشه از زیرساخت شما تبدیل میکند.
نظم لازم ساده است. گواهیهای عمومی فقط روی لبه زندگی میکنند، و فقط همان نامهایی را پوشش میدهند که عموم واقعاً استفاده میکند. مبدأ یک گواهی خودامضا میگیرد یا یکی از یک CA خصوصیِ کوچک، که با proxy_ssl_trusted_certificate در پیکربندی پروکسی پین شده — چیزی روی پیوندی که فقط پروکسیِ خودتان با آن حرف میزند، نیازی به مورداعتمادعمومیبودن ندارد، و صدور یک گواهی عمومی برایش چیزی جز یک ورودی در یک لاگ نصیبتان نمیکند. اگر به سابدامینهای عمومیِ زیادی نیاز دارید، یک گواهی wildcard بهجای سی نام، یک نام منتشر میکند. و ACME را روی چالش DNS-01 تنظیم کنید: چالش HTTP-01 نیاز دارد چیزی روی پورت 80 برای همان نام میزبانی که در حال اعتبارسنجی است پاسخ بدهد، که دقیقاً همان شنوندهٔ عمومیای است که همین الان حذفش کردید. در آخر، این عدمِ تقارن را بپذیرید — لاگها append-only هستند، پس نام میزبانی که یکبار منتشرش کردهاید دیگر قابلپسگرفتن نیست. اگر به مبدأ resolve میشده، مبدأ به یک آدرس تازه نیاز دارد.
ایمیل، و بقیهٔ سرویسهایی که روی آدرس اشتباه پاسخ میدهند
ایمیل کلاسیکترین راه دور زدن است، چون طبق تعریف باید در دسترس باشد. اگر رکورد MX دامنهٔ شما به مبدأ اشاره کند، بازی پیش از شروع تمام شده — آن رکورد عمومی است و یک dig به کل شکار پایان میدهد. حتی با MXای در جای دیگر، اپلیکیشنی که مستقیماً از مبدأ ایمیل میفرستد، آدرس میزبانِ فرستنده را در زنجیرهٔ Received: هر پیام مهر میزند، و هر فرمی که به تقاضا برای یک کاربر ایمیل بفرستد، همین را به یک جستوجوی خودسرویس تبدیل میکند. راهحل این است که مبدأ را یک کلاینت کنید و هرگز یک سرور: ایمیل خروجی را از طریق یک سرویس submission یا یک باکسِ جدا relay کنید، MX را روی ماشینی نگه دارید که اجازه دارد پیدا شود، و پیش از آنکه کارش را تمامشده اعلام کنید، هدرهای کامل یک پیام آزمایشی را بخوانید. اجرای ایمیل خودتان در مقیاس، خودش یک پروژهٔ جداست، و جایش روی ماشینی که در حال پنهانکردنش هستید نیست.
سپس یک جستوجوی کامل برای هر چیز دیگری انجام دهید که بیسروصدا در حال گوشدادن است. عاملهای مانیتورینگ، داشبوردهای کانتینر، پورتهای پایگاهدادهای که "موقتاً" باز شدهاند، یک نقطهٔ پایانی متریک روی 9100، یک پنل کنترل روی یک پورت بالا، یک دیمن SSH روی رابط عمومی. هرکدام از اینها یک سرویس است که روی همان آدرسی پاسخ میدهد که سعی دارید خصوصی نگهش دارید، و اسکنرها پورتهای بالا را به همان راحتیِ پورتهای پایین پیدا میکنند. ممیزیاش یک دستور است — ss -tulpn — و خروجیِ درست فهرستی است که در آن هیچچیز به یک آدرس عمومی بایند نیست. مرحلهٔ سومِ زیر همان چیزی است که این را درست میکند و درست نگهش میدارد.
Egress: اتصالهایی که مبدأ شما آغاز میکند
یک مبدأ که هیچچیز را نمیپذیرد، همچنان میتواند خودش را لو بدهد، چون فقط اتصال دریافت نمیکند — اتصال هم باز میکند. Mirrorهای پکیج، NTP، یک webhook به یک پردازشگر پرداخت، یک API ربات، تصویری که برای پیشنمایش لینک گرفته میشود، یک بررسیِ مجوز، یک نشستِ SMTP خروجی، یک Git remote، یک گزارشدهندهٔ خطا. برای سرِ دیگرِ هرکدام از اینها، آدرس عمومیِ مبدأ چیزی نیست جز آدرس فرستندهٔ آن اتصال. بیشتر اوقات این بیضرر است، چون خودتان سرِ دیگر را انتخاب کردهاید و به آن اعتماد دارید. مشکل، همان تعداد اندکی نقطهٔ پایانی است که یک مهاجم انتخاب میکند: یک لینک را در هر چیزی که پیشنمایش رندر میکند بچسبانید، یک webhook ثبت کنید، یا یک server-side request forgery در یک واردکنندهٔ تصویر پیدا کنید، آنگاه مبدأ resolve میشود و به میزبانی متصل میشود که مهاجم زیر نظر دارد. این یک ناشناسیزدایی دودقیقهای است، بدون آنکه هیچ اکسپلویتی درکار باشد.
دو پاسخ قابلدفاع وجود دارد. پاسخ سختگیرانه، تمام egress را از روی تونل عبور میدهد و میگذارد لبه آن را NAT کند، پس آدرس فرستندهٔ خروجیِ مبدأ همان آدرس لبه میشود — یک تنظیم در پیکربندی peer، بهعلاوهٔ forwarding و یک قاعدهٔ masquerade در سرِ دیگر. wg-quick حلقهٔ مسیریابی را خودش برایتان مدیریت میکند: با یک مسیر 0.0.0.0/0، یک قاعدهٔ fwmark نصب میکند تا بستههای خودِ تونل همچنان مستقیم به نقطهٔ پایانی برسند، که دقیقاً همان بخشی است که آدمها وقتی مسیرها را دستی مینویسند خرابش میکنند. پاسخ عملگرایانه، egress مستقیم را برای ترافیکی که خودتان کنترلش میکنید نگه میدارد و جلوی هر چیزی که یک URLِ ارائهشده از سمت کاربر را میگیرد، یک پروکسی میگذارد. آنچه قابلدفاع نیست، ندانستنِ این است که کدامیک از این دو را دارید. عامدانه تصمیم بگیرید، سپس آن را با یک درخواست به میزبانی که خودتان مالکش هستید و نگاهی به آدرس فرستنده در لاگهای آن تأیید کنید.
چقدر هزینه دارد، و چطور ثابت کنید که کار میکند
ردیف بودجه، یک VPS اضافه است. کوچکترین پلن با $8.00 در ماه، TLS را terminate میکند و برای یک سایت کوچک پروکسی میکند بدون آنکه اصلاً بار را حس کند — یک reverse proxy در اصل چیزی نیست جز کپیکردنِ یک سوکت، و 2 vCPU با 4 گیگابایت RAM، راحت خیلی جلوتر از نقطهای است که مبدأِ پشتش تبدیل به گلوگاه شود. آن را در منطقهای متفاوت از مبدأ قرار دهید تا یک سند حقوقیِ واحد یا یک مشکل در یک تأسیساتِ واحد، به هر دو نرسد، و تأخیر را هم فراموش نکنید: یک پرشِ اضافه، میلیثانیههای واقعی اضافه میکند، پس یک لبه در آمستردام جلوی یک مبدأ در کوالالامپور، یک تصمیم طراحی است، نه یک تصادف. جفتهای داخل یک قاره معمولاً چند میلیثانیهٔ تکرقمی هزینه دارند، و استفادهٔ مجددِ نشستِ TLS که روی لبه به دست میآورید، اغلب همان را در یک بارگذاریِ واقعیِ صفحه پس میدهد.
ثابتکردنِ اینکه کار میکند، همان بخشی است که یک پیکربندی را از یک کنترل جدا میکند، و این یک کارِ تکرارشونده است نه یکبارمصرف — هر سابدامین تازه، هر گواهی تازه، هر یکپارچهسازیِ تازه، یک فرصت تازه برای بازمنتشرکردنِ آن آدرس است. مجموعهآزمونهای مرحلهٔ هفت حدود ده دقیقه طول میکشد: سعی کنید مستقیماً روی آدرس مبدأ به سایت خودتان برسید، هر نام میزبانی را که تابهحال گواهی گرفتهاید فهرست کنید، نامهای بدیهیِ سابدامین را پیمایش کنید، رکورد AAAAای را که فراموش کرده بودید بررسی کنید، و برای خودتان یک ایمیل بفرستید. آن را بعد از هر تغییر زیرساختی اجرا کنید. و همینطور که این کار را میکنید، نتیجه را هم بهخاطر داشته باشید: اگر مبدأ پاسخ بدهد، راهحل درست یک قانون فایروال دیگر نیست — یک آدرس تازه است، چون آدرس قدیمی از قبل در بایگانیِ یک نفر هست. در این پشته، این لایه بعد از سختسازی و در کنار بکآپهای خارج از سایت مینشیند: سختسازی تعیین میکند شکستنِ باکس چقدر سخت است، بکآپها تعیین میکنند چقدر سریع بازیابی میکنید، و این یکی از همان اول تعیین میکند پیداکردنِ باکس چقدر سخت است.
SP·09گامبهگام
-
01
لبه را مستقر کنید و دقیقاً یک کار به آن بدهید
یک VPS دوم را در منطقهای مستقر کنید که مبدأ در آن زندگی نمیکند، و با آن مثل یک دستگاه تکمنظوره رفتار کنید: کاری که TLS را terminate میکند، یک reverse proxy، و نه چیز دیگری. نه پایگاهدادهای، نه کد اپلیکیشنی، نه اسکریپتهای شلی که دلتنگشان شوید. چکلیست اولین ساعت را روی آن اجرا کنید — SSH فقطباکلید، فایروال default-deny روی هر دو خانوادهٔ IP، بهروزرسانیهای امنیتیِ بدوندخالت — سپس دقیقاً سه پورت باز کنید.
ssh root@198.51.100.20 apt update && apt full-upgrade -y hostnamectl set-hostname edge-01 apt install -y nginx wireguard-tools ufw unattended-upgrades ufw default deny incoming && ufw default allow outgoing ufw limit 22/tcp ufw allow 80,443/tcp ufw allow 51820/udp ufw --force enable
-
02
پیش از دستزدن به DNS، تونل را بالا بیاورید
دو همتا، یک پیوند. روی هر ماشین یک جفتکلید تولید کنید و به تونل زیرشبکهٔ کوچک خودش را بدهید — مبدأ در نهایت فقط روی
10.66.0.2در دسترس خواهد بود و هیچجای دیگر. مبدأ به سمت لبه dial out میکند (همان طرفی است که هیچ پورت بازی نخواهد داشت)، پسEndpointو یک keepalive را حمل میکند؛ لبه فقط گوش میدهد.# on both machines umask 077; wg genkey | tee privkey | wg pubkey > pubkey # edge-01 — /etc/wireguard/wg0.conf [Interface] Address = 10.66.0.1/24 ListenPort = 51820 PrivateKey = <edge-privkey> [Peer] PublicKey = <origin-pubkey> AllowedIPs = 10.66.0.2/32 # origin-01 — /etc/wireguard/wg0.conf [Interface] Address = 10.66.0.2/24 PrivateKey = <origin-privkey> [Peer] PublicKey = <edge-pubkey> Endpoint = 198.51.100.20:51820 AllowedIPs = 10.66.0.1/32 PersistentKeepalive = 25
آن را در هر دو سر فعال کنید و پیش از هر کار دیگری دستدادن را تأیید کنید — تونلی که فقط تا ریبوتِ بعدی کار میکند، بدتر از نداشتنِ هیچ تونلی است.
systemctl enable --now wg-quick@wg0 wg show # expect a recent handshake and non-zero transfer ping -c3 10.66.0.1 # from the origin
-
03
مبدأ را از اینترنت عمومی غیرقابلدسترس کنید
این همان مرحلهای است که کار واقعی را انجام میدهد، و همان مرحلهای که آدمها در آن خودشان را بیرون قفل میکنند. یک نشست دومِ SSH باز کنید و پیش از اجرای هر چیزی در ادامه، آن را متصل نگه دارید — اینجا هیچ کنسول پشتیبانیای نیست که بتوانید به آن پناه ببرید. سپس وبسرور را به آدرس تونل bind کنید، هر چیزی روی رابط عمومی را رها کنید، و فقط تونل بهعلاوهٔ خودِ نقطهٔ پایانیِ WireGuard را مجاز کنید.
# /etc/nginx/sites-available/app — listen on the tunnel only listen 10.66.0.2:8080; ufw --force reset ufw default deny incoming ufw default allow outgoing ufw allow in on wg0 to any port 8080 proto tcp ufw allow in on wg0 to any port 22 proto tcp ufw allow from 198.51.100.20 to any port 51820 proto udp ufw --force enable # the audit: nothing may be bound to a public address ss -tulpn | grep -Ev '10\.66\.0\.2|127\.0\.0|\[::1\]'
اگر آن دستور آخر یک سرویس را چاپ کند، یعنی یک نشتی دارید — آدرس bind را درست کنید، نه اینکه دور آن یک قانون فایروال اضافه کنید. دو شنونده اجازه دارند دوام بیاورند: WireGuard روی 51820، و sshd اگر هنوز آن را به داخل تونل منتقل نکردهاید.
-
04
TLS را روی لبه terminate کنید و از روی تونل proxy کنید
گواهیِ عمومی را روی لبه صادر کنید، برای همان نامهایی که عموم واقعاً استفاده میکند، و به سمت آدرس تونل، upstream proxy کنید. بلوکِ سرورِ دوم یک تزیینِ اختیاری نیست: همان چیزی است که جلوی لبه را میگیرد از اینکه سایت شما را به اسکنری سرویس بدهد که با IP و بدون هدر
Hostمتصل میشود، که دقیقاً همینطوری درِ ورودی اثرانگشتگیری میشود.# edge-01 — /etc/nginx/sites-available/example.com server { listen 443 ssl; http2 on; server_name example.com www.example.com; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; add_header Strict-Transport-Security "max-age=63072000" always; location / { proxy_pass http://10.66.0.2:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_http_version 1.1; } } # anything that is not a known hostname gets nothing at all server { listen 80 default_server; listen 443 ssl default_server; ssl_reject_handshake on; return 444; } -
05
IP واقعیِ کلاینت را به مبدأ برگردانید
پشتِ یک پروکسی، هر درخواست از
10.66.0.1میرسد. اگر دستنخورده رهایش کنید، لاگهای access شما بیفایده میشوند، محدودیت نرخِ بهازای هر IP بهجای مهاجم، تونل را خفه میکند، وfail2banدیر یا زود لبه را بن میکند و سایت را میخواباند — یک راهِ واقعاً محبوب برای ایجاد یک outage درست وسط سختسازی. به هدرِ forward شده اعتماد کنید، اما فقط از آدرس تونل، هرگز از باقیِ دنیا.# origin-01 — /etc/nginx/conf.d/realip.conf set_real_ip_from 10.66.0.1; real_ip_header X-Forwarded-For; real_ip_recursive off;
معادلش را در اپلیکیشن هم انجام دهید —
ProxyFixدر Flask،TRUSTED_PROXIESدر Laravel،set_real_ip_fromبهعلاوهٔ فهرستِ trusted-proxy خودِ فریمورک — و محدودیت نرخ را روی لبه بگذارید، همانجایی که آدرس واقعیِ کلاینت بهشکل native وجود دارد:# edge-01 — /etc/nginx/nginx.conf (http block) limit_req_zone $binary_remote_addr zone=front:10m rate=20r/s; # then, inside the location block limit_req zone=front burst=40 nodelay;
-
06
ایمیل و egress را از روی آدرس مبدأ جابهجا کنید
MX را بهسمت ماشینی نشانه بگیرید که اجازه دارد پیدا شود، ایمیل خروجی را از طریق یک relay بفرستید نه مستقیماً از مبدأ، و تمدید گواهی را روی چالش DNS-01 تنظیم کنید تا چیزی مجبور نباشد روی پورت 80 پاسخ بدهد. سپس تصمیم بگیرید با باقیِ ترافیک خروجی چه میکنید. برای عبوردادنِ همهٔ آن از میان لبه،
AllowedIPsمبدأ را گستردهتر کنید و بگذارید لبه masquerade کند —wg-quickهمان قاعدهٔ fwmark را نصب میکند که خودِ تونل را قابلدسترس نگه میدارد، پس لازم نیست خودتان دستی برای نقطهٔ پایانی یک مسیر بنویسید.# origin-01 — /etc/wireguard/wg0.conf, in [Peer] AllowedIPs = 0.0.0.0/0, ::/0 # edge-01 — forward and NAT the tunnel echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/99-fwd.conf && sysctl --system ufw route allow in on wg0 out on eth0 # in /etc/ufw/before.rules, above the *filter block: # *nat # :POSTROUTING ACCEPT [0:0] # -A POSTROUTING -s 10.66.0.0/24 -o eth0 -j MASQUERADE # COMMIT systemctl restart ufw && wg-quick down wg0 && wg-quick up wg0
سپس آن را از روی مبدأ بررسی کنید:
curl -s https://ifconfig.coباید آدرس لبه را برگرداند، نه آدرس خودش را. -
07
بهدنبال مبدأ خودتان بگردید، سپس runbook را بنویسید
همانطور به آن حمله کنید که یک نفر دیگر میکرد. اولین دستور همان دستورِ مهم است — اگر مبدأ وقتی مستقیماً آدرسدهی شود همچنان سایت شما را سرویس بدهد، هیچکدام از موارد بالا هنوز کار نمیکند.
# does the origin answer for your hostname? curl -sk --resolve example.com:443:203.0.113.10 https://example.com/ \ -o /dev/null -w '%{http_code}\n' # want: a timeout, not 200 # every hostname you have ever certified, from the public logs curl -s 'https://crt.sh/?q=%25.example.com&output=json' \ | grep -o '"name_value":"[^"]*"' | sort -u # records that never moved dig +short example.com A; dig +short example.com AAAA; dig +short example.com MX for h in www mail ftp webmail cpanel dev staging vpn monitor origin direct; do printf '%-9s %s\n' "$h" "$(dig +short $h.example.com | tr '\n' ' ')" doneسپس از طریق اپلیکیشن برای خودتان یک ایمیل بفرستید و زنجیرهٔ کاملِ
Received:را بخوانید، و یک لینک به میزبانی که خودتان کنترلش میکنید در هر قابلیتی که پیشنمایش رندر میکند بچسبانید و ببینید کدام آدرس آن را گرفته است. برای هر بررسی، بنویسید یک نتیجهٔ درست چه شکلی است، و کل مجموعه را بعد از هر تغییرِ DNS، هر گواهیِ تازه و هر یکپارچهسازیِ تازه دوباره اجرا کنید. اگر هرکدام از آنها مبدأ را نشان داد، آن را روی یک آدرس تازه از نو بسازید — آدرسِ منتشرشده از قبل بایگانی شده، و هیچ قانونِ فایروالی آن را پس نمیگیرد.


