חשיפת שירותים ב-VPS דרך IPv6 ו-UFW
למה שירותים ב-VPS נשארים חשופים למרות חסימה ב-UFW? למד כיצד IPv6 עוקף הגדרות IPv4 וכיצד לסגור את הפער ב-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 היא מסוג שכוכבים שנוטים לשכוח שהפעילו.
רוב ה-daemons קשורים ל-:: כברירת מחדל, מכיוון שב-Linux socket מסוג :: מקבל בדרך כלל גם IPv4. לכן המצב המוגדר של שרת חדש הוא "לענות בשני ה-stacks, בכל מקום". ה-firewall שלכם הוא הדבר היחיד שעומד בפני זה, וזו הסיבה ש-firewall שרואה רק stack אחד מהווה בעיה אמיתית.
המקור האמיתי לפער ב-IPv6
קיימים ארבעה מקורות נפוצים. במערכת מסוימת עשויים להופיע אחד מהם או מספר מקורות במקביל.
1. Firewall בענן המסנן IPv4 בלבד. חומות אש רבות של ספקי ענן ומוצרי security-group התפתחו סביב IPv4. לכן, הן מתעלמות מ-IPv6 או דורשות כללים נפרדים עבור IPv6 שיש להוסיף ידנית. אם חומת האש היחידה שלך היא זו שנמצאת בלוח הבקרה של הספק והיא אינה מכסה IPv6, השירותים של [::] יהיו פתוחים, ללא קשר למה שמוגדר עבור port 22 ב-IPv4. קרא את תיעוד חומת האש של הספק וחפש ספציפית את המונח IPv6.
2. iptables שנכתב ידנית ללא ip6tables. הפקודה iptables משפיעה על טבלאות ה-IPv4 בלבד. ל-IPv6 יש פקודה נפרדת לחלוטין, ip6tables, עם כללים נפרדים משלה. אם כתבת script של חומת אש המלא בשורות iptables -A INPUT ... ומעולם לא כתבת את שורות ה-ip6tables המתאימות, חומת האש של IPv6 שלך תהיה ריקה. שרשרת INPUT ריקה עם מדיניות ACCEPT ברירת מחדל מאפשרת כל תעבורה:
sudo ip6tables -L INPUT -nChain INPUT (policy ACCEPT)
target prot opt source destinationהפלט הזה הוא מלכודת שלמה על מסך אחד. ה-IPv4 מסונן, בעוד ה-IPv6 מקבל את כל העולם.
3. Docker המפרסם ports ישירות מעבר לחומת האש שלך. כאשר מריצים את docker run -p 8080:80, Docker מכניס כללים משלו לפני הכללים של UFW. לכן, port שפורסם יהיה נגיש גם כאשר ufw status קובע שה-port נחסם. בגרסאות Docker מודרניות, הדבר תקף גם עבור IPv6. המדריך Why Docker bypasses UFW, and how to filter container ports properly מסביר את המנגנון ואת הפתרונות. עיין ב-the basics of Docker Compose on a VPS כדי לראות כיצד מצהירים על ה-ports המפורסמים הללו.
4. UFW כאשר IPv6 כבוי. UFW אכן מטפל ב-IPv6, אך רק כאשר מ指示 לו לעשות זאת. בדוק את ההגדרה:
grep IPV6 /etc/default/ufwגרסאות Ubuntu מודרניות מגיעות עם IPV6=yes, ולכן UFW מחיל כל כלל על שני ה-stacks. אם אתה רואה IPV6=no, מתוך image ישן או מדריך ישן, כל כלל UFW שכתבת תקף ל-IPv4 בלבד, ו-IPv6 נשאר ללא ניהול.
בדקו בדיוק מה אתם חושפים
אל תנחשו. מדדו מבחוץ. ראשית, רשמו את ה-listeners שלכם ושימו לב לכל אחד שקשור ל-:::
sudo ss -tlnp | grep '::'לאחר מכן, ממכונה אחרת, התחברו לכתובת ה-IPv6 הציבורית של השרת ונסו פורט שאתם סבורים שהוא סגור:
curl -6 -v http://[2001:db8:2a::1]:8080/אם התוצאה היא דף או banner, הפורט פתוח ב-IPv6. פורט סגור יחזיר Connection refused או timeout. כדי לקבל תמונה מלאה, סרקו את כתובת ה-IPv6 באמצעות nmap ממכונה חיצונית לשרת:
nmap -6 2001:db8:2a::1כל פורט ש-nmap מדווח עליו כפתוח ב-IPv6 הוא פורט שכל האינטרנט יכול להגיע אליו, ללא קשר למה שסריקת ה-IPv4 הראתה. השוואה בין סריקות ה-IPv4 וה-IPv6 זו לצד זו היא הדרך המהירה ביותר למצוא פערים: כל דבר שפתוח ב--6 אך סגור ב-IPv4 הוא שירות שחסר ב-firewall שלכם.
Close the gap
הגדר את UFW כך שיכסה את שני ה-stacks, והגדר deny כברירת מחדל. ודא שהשינוי נכנס לתוקף, ולאחר מכן הגדר מדיניות inbound 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אם 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 היא הפתרון הנקי ביותר כאשר אתה כותב כללים בעצמך.
קשר שירותים שאינם אמורים להיות ציבוריים ל-loopback. בסיס נתונים, לוח בקרה של מנהל מערכת, או נקודת קצה של מדדים (metrics) בדרך כלל אינם זקוקים לכתובת ציבורית. קשר אותם ל-127.0.0.1 ול-::1 כדי שהם לא יקשיבו לכתובת ניתנת לניתוב (routable) מלכתחילה. עבור Postgres, הגדר את listen_addresses = 'localhost'. עבור שרת אפליקציה, קשר אותו ל-127.0.0.1 והצב reverse proxy לפניו. סגירת ה-listener יעילה יותר מחסימה בחומת אש, כיוון שלא תהיה נקודת גישה כלל.
אל תסמוך על UFW כדי להגן על ה-ports המפורסמים של Docker. פרסם את ה-container ports לכתובת ספציפית במקום לכל interface, לדוגמה -p 127.0.0.1:8080:80, כך שה-port יהיה נגיש רק מה-host ומהגורם שדרכו בחרת להעביר תעבורה (proxy). כאשר קונטיינר חייב להיות ציבורי, הצב אותו מאחורי a Traefik reverse proxy ופרסם רק את ה-proxy, ולא כל אפליקציה בנפרד.
הוסף כללי IPv6 בחומת האש של הספק שלך, או קבל את העובדה שזו אינה חומת האש שלך עבור IPv6, ותן ל-UFW או ל-nftables ב-host לבצע את המשימה במקומה.
Verify you are actually closed
הרץ שוב את אותו בדיקה חיצונית לאחר ביצוע השינויים:
curl -6 -v http://[2001:db8:2a::1]:8080/
nmap -6 2001:db8:2a::1הפורט שענה קודם לכן אמור כעת לסרב לבקשה או להחזיר timeout, ו-nmap אמור לדווח עליו כ-filtered או closed. אם פורט עדיין פתוח, בדוק מחדש את ארבעת המקורות המופיעים לעיל: שירות שעדיין קשור ל-:: ללא חוק לפניו, חוק Docker שמופיע לפני UFW, או חומת אש של ספק שלא זיהתה IPv6 כלל.
הפרדה מוחלטת של שירותים רגישים מהאינטרנט הציבורי היא אמצעי חזק יותר. הצב את SSH ולוחות בקרה מאחורי WireGuard VPN וחסום את הפורטים שלהם כך שיענו רק דרך המנהרה; כך שסוגיית החשיפה ב-IPv6 לא תתקיים עבורם. כדי להאט סריקות brute-force הפוגעות במה שנשאר ציבורי, התקן Fail2ban לפני SSH מעל חומת אש במצב default-deny.
אם נושא הפורטים חדש עבורך, מהם פורטים וכיצד שירותים מאזינים הוא המדריך שמומלץ לקרוא תחילה.
FAQ
האם UFW חוסם IPv6 כברירת מחדל?
בהתקנה מודרנית של Ubuntu 24.04, התשובה היא כן. UFW קורא את IPV6=yes מתוך /etc/default/ufw ומחיל כל כלל גם על IPv4 וגם על IPv6, ו-ufw status מציג את כללי ה-IPv6 עם סיומת (v6). הבעיה מתרחשת כאשר IPV6=no (מתוך אימג' ישן או מדריך ישן), כאשר מסתמכים על חומת אש של ספק המסננת IPv4 בלבד, או כאשר Docker מפרסם (publishes) פורט מעבר ל-UFW. בדקו את ההגדרה באמצעות grep IPV6 /etc/default/ufw.
איך אוכל לבדוק מה ה-VPS שלי חושף ב-IPv6?
הריצו את sudo ss -tlnp ושימו לב לכל listener שכתובת ה-local שלו מתחילה ב-[::], מה שאומר שהוא מגיב בכל ממשק IPv6. לאחר מכן, ממכונה אחרת, בדקו את כתובת ה-IPv6 הציבורית של השרת ישירות באמצעות curl -6 -v http://[YOUR:IPV6::ADDR]:PORT/, או סרקו אותה באמצעות nmap -6 YOUR:IPV6::ADDR. כל פורט שפתוח בסריקת ה-IPv6 אך סגור ב-IPv4 מהווה פרצת אבטחה.
מדוע אני מצליח לגשת לפורט של Docker container למרות ש-UFW מציין שהוא חסום?
Docker מכניס כללי חומת אש משלו לפני הכללים של UFW כאשר מפרסמים פורט באמצעות -p, ולכן הפורט המפורסם נגיש למרות ש-ufw status מפרט אותו כחסום (denied). זה קורה ב-IPv4, וגם ב-IPv6 כאשר תמיכת ה-IPv6 של Docker מופעלת. פרסמו את הפורט לכתובת ספציפית כגון -p 127.0.0.1:8080:80, או הציבו את ה-container מאחורי reverse proxy ופרסמו רק את ה-proxy.
האם אני עדיין זקוק לחומת אש ל-IPv6 אם חומת האש של IPv4 חזקה?
כן. IPv4 ו-IPv6 הם מחסנית רשת (network stacks) נפרדות עם כללי חומת אש נפרדים. סט מושלם של כללי IPv4 אינו משפיע על תעבורת IPv6. אם ל-VPS שלכם יש כתובת IPv6 ציבורית, וכמעט לכולם יש, אז כל שירות שמאזין ב-:: יישאר נגיש דרך IPv6 עד שכלל חומת אש ל-IPv6 או קישור loopback יחסמו אותו.
איך אני גורם לשירות להאזין ל-IPv4 בלבד, או ל-localhost בלבד?
הגדירו את כתובת ה-bind של השירות בקובץ הקונפיגורציה שלו. הגדירו bind ל-127.0.0.1 עבור IPv4 loopback בלבד, או ל-0.0.0.0 עבור כל כתובות ה-IPv4 ללא listener ב-IPv6. Postgres משתמש ב-listen_addresses, SSH משתמש ב-ListenAddress, ורוב שרתי האפליקציות מציעים דגל host או bind. אשרו את התוצאה באמצעות sudo ss -tlnp וודאו ש-Local Address כבר לא מציג את [::].