פורט שנשאר פתוח ונשכח הוא הדרך הקלאסית להיכנס לשרת: מסד נתונים על כתובת ציבורית, לוח דיבאג, קונטיינר שפרסם פורט שאף אחד לא רצה לפרסם. הבדיקה לוקחת דקה כשמכירים שתי פקודות ומלכודת אחת. ביצענו את כל מה שלמטה על שרת Ubuntu 24.04 חדש מהאימג' שלנו וסרקנו אותו ממכונה שנייה, כך שהפלטים הם מה שתראו על השרת שלכם.
1. מה מאזין
הפקודה היא ss. האותיות פירושן TCP, UDP, שקעים מאזינים, מספרי פורטים ושם התהליך:
sudo ss -tulpn
בשרת חדש היא מדפיסה שש שורות:
Netid State Local Address:Port Process
udp UNCONN 127.0.0.54:53 systemd-resolve
udp UNCONN 127.0.0.53%lo:53 systemd-resolve
tcp LISTEN 127.0.0.54:53 systemd-resolve
tcp LISTEN 127.0.0.53%lo:53 systemd-resolve
tcp LISTEN 0.0.0.0:22 sshd
tcp LISTEN [::]:22 sshd
העמודה החשובה היא Local Address. היא אומרת מי יכול להתחבר:
| כתובת | מי מגיע אליה |
|---|---|
127.0.0.1:… או 127.0.0.53 | רק תוכנות בשרת הזה |
0.0.0.0:… | כל אחד דרך IPv4, אלא אם חומת האש חוסמת |
[::]:… | כל אחד דרך IPv6 (ובדרך כלל גם IPv4) |
10.0.0.5:… (כתובת מסוימת) | כל מי שמגיע לכתובת הזאת |
כלומר השרת החדש חושף כלפי חוץ דבר אחד בלבד: SSH בפורט 22, דרך IPv4 ו-IPv6. פותר ה-DNS על 127.0.0.53 פרטי.
כדי להציג רק את מה שהאינטרנט היה יכול לראות, סננו את כתובות ה-loopback:
sudo ss -tlnp | awk 'NR>1 && $4 !~ /^(127\.|\[::1\])/ {print $4, $6}'
הריצו את זה אחרי כל התקנה. התקנו nginx, Docker ואפליקציית Python קטנה, והרשימה גדלה מ-2 שורות ל-10, ביניהן docker-proxy על 0.0.0.0:8081 ושקע של containerd על פורט localhost אקראי.
2. איזה תהליך תופס פורט
כשפורט תפוס ורוצים לדעת מי תופס אותו:
sudo lsof -i :80 -P -n
sudo fuser 80/tcp
sudo ss -tlnp 'sport = :80'
הכי קריא הוא lsof. עבור nginx הוא הראה תהליך ראשי כ-root ו-worker כ-www-data, שניהם על *:80, ל-IPv4 ול-IPv6. fuser מדפיס רק את מזהי התהליכים, שימושי בסקריפטים. אם צריך לשחרר את הפורט, עצרו את השירות במקום להרוג את התהליך, אחרת systemd יפעיל אותו מחדש: sudo systemctl stop nginx.
3. מה האינטרנט רואה
ss מראה את נקודת המבט של השרת. נקודת המבט של האינטרנט יכולה להיות שונה, כי באמצע יש חומת אש או כי שירות קשור ל-127.0.0.1. כדי לראות את התמונה האמיתית, סרקו את השרת ממכונה אחרת, מהמחשב הנייד או משרת שני. הכלי הוא nmap (apt install nmap, או המתקין מ-nmap.org ב-Windows וב-macOS).
nmap your-server-ip
nmap -p- your-server-ip
nmap -p 22,80,443,3306 your-server-ip
הצורה הראשונה בודקת את 1,000 הפורטים הנפוצים, השנייה את כל 65,535, השלישית רשימה. בשרת החדש שלנו, לפני כל חומת אש:
Not shown: 999 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
Nmap done: 1 IP address (1 host up) scanned in 6.83 seconds
הסריקה המלאה של 65,535 פורטים לקחה 8.9 שניות ומצאה את אותו פורט יחיד. פורטים סגורים עונים מיד ב-reset, ולכן סריקתם מהירה.
שלושה מצבים חשובים:
- open: תוכנה קיבלה את החיבור. זה משטח התקיפה שלכם.
- closed: השרת ענה שאין שם שום דבר שמאזין. בטוח, אבל מאשר שהשרת קיים ומה הוא לא מריץ.
- filtered: אין תשובה. חומת אש זרקה את החבילה. הסורק לא יכול לדעת אם יש שירות מאחוריה.
לפורט בודד בלי להתקין כלום, nc -zv כתובת-השרת 443 מחזיר succeeded או refused, ובפורט מסונן נתקע עד תום הזמן. עבור UDP הוסיפו -sU ל-nmap והתכוננו להמתנה; בשרת שלנו שלושת פורטי ה-UDP הנפוצים, 53, 123 ו-161, היו closed.
הערה לחבילות NAT: מהאינטרנט מגיע רק פורט ה-SSH שהעברנו אליכם. סריקה של הכתובת המשותפת מראה את קצה הרשת שלנו, לא את השרת שלכם, ושאר הפורטים לא נגישים, לא משנה מה מאזין עליהם. בחבילה עם כתובת IPv4 משלה, כל פורט פתוח בניהולכם.
4. סגרו את מה שלא צריך
שלוש דרכים, מהטובה ביותר לרחבה ביותר.
קשרו ל-localhost. אם רק תוכנות בשרת משתמשות בשירות, תנו לו להאזין על 127.0.0.1. הפעלנו אפליקציית ווב קטנה עם --bind 127.0.0.1 8080: ss הראה אותה על 127.0.0.1:8080, ו-nmap מבחוץ דיווח 8080/tcp closed. למסדי נתונים זו ברירת המחדל הנכונה: Redis, MySQL ו-PostgreSQL מאזינים על localhost כבר מההתקנה ב-Ubuntu, והמדריכים שלנו ל-Redis ול-MySQL משאירים את זה כך.
עצרו את השירות. sudo systemctl disable --now שם מסיר את המאזין לתמיד. בדקו עם ss שהוא נעלם.
הפעילו את חומת האש. אשרו קודם SSH, אחר כך את מה שאתם מגישים, ואז הפעילו:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw enable
sudo ufw status verbose
הסריקה מבחוץ השתנתה לגמרי:
Not shown: 997 filtered tcp ports (no-response)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
8081/tcp open blackice-icecap
Nmap done: 1 IP address (1 host up) scanned in 10.18 seconds
כל פורט שלא אושר עבר מ-closed ל-filtered, והסריקה המלאה של 65,535 פורטים לקחה עכשיו 110 שניות במקום 8.9. יותר זמן לכל פורט עבור הסורק, פחות מידע על השרת שלכם. מדריך ה-UFW שלנו עוסק בהגבלת קצב, באישור כתובת IP אחת וב-IPv6.
אבל הסתכלו על השורה השלישית.
5. ההפתעה של Docker
פורט 8081 נשאר פתוח כשחומת האש פעילה ובלי כלל שמתיר אותו. הפורט שייך לקונטיינר שהפעלנו עם docker run -p 8081:80 nginx:alpine. Docker מוסיף כללים משלו לחומת האש של הליבה, והם נבדקים לפני הכללים של ufw. הכלל שהוא כתב בשרת שלנו:
-A DOCKER ! -i docker0 -p tcp --dport 8081 -j DNAT --to-destination 172.17.0.2:80
כל חבילה לפורט 8081 מועברת לקונטיינר לפני ש-ufw מקבל זכות דיבור. כך עושה כל קונטיינר עם -p או רשומת ports:, ו-ss מציג את זה ביושר כ-docker-proxy על 0.0.0.0:8081.
התיקון הוא לפרסם רק על localhost:
docker rm -f web
docker run -d --name web -p 127.0.0.1:8081:80 nginx:alpine
ב-Compose כתבו "127.0.0.1:8081:80" תחת ports:. אחרי השינוי ss הראה 127.0.0.1:8081, כלל ה-Docker קיבל -d 127.0.0.1/32, והסריקה מבחוץ דיווחה 8081/tcp filtered. שימו nginx כ-reverse proxy מלפנים עבור הצד הציבורי, עם HTTPS, כך שהפורטים הפתוחים היחידים בשרת יהיו 22, 80 ו-443.
רשימת בדיקה אחרי כל שינוי
sudo ss -tulpn: כל שורה עם0.0.0.0או[::]היא משהו שרציתם לחשוף?nmap כתובת-השרתמהמחשב הנייד: הפורטים הפתוחים תואמים?- מסד נתונים, מטמון או לוח ניהול על כתובת ציבורית: העבירו היום ל-
127.0.0.1או מאחורי חומת האש. - כל רשומת
ports:ב-Docker: מתחילה ב-127.0.0.1:אם זה לא פורט הווב הציבורי? - SSH פתוח לכולם: הוסיפו fail2ban או כניסה עם מפתח בלבד.
ב-Windows Server אותה בדיקה היא Get-NetTCPConnection -State Listen, והכללים נמצאים בחומת האש של Windows. לכל חבילת Linux, החל מ-Nano ב-$3, יש גרסה עם כתובת IPv4 משלה, ושם מה שאתם רואים ב-ss רואה כל העולם, חוץ ממה שחומת האש שלכם עוצרת.
תגובות
אין עדיין תגובות. היו הראשונים.