באג של הזרקת HTML מאוחסנת (stored HTML injection) בלוח הדרושים RamenHire אפשר לכל אחד למלא טופס ציבורי ולהפוך את אימיילי ההתראות של הצוות לפלטפורמת פישינג (phishing).
איך הבאג חלחל פנימה
RamenHire פועלת על Next.js ו-Supabase ומאפשרת לסטארט-אפים לפרסם משרות ללא צורך בהתחברות. כל שליחה מפעילה שליחת אימייל לצוות הגיוס הפנימי. גוף האימייל נבנה על ידי חיבור שדות טופס גולמיים — שם החברה, תיאור המשרה, שם איש הקשר — ישירות למחרוזת HTML. לא מתבצע escape או sanitisation לפני שהמחרוזת מגיעה לשירות המייל.
מכיוון שהתבנית מתייחסת לקלט המשתמש כ-HTML בטוח, מועמד זדוני יכול להזריק קישור מזויף של "לחץ כאן לאימות". ה-payload נשמר בשרת כחלק מפרסום המשרה, כך שכל אימייל התראה עוקב נושא את הקוד הזדוני. תוקף יכול להסתיר את הקישור הזה לצד כפתור ה-"View Dashboard" האמיתי, ובכך להטעות חבר צוות לחשוף פרטי גישה או לבקר באתר פישינג.
למה זה היה משמעותי
האימיילים מגיעים לצוות הגיוס המרכזי, אנשים שלוחצים על קישורים ללא הרף כדי להעביר מועמדים לאורך תהליך הגיוס (pipeline).
הפער הטכני
ביצוע escaping ל-HTML אינו שלב בודד. יש צורך בהגנה בשני הקשרים (contexts):
- Text nodes – תוכן בין תגיות. המרת
<ל-<ו->ל->עוצרת תגיות, אך אינה מונעת מתוקף "לצאת" מתוך ערך של מאפיין (attribute). - Attribute values – מחרוזות בתוך תגיות כגון
href="…". הזרקת גרשיים ("או') מאפשרת לתוקף לסגור את המאפיין מוקדם מדי ולהכניס markup משלו.
הקוד המקורי טיפל רק בטקסט פשוט (plain text), מה שהשאיר את הזרקת המאפיינים (attribute injection) פתוחה לרווחה.
פתרון הבעיה
המפתח הוסיף פונקציית עזר קטנה בשם escapeHtml ויישם אותה על כל ערך שסופק על ידי המשתמש לפני ה-interpolation, בין אם הערך מגיע ל-text node ובין אם למאפיין (attribute).
שלבי אימות
- Local testing – סדרת מחרוזות הזרקה הוזנה לפונקציות התבנית. כל בדיקה הראתה רק תווים שעברו escape, ללא תגיות ניתנות להרצה.
- Production testing – לאחר פריסת התיקון, payload נשלח דרך האתר החי ועבר בהצלחה את בדיקת הבוטים של Cloudflare Turnstile. האימייל שנוצר הגיע לתיבת הדואר הנכנס של המפתח; הקישור הזדוני וכל תגיות ה-script הופיעו כטקסט משובש, ולא כאלמנטים לחיצים.
הערה לגבי Gmail: השירות עשוי עדיין לצבוע URL בכחול ולהפוך אותו ללחיץ, אך מדובר בנוחות בצד הלקוח (client-side) וזה לא אומר שהזרקת ה-HTML הצליחה. התיקון מונע מההזרקה ליצור כפתורים אמיתיים או להריץ סקריפטים.
מה מפתחים צריכים לשים לב אליו
- לעולם אל תסמכו על נתונים מטופסי ציבור – גם טפסים שנראים תמים יכולים לייצר פלט מאוחסן (stored output) המשמש במקומות אחרים.
- בצעו escape בנקודת השימוש – יישמו escaping מותאם הקשר (טקסט לעומת מאפיין) ממש לפני הרינדור (rendering), ולא בשלבים מוקדמים יותר ב-pipeline.
- בדקו גם בצד הלקוח וגם בצד השרת – בדיקות יחידה (unit tests) תופסות כשלים ברורים; בדיקת end-to-end ידנית דרך ממשק המשתמש החי מאשרת שההגנות מחזיקות מעמד תחת תעבורה אמיתית.
- היו מודעים למוזרויות של לקוחות אימייל – לקוחות מסוימים יוצרים קישורים אוטומטיים ל-URLs, מה שיוצר תחושת ביטחון שגויה. ודאו שה-HTML שבבסיס אינו מכיל אלמנטים פעילים.
שורה תחתונה
מחדל בודד בטיפול בקלט משתמש הפך אימיילי התראה שגרתיים לווקטור פישינג. הכנסת שגרת HTML-escaping מקיפה ואימות התיקון באופן מקומי ובסביבת הייצור החזירו את שלמות תיבת הדואר הנכנס. המקרה מדגיש שיעור נצחי עבור כל אפליקציית ווב שמייצרת HTML מנתונים לא מהימנים: sanitisation תקין הוא תנאי שאין עליו ויכוח, והתעלמות ממנו עלולה לפתוח נתיב ישיר לתיבות הדואר של המשתמשים שלכם.
