רשתות ואינטרנט
TCP/IP ופורטים: איך שירותים מאזינים על המכונה שלך
פורט הוא דלת ממוספרת במכונה שלך. איך TCP ו-UDP משתמשים בפורטים, ואיך מפעילים, מוצאים ובודקים שרת מקומי על 127.0.0.1.
קריאה של חילופי HTTP אמיתיים שורה אחר שורה: מתודות, כתובות URL, כותרות, מחלקות של קודי סטטוס ועוגיות, עם בדיקות curl מול שרת מקומי.
HTTP הוא הפרוטוקול שדפדפנים, אפליקציות ו-APIs משתמשים בו כדי להחליף הודעות. RFC 9110 מגדיר מה ההודעות אומרות: מתודות, קודי סטטוס, שדות כותרת וכן הלאה. RFC 9112 מגדיר איך הודעות HTTP/1.1 נראות כטקסט פשוט על חיבור TCP. הסקירה של MDN היא מבוא קצר יותר, מכוון לדפדפנים, והיא משתלבת היטב עם ה-RFCs.
התבנית פשוטה. לקוח שולח בקשה, ושרת מחזיר תשובה אחת. הלקוח תמיד פותח את החילופים. HTTP רגיל, בלי TLS, לא מצפין כלום, והוא לא זוכר מי פנה אליו בין בקשה לבקשה. ההצפנה באה מ-TLS, שעליו מדבר המאמר הבא. הזיכרון בין בקשות בא מעוגיות ומטוקנים, שנגיע אליהם בהמשך.
רוב התנהגויות הווב שלומדים עוברות דרך HTTP. טופס שולח ערך בגוף הבקשה, API סומך על כותרת, הפניה מעבירה את המשתמש למקום אחר, ו-cache שומר עותק שגוי של דף. כשמשתמשים בהמשך ב-proxy כמו OWASP ZAP כדי לבחון תעבורה, הוא מציג את אותן הודעות בדיוק. בלי יכולת לקרוא בקשה גולמית, קשה להשתמש בפלט של ה-proxy.
בקשת HTTP/1.1 היא טקסט פשוט בצורה הזו:
GET / HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: curl/8.12.1
Accept: */*השורה הראשונה היא שורת הבקשה: המתודה, יעד הבקשה וגרסת הפרוטוקול. כל שורה אחריה היא שדה כותרת, שנכתב בצורה name: value. שורה ריקה מסיימת את חלק הכותרות. אם יש להודעה גוף, הוא מגיע אחרי השורה הריקה. על הקו כל שורה מסתיימת ב-CRLF (carriage return ואחריו line feed), ועורכי טקסט מסתירים את זה.
לתשובה יש אותו מבנה, עם שורת סטטוס בראש: גרסה, קוד סטטוס ו-reason phrase.
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 15
<h1>hello</h1>המתודה מציינת מה הלקוח רוצה שייעשה. RFC 9110 מגדיר את GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS ו-TRACE. PATCH מגיעה מ-RFC 5789.
שתי תכונות חשובות בקריאת תעבורה:
התכונות האלה הן הבטחות לגבי משמעות. השרת עדיין מחליט מה קורה בפועל. handler של GET שמוחק רשומה מפר את החוזה, וסוקר אבטחה יחפש בדיוק את הטעות הזו.
ניקח את הכתובת http://127.0.0.1:8000/missing?lang=en#top ונפרק אותה לחלקים:
http, שאומר ללקוח באיזה פרוטוקול להשתמש.127.0.0.1:8000, ה-host והפורט האופציונלי./missing.lang=en, אחרי התו ?.top, אחרי התו #.הלקוח לא שולח לשרת את ה-scheme או את ה-fragment. שורת הבקשה נושאת את ה-path ואת ה-query. ה-authority נכנס לכותרת Host. HTTP/1.1 דורש את Host כי הרבה אתרים יכולים לחלוק כתובת IP אחת, והשרת משתמש בכותרת הזו כדי להחליט איזה אתר יענה.
שמות כותרות לא תלויים ברישיות, ולכן Content-Type וגם content-type הם אותו שדה (RFC 9110, סעיף 5.1). ערכי כותרות הם עניין אחר: רבים מהם כן תלויים ברישיות, ואין להניח אחרת.
כותרות הבקשה הנפוצות:
Host: חובה בכל בקשת HTTP/1.1. לפי RFC 9112, סעיף 3.2, שרת חייב לענות 400 כשהיא חסרה.User-Agent: תוכנת הלקוח.Accept: סוגי המדיה שהלקוח יודע לטפל בהם.Cookie: העוגיות שהדפדפן שולח חזרה לשרת.Authorization: פרטי הזדהות, לעיתים קרובות bearer token.Content-Type וגם Content-Length: מתארות את הגוף, כשיש כזה.כותרות התשובה הנפוצות:
Content-Type: סוג המדיה של הגוף, למשל text/html.Content-Length: גודל הגוף בבתים.Date: השעון של השרת, ב-GMT.Server: שם התוכנה. שרתים רבים מדפיסים אותו. בסביבות production מסתירים אותו לעיתים קרובות.Last-Modified: מתי השרת שינה את המשאב בפעם האחרונה.Set-Cookie: מבקשת מהדפדפן לשמור עוגייה.כותרות הן נתונים שהשולח שולט בהם. דפדפן שולח את מה שהתוכנה שלו שולחת, וסקריפט או proxy יכולים לשלוח כל דבר. הדוגמה הקלאסית היא X-Forwarded-For: proxy מוסיף אותה, אבל גם לקוח יכול לשלוח אותה. אסור לסמוך על כותרת רק כי היא נראית רשמית.
לקוד סטטוס יש שלוש ספרות. הספרה הראשונה נותנת את המחלקה, כפי שמוגדר ב-RFC 9110, סעיף 15:
| מחלקה | משמעות | דוגמאות |
|---|---|---|
| 1xx | מידע. הבקשה התקבלה והעיבוד נמשך | 100 Continue |
| 2xx | הצלחה | 200 OK, 201 Created, 204 No Content |
| 3xx | הפניה. על הלקוח לעשות צעד נוסף | 301 Moved Permanently, 304 Not Modified |
| 4xx | שגיאת לקוח. יש בעיה בבקשה | 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 405 Method Not Allowed |
| 5xx | שגיאת שרת. השרת נכשל בטיפול בבקשה תקינה | 500, 501 Not Implemented, 502, 503 |
גם כשלקוח לא מכיר קוד מסוים, הוא יכול לפעול לפי המחלקה. 4xx אומר שהבקשה צריכה להשתנות. 5xx אומר שאותה בקשה אולי תעבוד מאוחר יותר.
שני קודים שקל לבלבל ביניהם. 401 אומר שלבקשה חסרים פרטי הזדהות תקפים. 403 אומר שהשרת הבין את הבקשה ומסרב לה, גם עם פרטי הזדהות.
ה-reason phrase, למשל Not Found, הוא טקסט שהשרת בוחר. לפי RFC 9112, סעיף 4, לקוחות צריכים להתעלם ממנו. את הלוגיקה כותבים מול המספר, אף פעם לא מול המילים.
עוגיות אינן חלק מ-RFC 9110. הן מוגדרות ב-RFC 6265. המחזור עובד כך:
Set-Cookie: session=abc123; Path=/; HttpOnly; Secure; SameSite=Lax.Cookie: session=abc123.עוגיית session היא מזהה שהשרת מחלק. היא לא סיסמה והיא לא פרטית למשתמש. מי שמחזיק בערך יכול בדרך כלל לפעול בשם אותו סשן עד שהוא פג או מבוטל. HttpOnly מרחיק את הערך מסקריפטים בדף. Secure מתיר אותו רק דרך HTTPS. SameSite מגביל מתי הדפדפן שולח אותו בבקשות בין אתרים. מאמרי אבטחת הווב חוזרים למאפיינים האלה.
צריך curl ו-Python 3. ב-Windows 11 עם Git Bash יש curl, ורוב מערכות Linux ו-macOS כוללות אותו. השרת הוא http.server המובנה של Python. מריצים אותו רק על המחשב שלך, קשור אל 127.0.0.1, ולעולם לא חושפים אותו לרשת.
הפלט להלן מגיע מהרצת הפקודות האלה על Windows 11 עם Git Bash, curl 8.12.1 ו-Python 3.12.10. על המחשב הזה python3 לא היה ב-path של Git Bash, ולכן השרת הופעל עם הנתיב המלא לקובץ ההרצה של Python 3.12. אפשר להשתמש בפקודה python3, py -3 או python, לפי מה שהמערכת מאפשרת. התאריכים, השעות ומחרוזות הגרסה אצלך יהיו שונים. מד ההתקדמות של curl הוסר מהפלט, וגופי HTML ארוכים קוצרו.
בטרמינל הראשון:
mkdir httplab && cd httplab && echo '<h1>hello</h1>' > index.html
python3 -m http.server 8000 --bind 127.0.0.1להשאיר את הטרמינל פתוח. השרת כותב שורת לוג אחת לכל בקשה, ולכן כל בקשה בהמשך תופיע כאן.
בטרמינל השני:
curl -v http://127.0.0.1:8000/פלט:
* Trying 127.0.0.1:8000...
* Connected to 127.0.0.1 (127.0.0.1) port 8000
* using HTTP/1.x
> GET / HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/8.12.1
> Accept: */*
>
* Request completely sent off
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.12.10
< Date: Sun, 08 Nov 2026 08:16:48 GMT
< Content-type: text/html
< Content-Length: 15
< Last-Modified: Sun, 08 Nov 2026 08:13:48 GMT
<
<h1>hello</h1>
* shutting down connection #0סדר הקריאה:
* הן הערות של curl על החיבור.> הן מה ש-curl שלח. את Host, User-Agent וגם Accept הוא הוסיף בעצמו.< הן מה שהשרת החזיר.> מסיימת את כותרות הבקשה. השורה הריקה של < מסיימת את כותרות התשובה.<h1>hello</h1> הוא הגוף. Content-Length: 15 תואם לקובץ, כי echo הוסיף ירידת שורה אחרי התגית.HTTP 1.0, assume close after body מראה חוסר התאמה. curl ביקש HTTP/1.1, אבל השרת ענה ב-HTTP/1.0. השרת הפשוט של Python מדבר HTTP/1.0, ולכן curl סוגר את החיבור אחרי קריאת הגוף.curl -i http://127.0.0.1:8000/missingהדגל -i מדפיס את כותרות התשובה יחד עם הגוף. הפלט, עם גוף HTML מקוצר:
HTTP/1.0 404 File not found
Server: SimpleHTTP/0.6 Python/3.12.10
Date: Sun, 08 Nov 2026 08:16:49 GMT
Connection: close
Content-Type: text/html;charset=utf-8
Content-Length: 335
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error response</title>
</head>
<body>
<h1>Error response</h1>
<p>Error code: 404</p>
<p>Message: File not found.</p>
<p>Error code explanation: 404 - Nothing matches the given URI.</p>
</body>
</html>בטרמינל הראשון נרשם:
127.0.0.1 - - [08/Nov/2026 05:16:49] code 404, message File not found
127.0.0.1 - - [08/Nov/2026 05:16:49] "GET /missing HTTP/1.1" 404 -קוד הסטטוס הוא 404, קוד ממחלקת הלקוח: ה-path לא ממופה לשום משאב. ה-reason phrase File not found הוא הניסוח של השרת עצמו. הגוף הוא דף HTML שהשרת יצר עבור השגיאה. כל סטטוס יכול לשאת כל גוף, ולכן קוראים את שניהם.
שורת הלוג מציגה 05:16 והכותרת Date מציגה 08:16. הלוג משתמש בשעה המקומית והכותרת ב-GMT, ולכן ההפרש ביניהם הוא ההיסט המקומי.
curl -i -X POST http://127.0.0.1:8000/הפלט, עם גוף HTML מקוצר:
HTTP/1.0 501 Unsupported method ('POST')
Server: SimpleHTTP/0.6 Python/3.12.10
Date: Sun, 08 Nov 2026 08:16:49 GMT
Connection: close
Content-Type: text/html;charset=utf-8
Content-Length: 357
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error response</title>
</head>
<body>
<h1>Error response</h1>
<p>Error code: 501</p>
<p>Message: Unsupported method ('POST').</p>
<p>Error code explanation: 501 - Server does not support this operation.</p>
</body>
</html>בשורת הלוג כתוב code 501, message Unsupported method ('POST'). הדגל -X משנה רק את המתודה. curl שלח את אותן כותרות כמו קודם.
לשרת הפשוט אין בכלל handler ל-POST, ולכן 501 Not Implemented היא התשובה הכנה. RFC 9110, סעיף 15.6.2, מגדיר את 501 עבור שרת שלא תומך ביכולת שהבקשה צריכה. אפליקציה אמיתית שמכירה את המשאב ומסרבת ל-POST עליו תענה בדרך כלל 405 Method Not Allowed. תשובת 405 חייבת לכלול כותרת Allow שמונה את המתודות שהמשאב מקבל.
curl -I http://127.0.0.1:8000/index.htmlפלט:
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.12.10
Date: Sun, 08 Nov 2026 08:16:50 GMT
Content-type: text/html
Content-Length: 15
Last-Modified: Sun, 08 Nov 2026 08:13:48 GMT
הדגל -I שולח בקשת HEAD. הסטטוס והכותרות תואמים למה ש-GET מחזיר, אבל אין גוף. הכותרת Content-Length עדיין אומרת 15, כי היא מתארת את הייצוג ש-GET היה מחזיר. RFC 9110, סעיף 9.3.2, מגדיר את ההתנהגות הזו. בלוג מופיע "HEAD /index.html HTTP/1.1" 200 -.
HEAD שימושית כשרוצים לבדוק אם קובץ קיים, מה גודלו או מתי השתנה לאחרונה, בלי להוריד אותו.
curl -v -H 'Cookie: session=demo123' http://127.0.0.1:8000/השורות הרלוונטיות בפלט:
> GET / HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/8.12.1
> Accept: */*
> Cookie: session=demo123
>
< HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.12.10
< Date: Sun, 08 Nov 2026 08:18:28 GMT
< Content-type: text/html
< Content-Length: 15
< Last-Modified: Sun, 08 Nov 2026 08:13:48 GMT
< שורת הגוף הושמטה כאן. השרת ענה באותו 200 כמו קודם, כי אין לו לוגיקת עוגיות והוא מתעלם מהכותרת. זו מטרת התרגיל. הדפדפן שולח עוגיות, אבל השרת מחליט מה הן אומרות. שרת שאף פעם לא מגדיר עוגייה לא יפעל לפי עוגייה ששלחת.
ללחוץ Ctrl+C בטרמינל הראשון בסיום.
Host, User-Agent, Cookie וגם X-Forwarded-For מגיעות כולן מצד הלקוח. צריך לאמת אותן בשרת.127.0.0.1.-X ישנה את הכותרות. הוא משנה רק את המתודה. כותרות מוסיפים עם -H.Content-Length וגם Transfer-Encoding.:8000 של הכתובת.proxy כמו ZAP מציג את אותן הודעות, ובגלל זה המאמרים הבאים על חולשות ווב נשענים על המאמר הזה. גם ה-API של NotHoly בכתובת api.notholy.com/v1 הוא API של HTTP עם גופי JSON, שמוגש דרך TLS, ולכן הכותרות וקודי הסטטוס שלמעלה תקפים גם כשפונים אליו.
רשתות ואינטרנט
פורט הוא דלת ממוספרת במכונה שלך. איך TCP ו-UDP משתמשים בפורטים, ואיך מפעילים, מוצאים ובודקים שרת מקומי על 127.0.0.1.
רשתות ואינטרנט
TLS 1.3 נותן ל-HTTPS סודיות, שלמות ואימות שרת. לחיצת היד, שרשראות תעודות, ומה המנעול בדפדפן לא מוכיח.
רשתות ואינטרנט
איך DNS הופך שמות לכתובות: resolvers, שרתים סמכותיים, מטמון TTL, הרשומות A, AAAA, CNAME, MX ו-TXT, ובדיקה בטוחה.