بازگشت به بلاگ
۵ آبان ۱۴۰۴Sergei Solod5 دقیقه مطالعه

سایت استاتیک Next.js من هر روز هزاران اسکن آسیب‌پذیری می‌گیرد؛ Nginx تقریباً متوجه نمی‌شود

لاگ‌هایم پر از probe برای WordPress، بک‌دورهای PHP، فایل‌های .env و .git/config است. روی سایت استاتیک Next.js + Nginx من، بیشتر این درخواست‌ها به miss و 404 کم‌هزینه ختم می‌شوند و بار CPU هنگام مشاهده ثابت ماند. این تجربه درباره امنیت سایت استاتیک چه چیزی را واقعاً نشان می‌دهد و چه چیزی را نه؟

امنیتNext.jsNginxDevOpsمیزبانی استاتیکسطح حمله

هر روز هزاران اسکن خودکار آسیب‌پذیری در لاگ سرور من ثبت می‌شود. ظاهر درخواست‌ها ترسناک است:

  • /wp-admin/wp.php
  • /111.php
  • /code.php
  • /.git/config
  • /i.php
  • /admin
  • /api/.env
  • /kal.php

چیزی که برایم جالب بود تعدادشان نبود؛ واکنش سرور بود. هنگام مشاهده این ترافیک، بار CPU تغییر قابل‌مشاهده‌ای نداشت.

خود این مشاهده واقعی است، اما توضیح اولیه‌ام بیش از حد مطلق بود. تحویل استاتیک سپر جادویی نیست و پاسخ 404 هم رایگان نیست. مزیت واقعی معماری من دقیق‌تر است: بسیاری از probeهای فرصت‌طلبانه ارزان تمام می‌شوند، چون تقریباً هیچ منطق server-side در مسیر عمومی نیست که بتوانند به آن برسند.

این‌ها probe هستند، نه مدرک compromise

دیدن /wp-admin/wp.php یا /111.php وسوسه‌انگیز است که هر درخواست را «exploit» بنامیم. از نظر فنی دقیق نیست. بیشتر چیزی که می‌بینم probing خودکار است: بات نام فایل‌ها و endpointهای رایج را امتحان می‌کند و می‌بیند چه چیزی پاسخ می‌دهد.

برخی مسیرها واضحاً WordPress یا PHP را هدف گرفته‌اند. مسیرهایی مثل /.git/config و /api/.env دنبال تنظیمات یا secretهایی هستند که ممکن است اشتباهی منتشر شده باشند. /admin عمومی‌تر است و حتی می‌تواند route قانونی یک سایت باشد. درخواست مشکوک نشان می‌دهد scanner دنبال چیست، نه اینکه آسیب‌پذیری واقعاً وجود دارد.

در مورد من، معماری تعیین‌کننده است: سایت عمومی HTML استاتیک pre-render شده با Next.js است و مستقیم توسط Nginx سرو می‌شود.

چرا این ترافیک روی stack من کم‌هزینه است

برای صفحاتی که درباره‌شان صحبت می‌کنم، پشت هر request یک PHP runtime یا database وجود ندارد. Nginx فایل‌های از قبل موجود را سرو می‌کند. اگر scanner مسیر /111.php را بخواهد و فایل وجود نداشته باشد، هیچ اپ PHP شروع نمی‌شود، کد WordPress اجرا نمی‌شود و query دیتابیس هم نداریم. request فقط miss می‌شود و به 404 ختم می‌شود.

این موضوع مدل هزینه را عوض می‌کند. در اپلیکیشن دینامیک، یک request می‌تواند routing، framework، authentication، rendering، database یا serviceهای دیگر را درگیر کند. مسیر استاتیک من قطعات متحرک بسیار کمتری دارد. Nginx هنوز باید connection را بپذیرد، HTTP را پردازش کند، resource را پیدا کند، log بنویسد و response بفرستد؛ اما کار اپلیکیشن کم است.

برای سایت caching تهاجمی هم دارم که به تحویل عادی فایل‌های استاتیک کمک می‌کند. با این حال، هر 404 تصادفی را نتیجه cache نمی‌دانم. یک URI بی‌معنی و جدید ممکن است قبل از 404 از processing معمول Nginx و filesystem lookup عبور کند.

اصلاح مهم: استاتیک به معنی «ذاتاً امن» نیست

این بخشی است که امروز دقیق‌تر می‌نویسم. معماری استاتیک خیلی از اجزای server-side را از مسیر عمومی حذف می‌کند، اما کار امنیتی را حذف نمی‌کند.

  • فایل لو رفته همچنان لو رفته است. اگر .git، فایل .env، backup یا secret داخل دایرکتوری عمومی قرار بگیرد، Nginx می‌تواند آن را مثل HTML سرو کند مگر اینکه configuration مانع شود.
  • Nginx، TLS، سیستم‌عامل و شبکه همچنان attack surface هستند. باید patch و درست configure شوند.
  • کد client-side هم می‌تواند آسیب‌پذیر باشد. HTML استاتیک، JavaScript، منطق auth در مرورگر یا third-party script را خودبه‌خود امن نمی‌کند.
  • resource exhaustion هنوز ممکن است. connection، bandwidth، TLS handshake، worker، log I/O و disk space محدودند. ترافیک کافی می‌تواند به سایت استاتیک هم فشار بیاورد.

پس نتیجه درست «این درخواست‌ها اهمیتی ندارند» نیست؛ نتیجه دقیق‌تر این است: بسیاری از probeهای مخصوص یک اپلیکیشن در این معماری چیزی برای اجرا پیدا نمی‌کنند.

«هزاران اسکن در روز» ممکن است از بار واقعی ترسناک‌تر به نظر برسد

لاگ‌ها noise اینترنت را بزرگ جلوه می‌دهند، چون هر probe ناموفق یک خط جداست. اما تعداد روزانه به‌تنهایی اثر عملیاتی را نشان نمی‌دهد. چند هزار request پخش‌شده در ۲۴ ساعت با همان تعداد در یک burst کوتاه فرق زیادی دارد.

CPU هم فقط یک signal است. در مورد من ثابت ماند و این نشانه مفیدی است که در زمان مشاهده، این ترافیک فشار محاسباتی قابل‌توجهی ایجاد نمی‌کرد. اما ثابت نمی‌کند هزینه در منابع دیگر صفر بوده است. برای ارزیابی کامل‌تر، request rate، bandwidth، active connection، وضعیت Nginx worker، latency، حجم log و مصرف disk را هم نگاه می‌کنم.

از دیدن این noise چه یاد گرفتم

مهم‌ترین درس برای من این است که معماری می‌تواند دسته‌های کامل حملات خودکار را بی‌اثر کند، بدون اینکه برای هر scanner signature قانون جدا بنویسم. بات می‌تواند تمام روز دنبال WordPress و PHP backdoor بگردد؛ اگر مسیر عمومی نه WordPress دارد و نه PHP، این probeها چیز زیادی برای استفاده ندارند.

ولی دیگر «نامربوط به stack من» را با «ترافیک بی‌ضرر» یکی نمی‌دانم. checklist فعلی من:

  • بدانم کدام runtime و service واقعاً از مسیر عمومی reachable هستند.
  • secret، repository metadata، backup و build artifact را بیرون web root نگه دارم یا صریحاً block کنم.
  • probeهای تکراری 404 را تا وقتی evidence دیگری از targeting یا impact نیست، noise عادی اینترنت در نظر بگیرم.
  • فقط با نگاه به CPU نتیجه نگیرم abusive traffic رایگان است.
  • Nginx، OS، TLS library و dependencyها را حتی برای اپ استاتیک patch نگه دارم.

این تجربه چه چیزی را ثابت نمی‌کند

من benchmark کنترل‌شده اجرا نکردم و DDoS را شبیه‌سازی نکردم. فقط production log و server load را هنگام رسیدن هزاران scanner request در روز مشاهده کردم. برای نتیجه عملی درباره setup خودم کافی است، اما قانون عمومی «استاتیک همیشه برنده است» را ثابت نمی‌کند.

چیزی که با اطمینان بیشتری می‌توانم بگویم محدودتر است: در Next.js static export من که Nginx سرو می‌کند، بیشتر scanner traffic مشاهده‌شده به file miss و 404 کم‌هزینه ختم می‌شد، نه به PHP runtime یا اپلیکیشن متصل به database. در زمان مشاهده بار CPU تغییر نکرد.

این همان مزیت resilience است که برای من ارزش دارد. قطعات کمتر سیستم را نفوذناپذیر نمی‌کنند؛ فقط مکان‌های کمتری باقی می‌گذارند که noise تصادفی اینترنت بتواند به کار واقعی اپلیکیشن تبدیل شود.