مشكلة UFW وIPv6: كيف تغلق المنافذ المفتوحة على VPS
قد تحمي قواعد UFW وجدار السحابة IPv4 فقط، بينما تظل خدمات VPS مكشوفة عبر IPv6. تعرّف إلى سبب المشكلة في Ubuntu 24.04 وكيف تغلقها.
مصيدة جدار الحماية الخاص بـIPv6 في جملة واحدة
يحمي جدار الحماية لديك IPv4. ومن شبه المؤكد أن VPS لديك يملك أيضاً عنوان IPv6 عاماً، وأن العديد من الخدمات تستمع إليه افتراضياً. إذا كان جدار الحماية يغطي IPv4 فقط، أو إذا كنت تعتمد على جدار حماية سحابي يرشّح IPv4 فقط، فستكون كل خدمة من تلك الخدمات قابلة للوصول من الإنترنت بالكامل عبر IPv6، بينما يبدو جانب IPv4 لديك محمياً بإحكام. تختبر منفذاً باستخدام curl، وترى أن الاتصال مرفوض، فتشعر بالأمان. لكن المهاجم يتصل بالمنفذ نفسه عبر 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 فهو من النوع الذي تنسى أنك شغّلته.
ترتبط معظم الخدمات بـ:: تلقائياً، لأن مقبس :: في Linux يقبل عادةً اتصالات IPv4 أيضاً. لذلك يكون الإعداد الافتراضي للخادم الجديد هو «الاستجابة عبر المكدسين وفي كل مكان». ويكون جدارك الناري هو الحاجز الوحيد أمام ذلك، ولهذا فإن جداراً نارياً يراقب مكدساً واحداً فقط يمثل مشكلة حقيقية.
مصدر فجوة IPv6 فعلياً
هناك أربعة مصادر شائعة. قد تجد واحداً منها أو عدة مصادر معاً على خادم معيّن.
1. جدار ناري سحابي يرشّح IPv4 فقط. نشأت كثير من منتجات جدران الحماية ومجموعات الأمان لدى المزوّدين حول 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 أن هذا المنفذ مرفوض. وينطبق الأمر نفسه على IPv6 في إصدارات Docker الحديثة. يشرح سبب تجاوز Docker لـUFW وكيفية ترشيح منافذ الحاويات بطريقة صحيحة الآلية والحلول. راجع أساسيات Docker Compose على VPS لمعرفة كيفية تعريف هذه المنافذ المنشورة.
4. تعطيل IPv6 في UFW. يتعامل UFW مع IPv6، لكن فقط عند تفعيله لذلك. تحقّق من الإعداد:
grep IPV6 /etc/default/ufwتتضمن إصدارات Ubuntu الحديثة IPV6=yes، ولذلك يطبّق UFW كل قاعدة على مكدسي الشبكة معاً. إذا ظهر لك IPV6=no، سواء بسبب صورة قديمة أو دليل قديم، فكل قواعد UFW التي كتبتها مخصّصة لـIPv4 فقط، ويظل IPv6 من دون إدارة.
اعرف بدقة ما الذي تعرّضه للإنترنت
لا تعتمد على التخمين. قِس ذلك من خارج الخادم. اعرض أولاً المنافذ التي تستمع إليها الخدمات، وسجّل كل منفذ مرتبط بـ :::
sudo ss -tlnp | grep '::'بعد ذلك، ومن جهاز مختلف، اتصل بعنوان IPv6 العام للخادم، وجرّب منفذاً تظن أنه مغلق:
curl -6 -v http://[2001:db8:2a::1]:8080/إذا أعاد ذلك صفحة أو رسالة تعريف، فالمنفذ مفتوح عبر IPv6. أما المنفذ المغلق فيعيد Connection refused أو تنتهي محاولته بمهلة. لا تعني هاتان الحالتان الإشارتين نفسيهما؛ إذ يوضح الفرق بين الرفض وانتهاء المهلة ما إذا كان المضيف قد استجاب ورفض الاتصال، أو أسقط جدار ناري الحزمة بصمت. وللحصول على صورة كاملة، افحص عنوان IPv6 باستخدام nmap من خارج الخادم:
nmap -6 2001:db8:2a::1كل منفذ يعرضه nmap على أنه مفتوح عبر IPv6 هو منفذ يستطيع كامل الإنترنت الوصول إليه، بغض النظر عما أظهره فحص IPv4. تتيح مقارنة فحصي IPv4 وIPv6 جنباً إلى جنب العثور على الفجوة بأسرع طريقة: أي منفذ مفتوح على -6 لكنه مغلق على IPv4 يمثل خدمة لا يغطيها جدارك الناري.
سدّ الفجوة
اجعل UFW يغطي كلا المكدسين، واضبط السياسة الافتراضية على الرفض. تحقّق من التبديل، ثم اضبط سياسة افتراضية لرفض الاتصالات الواردة، واسمح فقط بما تحتاج إليه:
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إذا كان UFW نشطاً بالفعل عند تفعيل IPV6=yes، فلن يسري التغيير حتى تشغّل 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 لديك يعمل بنظام Rocky أو AlmaLinux بدلاً من Ubuntu، فلن تجد UFW لإعداده، وستدير firewalld باعتباره الواجهة الأمامية بدلاً منه، إذ يطبّق قواعد المناطق الخاصة به على كلا المكدسين دفعة واحدة.
اربط الخدمات التي لا تريد إتاحتها للعامة بواجهة loopback. نادراً ما تحتاج قاعدة بيانات أو لوحة إدارة أو نقطة نهاية للمقاييس إلى عنوان عام أصلاً. اربطها بـ 127.0.0.1 و::1 حتى لا تستمع منذ البداية إلى عنوان قابل للتوجيه. بالنسبة إلى Postgres، اضبط listen_addresses = 'localhost'. وبالنسبة إلى خادم تطبيق، اربطه بـ 127.0.0.1 وضع reverse proxy أمامه. إغلاق نقطة الاستماع أفضل من حجبها بجدار ناري، لأنه لا يعود هناك شيء يمكن الوصول إليه.
لا تعتمد على UFW لحماية المنافذ التي ينشرها Docker. انشر منافذ الحاويات على عنوان محدد بدلاً من كل واجهة، مثل -p 127.0.0.1:8080:80، حتى لا يصبح المنفذ قابلاً للوصول إلا من المضيف ومن أي reverse proxy تسمح له به عمداً. عندما يجب أن تكون الحاوية متاحة للعامة فعلاً، ضعها خلف reverse proxy من Traefik وانشر proxy فقط، لا كل تطبيق.
أضف قواعد IPv6 إلى جدار الحماية لدى مزود الخدمة، أو أقرّ بأن جدار الحماية لديه لا يحمي IPv6، ودَع UFW أو nftables على المضيف يتولى هذه المهمة بدلاً منه.
تحقّق من أن المنفذ مغلق فعلاً
أعد اختبار الوصول الخارجي نفسه بعد إجراء التغييرات:
curl -6 -v http://[2001:db8:2a::1]:8080/
nmap -6 2001:db8:2a::1يجب أن يرفض المنفذ الذي استجاب سابقاً الاتصالات الآن أو تنتهي مهلة الاتصال، ويجب أن يعرض nmap حالته على أنها filtered أو closed. إذا ظل منفذ ما مفتوحاً، فأعد فحص المصادر الأربعة السابقة: خدمة ما زالت مرتبطة بـ`::` من دون قاعدة أمامها، أو قاعدة Docker تسبق UFW، أو جدار ناري لدى مزود الخدمة لم يتعامل مع IPv6 أصلاً.
يُعد إبقاء الخدمات الحساسة خارج الإنترنت العام تماماً خياراً أكثر أماناً. ضع SSH ولوحات الإدارة خلف شبكة WireGuard VPN واضبط جداراً نارياً لمنافذها بحيث تستجيب فقط عبر النفق؛ عندها لا يعود سؤال تعريض IPv6 منطبقاً عليها. ولإبطاء عمليات الفحص بالقوة الغاشمة التي تستهدف ما يبقى مكشوفاً للعامة، أضف Fail2ban أمام SSH فوق جدار ناري يعتمد المنع الافتراضي.
إذا كانت المنافذ جديدة عليك، فاقرأ أولاً شرح المنافذ وكيف تستمع الخدمات.
FAQ
هل يحظر UFW حركة IPv6 افتراضياً؟
في تثبيت حديث لـUbuntu 24.04، نعم. يقرأ UFW IPV6=yes من /etc/default/ufw ويطبّق كل قاعدة على IPv4 وIPv6، ويعرض ufw status قواعد IPv6 مع اللاحقة (v6). تظهر المشكلة عندما يكون IPV6=no مفعّلاً، كما يحدث في صورة قديمة أو في برنامج تعليمي قديم، أو عندما تعتمد على جدار ناري لدى مزود الخدمة لا يرشّح إلا 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 يمثل فجوة في الإعداد.
لماذا يمكنني الوصول إلى منفذ حاوية Docker رغم أن UFW يقول إنه محظور؟
يُدخل Docker قواعد جدار ناري خاصة به قبل قواعد UFW عند نشر منفذ باستخدام -p، لذلك يمكن الوصول إلى المنفذ المنشور رغم أن ufw status يعرضه على أنه مرفوض. يحدث ذلك على IPv4، ويحدث على IPv6 أيضاً عندما يكون دعم Docker لـIPv6 مفعّلاً. انشر المنفذ على عنوان محدد مثل -p 127.0.0.1:8080:80، أو ضع الحاوية خلف reverse proxy وانشر proxy فقط.
هل ما زلت أحتاج إلى جدار ناري لـIPv6 إذا كان جدار IPv4 لدي مضبوطاً جيداً؟
نعم. IPv4 وIPv6 مكدسان منفصلان للشبكة، ولكل منهما قواعد جدار ناري مستقلة. لا تؤثر مجموعة قواعد IPv4 المثالية في حركة IPv6. إذا كان لدى VPS الخاص بك عنوان IPv6 عاماً، وهذا هو الحال في معظم الحالات، فستظل أي خدمة تستمع على :: قابلة للوصول عبر IPv6 إلى أن تمنعها قاعدة جدار ناري لـIPv6 أو ربطها بواجهة loopback.
كيف أجعل خدمة تستمع على IPv4 فقط، أو على localhost فقط؟
اضبط عنوان الربط الخاص بالخدمة في ملف إعدادها. اربطها بـ127.0.0.1 لاقتصارها على loopback في IPv4، أو بـ0.0.0.0 لجميع عناوين IPv4 من دون مستمع IPv6. يستخدم Postgres listen_addresses، ويستخدم SSH ListenAddress، وتوفّر معظم خوادم التطبيقات خيار host أو bind. أكّد النتيجة باستخدام sudo ss -tlnp، وتحقق من أن Local Address لم يعد يعرض [::].