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

נגדיר שלושה מושגים בהם:

  • מקליד - הצד התוקף, הצד אשר שולח את הקוד הזדוני
  • נמען - הקורבן, מקבל את הקוד הזדוני מן השרת ופועל בהתאם
  • שרת - ייחוס לשרת ה socketio האחראי על העברת ה events בין המשתמשים

כאשר נכנס לדף הצ’אט ונבדוק את ה sources נוכל לראות את הקובץ private.js אשר אחראי על תפקוד רשימת ההודעות, חלק מן הקוד הכתוב בקובץ מכיל את השורות הבאות:

// https://static.fcdn.co.il/dyn/projects/privatemessage/private.js

socket.on('type_to_pm_list', function(parent_id) {
    satart_useridtyping(parent_id);
});

function satart_useridtyping(parent_id) {
    if ($('[data-parent-id=' + parent_id + ']').length > 0) {
    	...
    }
}

השורה הראשונה מאזינה לאיוונט ההקלדה של ה”מקליד” וכאשר האיוונט מופעל עם הערך parent_id הוא קורא לפונקציה satart_useridtyping איתו.

פעולתה של הפונקציה satart_useridtyping פשוטה, היא אחראית על הצגת מצב ההקלדה הנוכחי של ה”מקליד”

ניתן להבחין כי השורה הראשונה של הפונקציה מכילה ביטוי jQuery אשר אמור למצוא אלמנט בעל תכונה מסוימת. מהיכרותי עם jQuery ידוע לי שבגרסאות המוקדמות של הספרייה קיימת בעיה הנובעת משימוש בביטוי Regex שאינו קפדני מספיק ומאפשרת להזריק תגי HTML אל ביטוי ה selector מה שיוביל לפירוש שגוי של הערך הביטוי כהוראה ליצירת אלמנט HTML.

מציאת החולשה:

נוכל לערוך את ערך הפרמטר המועבר מן ה”מקליד” אל הנמען ובכך לנסות ליצור אלמנט המריץ קוד javascript, נצטרך לראות שהשרת המקשר לא מסנן את התווים הנחוצים להרכבת אלמנט html.

נתחיל בלהסתכל על ה”מקליד” ולחפש את הטריגר של איוונט הכתיבה.

// https://static.fcdn.co.il/dyn/projects/privatemessage/privatechat3.js

function typeingsend(){
  socket.emit('pmtype_to', user_id_of_pm_sender,USER_ID_FXP, parent_pmid,nodetoken); 
  socket.emit('pmtype_to_list', user_id_of_pm_sender, parent_pmid,USER_ID_FXP,usertokenpm );
}

נוכל להבחין בזימון האיוונט pmtype_to_list בצירוף ערכים שונים (רובם אחראים על הכוונת השרת לשליחה לנמען) ניתן לראות פרמטר מוכר הנקרא parent_pmid נוכל לראות אותו בצד הנמען, בפעולה שחקרנו לפני כן. נשאר לנו לבדוק האם ניתן לקרוא לפעולה לפעולה satart_useridtyping עם ערך אשר השרת לא סינן.

נשלח מ”המקליד” לשרת את האיוונט הבא:

socket.emit('pmtype_to_list', user_id_of_pm_sender, '<img onerror=alert(1) src', USER_ID_FXP, usertokenpm);

וכתגובה הנמען יקבל מן השרת את זימון האיוונט הבא:

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

עדכון – xss נוסף:

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

// https://static.fcdn.co.il/dyn/projects/privatemessage/private.js

socket.on('lisendeletemessage', function(data) {
    ...
    if ($('[data-pm-text-id=' + data + ']') == null || $('[data-pm-text-id=' + data + ']')[0] == "undefined") {
        return;
    }
    ...
});

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

כאשר נסתכל על הפעולה אשר אחראית על הפעלת האיוונט ושליחת המידע ונחקור את הערכים המועברים אל האיוונט נוכל לראות כי הפרמטרים אינם מעידים על הצ’אט הנוכחי והאלמנט המשפיע הוא user_id_of_pm_sender

// https://static.fcdn.co.il/dyn/projects/privatemessage/copymessage3.js

function deleteMessage(pmid, datatextid) {
    ...
    socket.emit('deletemessage', user_id_of_pm_sender, datatextid, myuseridsocket, nodetoken);
    ...
}

לכן נוכל לנסות להסיר את שאר הפרמטרים ולשלוח הודעה בפורמט הבא:

socket.emit('deletemessage', user_id, 'MESSAGE ID');

הודעה זו תאפשר לנו לשלוח קוד זדוני לכל משתמש ולהריצו במידה והוא נמצא באזור הצאט באתר.

חשוב לציין שהבעיות לא מחייבות כניסה של המשתמש לשיחה פעילה אלא את הכנסותו לדף השיחות הראשי