אבטחה

    סורקי קישורים וסודות חד-פעמיים

    קישור חד-פעמי שנמחק בבקשת GET רגילה מת ברגע ש-Slack או שער אבטחה של אימייל יוצרים לו תצוגה מקדימה. החזירו מטא-דאטה ב-GET ומחקו רק בהצגה מפורשת.

    W. Miller · עודכן · על LinkPilot

    קישור חד-פעמי שנמחק בבקשת GET רגילה מת ברגע ש-Slack, Teams או שער אבטחה של אימייל טוענים אותו כדי ליצור תצוגה מקדימה. הפתרון הוא להחזיר ב-GET רק מטא-דאטה, ולמחוק את הסוד רק בפעולת הצגה מפורשת ב-POST, עם ביטוי סיסמה שנשלח בערוץ שני כרשת ביטחון. הפוסט מסביר אילו מערכות טוענות קישורים בלי לשאול, ואיך לתכנן כך שאף אחת מהן לא תזיק.

    מחולל הקישורים הסודיים החד-פעמיים של LinkPilot בנוי כך: טעינה לצורך תצוגה מקדימה מקבלת רק את המעטפת של הדף. הסוד נטען, והצפייה נספרת, רק כשדפדפן מריץ את הדף.

    מצב הכשל הרגיל

    רוב תוכנות הצ'אט והאימייל מנסות ליצור תצוגה מקדימה כשהן רואות כתובת URL. התהליך נראה כך:

    1. המשתמש מדביק את הקישור הסודי ב-Slack.
    2. הבוט של Slack טוען את הכתובת (שולח GET; Slack מתעד את ההתנהגות הזו תחת link unfurling).
    3. הקוד שמטפל בהצגה מתייחס לבקשה כאל צפייה של אדם, מוחק את הסוד ומחזיר את התוכן.
    4. Slack מקבל את התוכן שנמחק (או דף כללי) ומציג אותו.
    5. האדם לוחץ על הקישור, רואה "already viewed" ופונה לשולח כדי לקבל קישור חדש.

    לבוט לא הייתה כוונה רעה. הקוד ערבב בין "מישהו טען את הכתובת" לבין "הנמען קרא את הסוד". אלה שני אירועים שונים, וכל בעיית התכנון היא להפריד ביניהם.

    בשלב 3 מסתתרת גרסה גרועה יותר: אם בקשת ה-GET של הבוט קיבלה את התוכן עצמו, הסוד יושב עכשיו בתשתית של צד שלישי שמייצרת תצוגות מקדימות. במקרה הטוב הוא נמחק אחרי שהתצוגה נבנתה, אבל אין לכם דרך לוודא את זה, ואין אירוע ביומן הביקורת שמספר לכם שזה קרה.

    בוטים של תצוגה מקדימה בצ'אט, שערי אבטחה של אימייל, הגנת קצה, פרוקסי ארגוניים ותוספי תצוגה מקדימה, ובדרך כלל יותר מאחד מהם. "Slack יוצר תצוגות מקדימות" ממעיט בגודל הבעיה. כשמדביקים כתובת URL במקום עבודה מודרני, בדרך כלל כמה מערכות טוענות אותה לפני שאדם כלשהו לוחץ עליה:

    מי טועןמתי זה קורהאפשר לזהות?
    בוטים של תצוגה מקדימה בצ'אט (Slack, Teams, iMessage, WhatsApp, LinkedIn)ברגע שההודעה נשלחתבדרך כלל: User Agent ייחודי כמו Slackbot-LinkExpanding
    שערי אבטחה של אימייל (Mimecast, Proofpoint, Microsoft Defender)במסירה, ולעיתים קרובות שוב בזמן הלחיצהלעיתים קרובות לא: רבים מציגים User Agent של דפדפן אמיתי
    הגנת קצה / אנטי-וירוסכשהכתובת מגיעה למחשב של הנמעןלעיתים רחוקות
    פרוקסי ארגונייםבזמן הלחיצה, לפני שהבקשה של המשתמש מועברת הלאהלעיתים רחוקות
    תוספי דפדפן לתצוגה מקדימה של קישוריםבמעבר עכבר או בטעינת הדףלא: הם פועלים בתוך סשן הדפדפן של המשתמש עצמו

    ההתנהגות של הספקים בטבלה משתנה כל הזמן. התייחסו לטענות על שערים מסוימים כ"נכון לפי מה שפורסם ביולי 2026, יש לבדוק מול התיעוד העדכני". המכנה המשותף לא משתנה: כל המערכות האלה שולחות GET רגיל, כי זו הדרך הבטוחה והאחידה היחידה להסתכל על כתובת שלא בשליטתכם.

    החזירו מטא-דאטה ב-GET ומחקו רק ב-POST מפורש. הסמנטיקה של HTTP כבר קיימת: RFC 9110, סעיף 9.2.1 מגדיר את GET כבטוח ואידמפוטנטי, כלומר בקשה שכל אחד רשאי לשלוח בחופשיות כי היא לא אמורה לשנות שום דבר. POST הוא הפועל של פעולה. לכן הפכו את ההצגה לפעולת POST מאחורי כפתור גלוי:

    GET  /s/abc123   →  HTML page: "Click to reveal this secret"
    POST /s/abc123   →  burns the secret, returns the payload
    

    עכשיו בקשת ה-GET של Slack מחזירה דף מטא-דאטה ניטרלי. הסוד נמחק רק כשהאדם לוחץ על הכפתור. כל סורק שמכבד את הסמנטיקה של HTTP לא נוגע בתוכן בכלל, וזה כמעט כולם, כי שליחת בקשות POST עם תופעות לוואי לכתובות אקראיות הייתה שוברת חצי מהאינטרנט.

    דוגמה מלאה, צעד אחר צעד

    עקבו אחרי קישור אחד מתחילתו ועד סופו. אתם יוצרים סוד עם מחיקה לאחר קריאה וביטוי סיסמה, ואז מדביקים את הכתובת בהודעה פרטית ב-Slack ובאימייל.

    1. הבוט של Slack שולח GET /s/abc123. התגובה: דף מטא-דאטה קטן, עם הסטטוס "active" (פעיל) ודרישה לביטוי סיסמה. לא נרשמת צפייה. הסוד עדיין קיים.
    2. שער האימייל של הנמען משכתב את הכתובת באימייל וטוען אותה בזמן המסירה לניתוח מוניטין. אותו GET, אותו מטא-דאטה. הסוד עדיין קיים.
    3. הנמען לוחץ על הקישור באימייל. הסורק של השער בזמן הלחיצה טוען אותו שוב (GET) ואז מפנה את הדפדפן הלאה.
    4. הדפדפן טוען את דף ההצגה, עוד GET. הדף מציג כפתור הצגה ושדה לביטוי הסיסמה. עדיין לא נרשמת צפייה.
    5. הנמען מזין את ביטוי הסיסמה ולוחץ על כפתור ההצגה. הדפדפן שולח POST /s/abc123. הצפייה נרשמת, התוכן מוחזר והסוד נמחק.
    6. כל מי שטוען את הכתובת אחר כך מקבל תגובה מפורשת שהסוד "כבר נצפה והושמד", במקום התוכן.

    חמש טעינות, הצגה אחת. היחס הזה רגיל בסביבה ארגונית. כל תכנון שלא עומד בו ייכשל כבר ביום הראשון, והשולח פשוט ידביק את הסיסמה בצ'אט במקום.

    הגנה בשכבות

    ההפרדה בין GET ל-POST

    מטפלת בכל בוט תצוגה מקדימה וסורק שמתנהגים כראוי, וזה הרוב המכריע של התעבורה האוטומטית. זה הבסיס. כל מה שמופיע בהמשך הוא שכבות נוספות.

    שכבת ביטוי סיסמה

    מכסה את מה שההפרדה לא מכסה: סביבות sandbox שמריצות JavaScript ומבצעות פעולות בדף, וגם לחיצות אנושיות בטעות ואימיילים שהועברו הלאה. sandbox שמפעיל קישורים חשודים יכול ללחוץ על כפתור הצגה, אבל הוא לא יכול להזין ביטוי סיסמה שנשלח בערוץ אחר. שלחו את הקישור באימייל ואת ביטוי הסיסמה בהודעה פרטית ב-Slack או ב-SMS.

    סינון בוטים לפי User Agent

    חסמו בוטים מוכרים של תצוגה מקדימה (Slackbot, LinkedInBot, Twitterbot) לפני שהם מגיעים ללוגיקת ההצגה. כדאי לעשות את זה כאמצעי נוסף, אבל זה אף פעם לא מספיק לבד: סורקי אבטחה מציגים באופן קבוע User Agent של דפדפן אמיתי, בדיוק כדי שדפים זדוניים לא יוכלו להסתתר מפני הניתוח.

    תפוגה קצרה

    הגבילו את חלון הנזק. קישור שחי רבע שעה משאיר אצל הסורקים, בקובצי הלוג ובהיסטוריית הצ'אט כתובת שכבר לא מובילה לשום דבר.

    Cache-Control: no-store

    הגדירו אותו בכל תגובה של הצגה, כדי שאף גורם ביניים, לא פרוקסי ארגוני, לא CDN ולא מטמון הדפדפן, ישמור עותק של התוכן אחרי הקריאה הלגיטימית היחידה.

    שערי אבטחה של אימייל במיוחד

    שערים ארגוניים מקבלים פרק משלהם כי הם טוענים את הקישור פעמיים. מוצרים מהסוג הזה (Microsoft Defender Safe Links הוא הדוגמה המתועדת) משכתבים כל כתובת בדואר נכנס כך שתעבור דרך דומיין הסריקה שלהם. בדרך כלל הכתובת נטענת פעם אחת בזמן המסירה לניתוח מוניטין, ושוב בזמן הלחיצה, לפני שהאדם מופנה ליעד האמיתי.

    אם אחת הטעינות האלה הייתה מוחקת את הסוד, הנמען היה רואה תמיד קישור מת. למשתמשים זה נראה כמו כשל אקראי, אבל הוא דטרמיניסטי לגמרי. ההפרדה בין GET ל-POST סופגת את שתי הטעינות בלי שום עלות, כי שתיהן GET רגיל. ההפניה בזמן הלחיצה מביאה את האדם לדף ההצגה, ושם מתבצע ה-POST.

    מקרי קצה שעדיין כואבים

    • סביבות sandbox שמריצות JavaScript. חלק מהסביבות שמפעילות קישורים חשודים מציגות את הדף ומבצעות בו פעולות. sandbox אגרסיבי יכול, באופן עקרוני, ללחוץ על כפתור ששולח POST אם הדף לא דורש ביטוי סיסמה. שכבת ביטוי הסיסמה היא רשת הביטחון.
    • הקישור וביטוי הסיסמה באותו ערוץ. אם שניהם נשלחים באותו אימייל, כל ההגנות שלמעלה מצטמצמות ל"נקווה שאף אחד לא יקרא את האימייל הזה". תמיד הפרידו בין הערוצים.
    • העברה הלאה. כל העברה לצ'אט או למערכת דואר חדשה מפעילה סבב חדש של תצוגות מקדימות. עם ההפרדה זה לא מזיק, אבל כל תחנה היא עוד סט של לוגים שמחזיק את הכתובת, וזו סיבה נוספת לתפוגה קצרה.
    • הצגות בו-זמניות. אם שני אנשים לוחצים על כפתור ההצגה באותו רגע, הקריאה והמחיקה חייבות לקרות בצעד אחד שאי אפשר לפצל: בקשה אחת בדיוק מנצחת, והשנייה מקבלת "already viewed". מימוש שבו הקריאה והמחיקה הן שני צעדים נפרדים עלול להחזיר את התוכן פעמיים.

    בדקו את הכלי שלכם

    לפני שאתם מפקידים פרטי התחברות אמיתיים בידי שירות לשיתוף מידע רגיש, הריצו את הבדיקה הזו, שלוקחת שתי דקות:

    1. צרו סוד לבדיקה עם מחיקה לאחר קריאה.
    2. טענו אותו מהטרמינל. זה בדיוק מה שבוט תצוגה מקדימה עושה: curl -s -o /dev/null -w "%{http_code}" "<url>"
    3. פתחו את הקישור בדפדפן. אם מופיע "already viewed", הכלי מוחק את הסוד ב-GET ולא בטוח להדביק את הקישור בצ'אט או באימייל.
    4. אם הסוד שרד, הציגו אותו כרגיל בדפדפן ואז הריצו curl שוב. עכשיו אתם אמורים לקבל תגובה מפורשת שהסוד נמחק, ולא את התוכן.

    איך LinkPilot מממש את זה

    לפרוטוקול, זה התכנון שהקישורים הסודיים של LinkPilot משתמשים בו: בקשת GET לנקודת הקצה של ההצגה מחזירה רק מטא-דאטה (סטטוס, והאם נדרש ביטוי סיסמה), וצפייה נרשמת רק בהצגה מפורשת ב-POST. ביטויי הסיסמה עוברים hash ב-SHA-256 בצד הלקוח, ויש מגבלה של חמישה ניסיונות לכל IP לדקה לכל סוד. כל תגובת הצגה נושאת Cache-Control: no-store, סודות שנמחקו או שפג תוקפם מחזירים HTTP 410 והתוכן השמור נדרס, ויומן הביקורת רושם כל אירוע עם IP שעבר hash עם salt שמתחלף כל יום. כתובת ה-IP הגולמית אף פעם לא נשמרת. כל הפרטים בדף ארכיטקטורת האבטחה.

    נסו בחינם, בלי הרשמה

    הודעה שמשמידה את עצמה

    שלחו פתק שנעלם אחרי קריאה אחת.

    פתיחת הכלי

    המגבלות של התכנון הזה

    הגבולות, בכנות: שום דבר מכל זה לא מגן על סוד אחרי הצגה לגיטימית. הנמען יכול להעתיק את התוכן, לצלם אותו או להעביר אותו הלאה. זה לא עוזר אם ביטוי הסיסמה נשלח יחד עם הקישור. וזה לא יכול למנוע מאדם שקיבל את הקישור בטעות לפתוח אותו לפני הנמען המיועד. בשביל זה יש יומן ביקורת: אתם רואים את הצפייה הלא צפויה ומחליפים את פרטי ההתחברות.

    מקורות

    כלים למאמר הזה

    שאלות נפוצות

    LinkPilot שולח קישור סודי שנמחק אחרי שפותחים אותו.

    מוצפן בדפדפן שלכם, מוצג פעם אחת ואז מושמד. ללא חשבון.

    יצירת קישור סודי

    לקריאה נוספת

    אבטחה

    איך משתפים מפתחות API בצורה מאובטחת

    אל תדביקו מפתח API בצ'אט או באימייל. שלחו קישור סודי חד-פעמי שמוצפן בדפדפן ומושמד אחרי פתיחה אחת, והחליפו כל מפתח שכבר הודבק.

    אבטחה

    איך עובדים קישורים סודיים חד-פעמיים

    קישור סודי חד-פעמי מציג את התוכן פעם אחת: הדפדפן מצפין אותו, השרת שומר רק טקסט מוצפן, וההצגה הראשונה מוחקת את העותק השמור.

    אבטחה

    קישורים עם תפוגה: הסוד מושמד, לא רק מפסיק להפנות

    קישור סודי עם תפוגה משמיד את הסוד במועד שנקבע; קישור מקוצר שמת רק מפסיק להפנות. איך בוחרים חלון זמן, ומה קורה במקרי הקצה.