TLS 1.3 עדיין שולח את שם השרת בטקסט גלוי
TLS 1.3 מצפין את לחיצת היד אך לא את ה-SNI. קראו את ה-ClientHello, בדקו קבלת ECH אמיתית ותעדו מה משקיף עדיין יכול לראות.
שדרוג מאזין ל-TLS 1.3 משנה מה שמשקיף יכול לקרוא, אך לא כפי שמספר הגרסה מרמז. ה-ClientHello עדיין עובר לפני שמנוהל משא ומתן על הצפנה כלשהי, ובחיבור רגיל שם השרת המבוקש נמצא שם בטקסט גלוי. Encrypted Client Hello הוא המנגנון שמסתיר אותו, וזה פריסה נפרדת עם התנאים המוקדמים שלה. בצעו את ארבעת השלבים האלה לפני שתכתבו מדיניות שמניחה אחת משתי ההתנהגויות.
מה לחיצת היד באמת חושפת
TLS 1.3 מצפין את תעודת השרת ואת רוב לחיצת היד שאחרי ה-ServerHello. כל מה שהלקוח שולח ראשון נמצא מחוץ להגנה הזו, כי המפתחות עדיין לא קיימים. ציון שם השרת הוא חלק מהמשלוח הראשון הזה, ולכן משקיף פסיבי עדיין יכול לקרוא אותו בלי לשבור דבר.
מפרט ECH, RFC 9849, משנה זאת על ידי הצפנת ClientHello פנימי עבור שרת שמשתף פעולה. לחיצת היד החיצונית נשארת ונושאת שם ציבורי שהרשת יכולה לראות, ולכן הפריסה עדיין זקוקה לזהות חיצונית שיש לה משמעות עבור המתווכים. השם החיצוני הזה הוא החלק שאנשים מפספסים כשהם מצפים שהחיבור יהפוך לאנונימי. זהו שם כיסוי, לא שם נסתר, והוא חייב להיפתר ולהאומת כמו כל אחר.
שום דבר מזה לא מסתיר את נתיב האספקה. כתובות המקור והיעד, הפורטים, גדלי החבילות והתזמון נשארים ניתנים לצפייה, ולעיתים קרובות הם אומרים יותר על מי מדבר מאשר השם. פתרון שמות ויחסים ברמת היישום הם שכבה נוספת, לכל אחד מהן הנראות שלו.
קראו ClientHello במקום להניח
הגרסה ב-ServerHello לא אומרת לכם מה הלקוח שלח. הביטו במשלוח הראשון ישירות, מעמדה שמייצגת את המשקיף שמעניין אתכם.
openssl s_client -connect app.plant.example:443 -servername app.plant.example -tls1_3 </dev/null> 2>/dev/null | head -20 sudo tcpdump -ni eth0 -c 40 'tcp port 443' -w /tmp/tls.pcap tshark -r /tmp/tls.pcap -Y 'tls.handshake.extensions_server_name' -T fields -e tls.handshake.extensions_server_name
הפקודה הראשונה מבצעת לחיצת יד ומציגה את הפרמטרים שנקבעו, כולל גרסת הפרוטוקול והשם שביקשתם. השנייה לוכדת את החבילות הראשונות של חיבור אמיתי, והשלישית מחלצת את שדה ה-SNI מהלכידה הזו. אם השדה מלא, המשקיף שחיקתם יכול לקרוא אותו.
לכדו בנפרד בצד הלקוח ובנתיב כאשר השניים לא מסכימים, כי לכידה מקומית עשויה להראות שם שרכיב ביניים משכתב או מפיל מאוחר יותר. לכדו גם ממארח שלא מסיים TLS בעצמו, שכן פרוקסי שמסיים רואה טקסט גלוי מעצם תכנונו ואינו מוכיח דבר על מה שהרשת רואה.
התייחסו להרחבת ECH כאל טענה, לא כהוכחה
לראות הרחבת ECH ב-ClientHello לא אומר שהחיבור השתמש ב-ECH. לקוחות שולחים GREASE ECH כשאין להם תצורה שמישה, בדיוק כדי שלחיצות היד שלהם ייראו אחידות. ההרחבה קיימת, ה-hello הפנימי הוא פיתיון, והשם האמיתי עדיין קריא.
dig +short HTTPS app.plant.example openssl s_client -connect app.plant.example:443 -tls1_3 -status </dev/null> 2>/dev/null | grep -i -E 'ech|status_request'
הפקודה הראשונה מראה אם המקור מפרסם רשומת משאב HTTPS עם פרמטר תצורת ECH, וכך לקוחות לומדים את המפתח. השנייה מחפשת מידע על מצב לחיצת היד, אם כי הודעת ההצלחה של הלקוח היא ההוכחה החזקה יותר. הקבלה היא מה שקובע: שרת שאין לו המפתח המתאים לא יכול לפענח את ה-hello הפנימי, והחיבור נסוג.
בדקו גם את הלקוח, כי תמיכת הדפדפנים והתצורה משתנות לפי גרסה ופלטפורמה. מדיניות שנבנתה על „דפדפנים מודרניים משתמשים ב-ECH“ היא ניחוש לגבי צי, לא עובדה לגבי הצי שלכם. עשו מלאי של הגרסאות שמתחברות בפועל, ואז בדקו אחת מכל סוג. ייתכן שאותו עמוד שעובד מדפדפן שולחני עדכני לא יסתיר דבר מלקוח ישן יותר באותה רשת.
כתבו את היקף המשקיף לתוך המדיניות
ציינו היכן המשקיף יושב ומה נראה בנקודה הזו לפני שתחליטו על מה בקרה יכולה להסתמך. החיבור של דפדפן לאתר, תעבורת VPN חיצונית ופרוקסי שמסיים TLS חושפים כל אחד מערכת עובדות אחרת, וכלל שנכתב לעמדה אחת עובר לעיתים רחוקות בצורה נקייה לאחרת.
אם בקרה תלויה בקריאת SNI, בדקו כיצד הלקוחות שנפרסו מתנהגים כשה-ECH זמין וכשהוא אינו זמין. אחר כך תעדו את השלכות התאימות, כי מסנן שמפסיק להתאים בשקט גרוע מזה שמעולם לא נפרס.
לבסוף, שמרו את אי-הוודאות ברשומה. החלפת טענה אוניברסלית ישנה בחדשה יותר משאירה את אותו פער תחת שם פרוטוקול אחר, והסוקר הבא יאמין בה באותה נחישות.