بستن حفره امنیتی IPv6 در فایروال UFW روی VPS
بسیاری از سرویسهای VPS به دلیل تنظیمات پیشفرض روی IPv6 در دسترس هستند. در این راهنما یاد میگیرید چگونه با پیکربندی صحیح UFW در Ubuntu 24.04 از نشت دسترسی جلوگیری کنید.
دام فایروال IPv6 در یک جمله
فایروال شما از IPv4 محافظت میکند، اما VPS شما تقریباً بهطور قطع دارای یک آدرس IPv6 عمومی نیز هست و بسیاری از سرویسها بهصورت پیشفرض روی آن گوش میدهند. اگر فایروال شما فقط IPv4 را پوشش دهد، یا اگر به فایروال ابری متکی باشید که فقط IPv4 را فیلتر میکند، تمام آن سرویسها از طریق IPv6 برای کل اینترنت قابلدسترسی خواهند بود، در حالی که سمت IPv4 شما امن به نظر میرسد. شما یک پورت را با curl تست میکنید، با خطای refused connection مواجه میشوید و احساس امنیت میکنید؛ اما یک مهاجم از طریق IPv6 به همان پورت متصل شده و بهراحتی وارد میشود.
این راهنما نشان میدهد که این شکاف امنیتی در یک VPS معمولی با Ubuntu 24.04 از کجا ناشی میشود، چگونه دقیقاً متوجه شوید چه چیزی را در معرض دید قرار دادهاید و چگونه آن را مسدود کنید. UFW در اینجا مقصر نیست. در نصب مدرن Ubuntu، ابزار UFW از قبل IPv6 را مدیریت میکند. این نشت امنیتی از لایههای اطراف آن و از سرویسهایی ناشی میشود که نمیدانستید در حال گوش دادن هستند.
چرا VPS شما اساساً روی IPv6 قرار دارد
تقریباً تمام VPSهای امروزی با یک آدرس IPv6 عمومی، که اغلب یک /64 کامل است، در کنار آدرس IPv4 عرضه میشوند. وضعیت خود را بررسی کنید:
ip -6 addr show scope global2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
inet6 2001:db8:2a::1/64 scope globalآن 2001:db8:2a::1 از هر جای اینترنت قابل مسیریابی است، دقیقاً مشابه آدرس IPv4 شما. اکنون بررسی کنید چه چیزی در حال گوش دادن است:
sudo ss -tlnpState Recv-Q Local Address:Port Process
LISTEN 0 0.0.0.0:22 sshd
LISTEN 0 [::]:22 sshd
LISTEN 0 127.0.0.1:5432 postgres
LISTEN 0 [::]:8080 docker-proxyستون Local Address را با دقت بخوانید. 0.0.0.0:22 به معنای «گوش دادن روی تمام آدرسهای IPv4» است. [::]:22 به معنای «گوش دادن روی تمام آدرسهای IPv6» است. 127.0.0.1:5432 به loopback متصل شده و اصلاً عمومی نیست، بنابراین خط مربوط به Postgres امن است. دو خط [::] به کل اینترنت از طریق IPv6 پاسخ میدهند و خط docker-proxy همان موردی است که فراموش کردهاید آن را راهاندازی کردهاید.
بیشتر دیمونها بهصورت پیشفرض به :: متصل میشوند، زیرا در لینوکس یک سوکت :: معمولاً IPv4 را نیز میپذیرد. بنابراین وضعیت پیشفرض یک سرور تازه، «پاسخدهی روی هر دو پشته، در همه جا» است. فایروال شما تنها چیزی است که در برابر این وضعیت قرار دارد، و به همین دلیل است که فایروالی که فقط یک پشته را میبیند، یک مشکل واقعی محسوب میشود.
منشأ واقعی شکاف IPv6 کجاست
چهار منبع رایج برای این مشکل وجود دارد. روی یک سرور خاص، ممکن است با یکی از آنها یا همزمان با چند مورد مواجه باشید.
1. فایروال ابری که فقط IPv4 را فیلتر میکند. بسیاری از فایروالهای ارائهدهندگان خدمات و محصولات گروه امنیتی (security-group) بر پایه IPv4 توسعه یافتهاند و یا IPv6 را نادیده میگیرند و یا نیاز دارند که قوانین IPv6 را بهصورت دستی اضافه کنید. اگر تنها فایروال شما همان پنل مدیریتی ارائهدهنده است و IPv6 را پوشش نمیدهد، سرویسهای [::] شما بدون توجه به وضعیت پورت 22 در IPv4، در دسترس هستند. مستندات فایروال ارائهدهنده خود را مطالعه کنید و بهدنبال عبارت IPv6 بگردید.
2. استفاده از iptables دستی بدون ip6tables. دستور iptables فقط جداول IPv4 را تغییر میدهد. IPv6 دستور کاملاً مجزایی به نام ip6tables دارد که قوانین خاص خود را میطلبد. اگر یک اسکریپت فایروال پر از خطوط iptables -A INPUT ... نوشتهاید و هرگز قوانین متناظر ip6tables را ایجاد نکردهاید، فایروال IPv6 شما خالی است و یک زنجیره INPUT خالی با سیاست پیشفرض ACCEPT، همه چیز را مجاز میشمارد:
sudo ip6tables -L INPUT -nChain INPUT (policy ACCEPT)
target prot opt source destinationاین خروجی، تمام دام را در یک نگاه نشان میدهد. IPv4 فیلتر شده است، اما IPv6 دسترسی همه را میپذیرد.
3. انتشار پورتها توسط Docker و دور زدن فایروال. هنگامی که از docker run -p 8080:80 استفاده میکنید، Docker قوانین خود را پیش از قوانین UFW قرار میدهد؛ بنابراین یک پورت منتشرشده حتی زمانی که ufw status میگوید آن پورت مسدود است، قابل دسترسی خواهد بود و در نسخههای مدرن Docker، همین موضوع برای IPv6 نیز صدق میکند. چرا Docker فایروال UFW را دور میزند و نحوه صحیح فیلتر کردن پورتهای کانتینر این مکانیزم و راهحلهای آن را توضیح میدهد. برای اطلاع از نحوه تعریف این پورتهای منتشرشده، به اصول Docker Compose روی VPS مراجعه کنید.
4. فعال نبودن IPv6 در UFW. ابزار UFW از IPv6 پشتیبانی میکند، اما تنها زمانی که برای آن تنظیم شده باشد. این سوئیچ را بررسی کنید:
grep IPV6 /etc/default/ufwنسخههای مدرن Ubuntu دارای IPV6=yes هستند، بنابراین UFW هر قانون را روی هر دو پشته (stack) اعمال میکند. اگر IPV6=no را مشاهده کردید (که ناشی از ایمیجهای قدیمی یا راهنماهای قدیمی است)، تمام قوانینی که در UFW نوشتهاید فقط برای IPv4 هستند و IPv6 بدون مدیریت باقی میماند.
دقیقاً ببینید چه چیزی را در معرض دید قرار دادهاید
حدس نزنید. آن را از بیرون اندازهگیری کنید. ابتدا لیست listenerهای خود را تهیه کنید و هر موردی که به :: متصل است را یادداشت کنید:
sudo ss -tlnp | grep '::'سپس، از یک ماشین دیگر، به آدرس IPv6 عمومی سرور متصل شوید و پورتی را که فکر میکنید بسته است امتحان کنید:
curl -6 -v http://[2001:db8:2a::1]:8080/اگر این دستور یک صفحه یا بنر برگرداند، پورت روی IPv6 باز است. پورت بسته به شما Connection refused یا یک timeout میدهد. این دو خطا سیگنالهای یکسانی نیستند و تفاوت بین refused و timed out به شما میگوید که آیا میزبان پاسخ داده و شما را رد کرده است یا یک فایروال بسته شما را در سکوت دور انداخته است. برای داشتن تصویری کامل، آدرس IPv6 را با nmap از خارج از سرور اسکن کنید:
nmap -6 2001:db8:2a::1هر پورتی که nmap آن را روی IPv6 باز گزارش میکند، پورتی است که کل اینترنت میتواند به آن دسترسی داشته باشد، فارغ از اینکه اسکن IPv4 شما چه چیزی را نشان داده است. مقایسه اسکنهای IPv4 و IPv6 در کنار هم، سریعترین راه برای یافتن شکاف است: هر چیزی که روی -6 باز است اما روی IPv4 بسته است، سرویسی است که فایروال شما آن را نادیده گرفته است.
پر کردن شکاف امنیتی
تنظیمات UFW را برای پوشش هر دو پشته اعمال کنید و سیاست پیشفرض را روی deny قرار دهید. تغییر را تأیید کنید، سپس سیاست ورودی پیشفرض را روی deny تنظیم کرده و فقط دسترسیهای ضروری را مجاز کنید:
sudo sed -i 's/^IPV6=no/IPV6=yes/' /etc/default/ufw
sudo ufw default deny incoming
sudo ufw allow 22/tcp
sudo ufw enable
sudo ufw status verboseاگر هنگام تغییر IPV6=yes، سرویس UFW از قبل فعال بوده باشد، تغییرات تا زمانی که sudo ufw reload را اجرا نکنید اعمال نخواهند شد.
ufw status هر قانون را دو بار فهرست میکند؛ یک بار به صورت عادی و یک بار با پسوند (v6). وقتی خطوط (v6) را مشاهده میکنید، یعنی UFW در حال فیلتر کردن IPv6 است:
22/tcp ALLOW IN Anywhere
22/tcp (v6) ALLOW IN Anywhere (v6)اگر مدیریت iptables را بهصورت دستی انجام میدهید، تمام قوانین را در ip6tables نیز تکرار کنید، یا به سراغ nftables بروید که جداول inet آن، IPv4 و IPv6 را در یک مکان پوشش میدهند و این دسته از خطاها را بهطور کامل حذف میکنند. هنگام نوشتن دستی قوانین، استفاده از یک جدول فیلتر inet در nftables تمیزترین راهکار است. اگر VPS شما به جای Ubuntu از Rocky یا AlmaLinux استفاده میکند، ابزار UFW برای پیکربندی وجود ندارد و در عوض firewalld رابط مدیریتی شما خواهد بود که قوانین zone خود را بهطور همزمان روی هر دو پشته اعمال میکند.
سرویسهایی که نمیخواهید عمومی باشند را روی loopback محدود کنید. یک پایگاه داده، پنل مدیریت یا endpoint مربوط به متریکها، بهندرت به آدرس عمومی نیاز دارند. آنها را روی 127.0.0.1 و ::1 bind کنید تا از همان ابتدا روی آدرسهای قابل مسیریابی گوش ندهند. برای Postgres، مقدار listen_addresses = 'localhost' را تنظیم کنید. برای یک سرور برنامه، آن را روی 127.0.0.1 bind کرده و یک reverse proxy در مقابل آن قرار دهید. بستن listener بهتر از فایروال کردن آن است، زیرا در این صورت هیچ چیزی برای دسترسی وجود نخواهد داشت.
برای محافظت از پورتهای منتشر شده توسط Docker به UFW اعتماد نکنید. پورتهای container را به جای همه رابطها، روی یک آدرس خاص منتشر کنید (مثلاً -p 127.0.0.1:8080:80) تا پورت فقط از طریق میزبان و هر آنچه که عمداً به آن proxy میکنید، قابل دسترسی باشد. زمانی که یک container حتماً باید عمومی باشد، آن را پشت یک reverse proxy مانند Traefik قرار دهید و فقط proxy را منتشر کنید، نه تکتک برنامهها را.
قوانین IPv6 را به فایروال ارائهدهنده خود اضافه کنید، یا بپذیرید که آن فایروال برای IPv6 مناسب نیست و اجازه دهید UFW یا nftables روی میزبان این وظیفه را انجام دهند.
تأیید بسته بودن واقعی پورتها
پس از اعمال تغییرات، دوباره همان تست خارجی را اجرا کنید:
curl -6 -v http://[2001:db8:2a::1]:8080/
nmap -6 2001:db8:2a::1پورتی که پیشتر پاسخ میداد، اکنون باید اتصال را رد کند یا با timeout مواجه شود و nmap باید وضعیت آن را filtered یا closed گزارش کند. اگر پورتی همچنان باز است، چهار منبع ذکر شده در بالا را دوباره بررسی کنید: سرویسی که هنوز روی :: گوش میدهد و هیچ قانونی جلوی آن نیست، قانونی در Docker که پیش از UFW قرار گرفته، یا فایروال ارائهدهنده سرویس که اصلاً IPv6 را در نظر نگرفته است.
دور نگه داشتن کامل سرویسهای حساس از اینترنت عمومی، امنیت بسیار بالاتری دارد. SSH و پنلهای مدیریتی را پشت یک VPN از نوع WireGuard قرار دهید و پورتهای آنها را در فایروال محدود کنید تا فقط از طریق تونل پاسخگو باشند؛ در این صورت، مسئلهٔ exposure در IPv6 دیگر برای آنها موضوعیت نخواهد داشت. برای کند کردن اسکنهای brute-force که هر سرویس عمومی را هدف قرار میدهند، در کنار یک فایروال با سیاست پیشفرض deny، از Fail2ban برای محافظت از SSH استفاده کنید.
اگر مفهوم پورتها برای شما جدید است، پورتها چیستند و سرویسها چگونه روی آنها گوش میدهند اولین مطلبی است که باید مطالعه کنید.
FAQ
آیا UFW بهصورت پیشفرض IPv6 را مسدود میکند؟
در نصب مدرن Ubuntu 24.04، بله. ابزار UFW فایل IPV6=yes را از مسیر /etc/default/ufw میخواند و هر قانون را هم برای IPv4 و هم برای IPv6 اعمال میکند؛ همچنین ufw status قوانین IPv6 را با پسوند (v6) نمایش میدهد. مشکل زمانی رخ میدهد که IPV6=no (به دلیل استفاده از ایمیجهای قدیمی یا آموزشهای منسوخ) غیرفعال باشد، یا زمانی که به فایروال ارائهدهنده سرویس (VPS) تکیه میکنید که فقط IPv4 را فیلتر میکند، یا وقتی Docker یک پورت را بدون عبور از UFW منتشر میکند. وضعیت این سوئیچ را با grep IPV6 /etc/default/ufw بررسی کنید.
چگونه بررسی کنم که VPS من چه پورتهایی را روی IPv6 باز گذاشته است؟
دستور sudo ss -tlnp را اجرا کنید و تمام سرویسهایی که آدرس محلی آنها با [::] شروع میشود را یادداشت کنید؛ این یعنی سرویس روی تمام اینترفیسهای IPv6 پاسخ میدهد. سپس از یک ماشین دیگر، آدرس IPv6 عمومی سرور را مستقیماً با curl -6 -v http://[YOUR:IPV6::ADDR]:PORT/ تست کنید یا آن را با nmap -6 YOUR:IPV6::ADDR اسکن کنید. هر پورتی که در اسکن IPv6 باز است اما در IPv4 بسته است، شکاف امنیتی شما محسوب میشود.
چرا با وجود اینکه UFW میگوید پورت کانتینر Docker مسدود است، همچنان میتوانم به آن دسترسی داشته باشم؟
زمانی که پورتی را با -p منتشر میکنید، Docker قوانین فایروال خود را پیش از قوانین UFW تزریق میکند؛ بنابراین پورت منتشرشده در دسترس باقی میماند، حتی اگر ufw status آن را در وضعیت denied نشان دهد. این اتفاق در IPv4 رخ میدهد و اگر پشتیبانی IPv6 در Docker فعال باشد، در IPv6 نیز تکرار میشود. پورت را روی یک آدرس خاص مانند -p 127.0.0.1:8080:80 منتشر کنید یا کانتینر را پشت یک reverse proxy قرار دهید و فقط پورت proxy را منتشر کنید.
آیا با وجود داشتن فایروال IPv4 قوی، همچنان به فایروال IPv6 نیاز دارم؟
بله. IPv4 و IPv6 دو پشته شبکه مجزا با قوانین فایروال جداگانه هستند. یک مجموعه کامل از قوانین IPv4 هیچ تأثیری بر ترافیک IPv6 ندارد. اگر VPS شما دارای آدرس IPv6 عمومی باشد (که تقریباً همه آنها دارند)، هر سرویسی که روی :: گوش میدهد، تا زمانی که یک قانون فایروال IPv6 یا اتصال به loopback مانع آن نشود، از طریق IPv6 در دسترس باقی میماند.
چگونه یک سرویس را فقط روی IPv4 یا فقط روی localhost تنظیم کنم؟
آدرس bind سرویس را در فایل پیکربندی خودش تنظیم کنید. برای محدود کردن به IPv4 loopback از 127.0.0.1، و برای گوش دادن روی تمام آدرسهای IPv4 بدون شنود IPv6 از 0.0.0.0 استفاده کنید. Postgres از listen_addresses، SSH از ListenAddress استفاده میکنند و اکثر سرورهای اپلیکیشن نیز یک فلگ برای host یا bind دارند. نتیجه را با sudo ss -tlnp تأیید کنید و مطمئن شوید که Local Address دیگر مقدار [::] را نشان نمیدهد.