ברוכים הבאים לאזור הבלוגים הטכנולוגיים של ITsafe

קריאה מהנה

FaceBook ImageGate & Chrome Manipulation

בבלוג זה אציג בפניכם מחקר שביצעתי ביחד עם דקלה ברדה. במחקר זה הצלחנו להעלות קובץ זדוני ל-CDN של Facebook על ידי ביצוע מניפולציה לדפדפן ויצרנו Fuzzer פשוט שעזר לנו לעקוף את ה-Image Parser של Facebook אשר מחק את הקוד הזדוני מהתמונה.

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

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

לסרטון הדגמה:


Part 1

בעת העלאת תמונה ל-Facebook, התמונה עולה ל-CDN לצורך שמירה על ביצועים וזמני תגובה מהירים יותר, ברגע שאתם מעלים תמונה ברזולוציה מעל ל- 962x541 התמונה עולה ל-CDN בלי פרמטרים אשר מזהים את התמונה עם חשבון כלשהו. כך זה נראה:

` https://scontent-lhr3-1.xx.fbcdn.net/t31.0-8/14102894_1137188709676282_2198558191558447569_o.jpg

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

` https://scontent-lhr3-1.xx.fbcdn.net/t31.0-8/14102894_1137188709676282_2198558191558447569_o.jpg?_nc_cat=0&oh=2d6cfd727aa2e63c31b3a10e34872ffa&oe=5BA12209

הצעד הראשון שלנו היה להעלות תמונות שונות ל-Facebook ברזולוציות שונות ולבחון אותן. לאחר שמצאנו את הרזולוציה המתאימה, שהיא מעל לרזולוציה 962x541 עברנו לשלב החדרת הקוד הבא לתמונה. לצורך הדגמה בלבד:

                                                `
<script>
    WshShell=new ActiveXObject("WScript.Shell");
    WshShell.Run('c:/windows/system32/calc.exe',1,false);
</script>
                                                
                                            





התוצאה נראית כך:
הזרקת קוד ActiveX לתוך תמונה
העלנו את הקובץ ל-Facebook ואז הורדנו אותו חזרה לאחר שהוא עבר את תהליך המרת התמונה של Facebook על ידי מפרש התמונות (Image Parser).
אנו מעלים תמונה לפייסבוק שמכילה את הקוד של ה-ActiveX
קיווינו שלפחות חלק מהקוד שהחדרנו יישאר, אך התוצאה הייתה בעייתית במידה מסוימת: כל הקוד שהכנסנו לתמונה נדרס על ידי מפרש התמונות שביצע דחיסה לתמונה על מנת לשמור על מקום.
בדיקה האם הקוד נשאר בתמונה ולא נמחק
כיצד עוקפים דבר כזה?

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

כך לאחר שינוי הקובץ הדחוס הצלחנו לפחות לראות חלק קטן מהקוד שהכנסנו.

איך מתקדמים מכאן?

לאחר מחשבה מרובה עלה בדעתנו לכתוב fuzzer שמשתמש ב-graph API אשר מבצע את הפעולות הבאות:

  • 1. קובע מיקום בקובץ ומחדיר לתוכו את הקוד שלנו.
  • 2. מעלה את התמונה ל-Facebook.
  • 3. מוריד את התמונה חזרה לאחר שעברה את הדחיסה.
  • 4. בודק האם הקוד שלנו נמצא בתמונה.
  • 5. במידה והקוד לא נמצא זזים byte אחד ימינה או שמאלה בהתאם לתוצאה הטובה ביותר.
  • 6. עוצרים אחרי 50 ניסיונות לצורך אבחון התוצאות ועדכון ה-fuzzer.
להלן הפונקציה המרכזית של הקוד שנכתבה:

                                                `
def payload_check():
    # Send Picture to Facebook
    r = requests.post("https://graph.facebook.com/v2.7/me/photos?access_FBToken={}&caption={}&url={}".format(FBToken,message,URLToImage))

    # Get the Picture Back
    r = requests.get('https://graph.facebook.com/v2.7/me/posts?access_FBToken={}&debug=all&fields=message,object_id,type&format=json&limit=1&method=get&pretty=0&suppress_http_code=1&with="{}"'.format(FBToken,message))
    image_code = json.loads(r.text)["data"][0]["object_id"]

    r = requests.get('https://graph.facebook.com/v2.7/{}/picture?access_FBToken={}&debug=all&format=json&method=get&pretty=0&redirect=false&suppress_http_code=1'.format(image_code,FBToken))
    # This URL will not contain the Payload, We need the full size one :)
    # print json.loads(r.text)["data"]["url"]

    # This url may contain the Payload!
    good_url = str(json.loads(r.text)["data"]["url"]).replace("s720x720/","")
    download_file(good_url)

    # Check if we see some part of the payload to enhance the positions
    with open("image.jpg","r") as f:
        if half_payload in f.read():
            print "[+] Found! Values:"
            print "Counter1: {}, Counter2: {}, Cursor: {}, half_payload: {}, Index: {}".format(counter1, counter2, cursor, half_payload, index)
            return True

        else:
            return False
                                                
                                            
שימו לב ש-Graph API מחזיר לנו תמונה בפורמט s720x720, התמונה קטנה יותר משמע חסרים לנו תווים! כדי לגשת לתמונה המקורית עלינו למחוק את ה-s720x720 מה-URL ואז לגשת לתמונה.

לאחר ההרצה הראשונה הצלחנו לאתר מיקום שמאפשר לנו להעלות כמעט את כל הקוד בשלמותו ואילו בהרצה השנייה ה-Fuzzer שלנו הצליח לאתר מיקום מדויק שמאפשר לנו להעלות את כל הקוד במלואו! מה שנראה כך:

מוסיפים כמה תווי A לפני הקוד ואחרי הקוד כדי לאתר את הקוד בקלות התווים ‘A’ שאתם רואים זה בעצם padding שה-fuzzer ביצע.

כך הצלחנו להעלות קוד לתוך תמונת .jpg לצד השרת אך זאת רק ההתחלה, עלינו כעת לגרום לקובץ לרדת עם סיומת .hta על מנת שהקובץ ירוץ, אחרת מה עשינו בזה?

אם נבחן את התשובה של השרת נראה שה-content-type שלו הינו image/jpeg כך שאם נוריד את הקובץ נקבל קובץ תמונה, לכן אנו צריכים למצוא דרך להמיר את ה-content-type למשהו אחר כגון application/octet-stram

תמונות עולות ל-facebook עם content-type שהינו jpg
לאחר מספר ניסיונות מצאנו שניתן להוסיף נקודה לסיומת של ה-url על מנת לקבל את ה-content-type הרצוי:
הוספת נקודה ב-url תגרום לשינוי content-type ל-octet stream
לאחר שהצלחנו לשלוט על ה-content-type עלינו לגרום לתמונה לרדת, כדי לעשות זאת אנו נוסיף את הפרמטר dl=1 אשר מציין download, מה שנראה כך:
הוספת dl=1 תגרום להורדת התמונה
נחזור ל-burp suit ונסתכל על החבילה, ניתן לראות שהפרמטר dl=1 מוסיף ל-URL את הכותר
` Content-Disposition: attachment

אשר מציין הורדת קובץ
התמונה ירדה בגלל הcontent-dispostion שהינו attachment
מכיוון ש-Facebook לא צרפו ל-Content-Disposition את שם הקובץ, על הדפדפן יהיה להחליט מה יהיה שם הקובץ שהוא מתכוון להוריד מהאתר.

Part 2

על מנת להבין על סמך מה הדפדפן מסיק מה יהיה שם הקובץ נסתכל בקוד המקור של פרויקט chromium:

` https://cs.chromium.org/chromium/src/content/renderer/loader/web_url_loader_impl.cc

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

הקוד של הפונקציה PopulateURLResponse
פונקציה זו מנהלת את כל המידע שהתקבל ומגיבה לכותרים בהתאם, כמו כן היא גם קובעת את שם הקובץ במידה ויש לנו content-disposition בכותרי התשובה של השרת.

אם אכן יש content-disposition הפונקציה הזאת קוראת לפונקציה GetSuggestedFileName :

הקוד של הפונקציה GetSuggestedFileName
בפונקציה headers->EnumerateHeader בודקים האם יש filename ב-content-disposition ואם אין אז value שהוא המשתנה השני לפונקציה GetSuggestedFilename יהיה בעצם ריק.
אם נסתכל בתוך הפונקציה הזאת נראה שהיא בסך הכל עוטפת את GetSuggestedFilenameImpl:
הקוד של הפונקציה GetSuggestedFilename פונקציה זו בודקת האם ה-content_disposition הינו ריק, ואם כן היא נכנסת לפונקציה GetFileNameFromURL
הקוד של הפונקציה GetSuggestedFilenameImpl
פונקציה זו בודקת האם הפנייה היא בעצם פנייה רגילה ולא data:// או about:// ואז קורית ל-ExtractFileName
הקוד של הפונקציה GetFileNameFromURL
אשר בסך הכל עוטף את DoExtractFileName
הקוד של הפונקציה GetFileNameFromURL הקוד של הפונקציה ExtractFileName
הפונקציה DoExtractFileName נראית כך: הקוד של הפונקציה DoExtractFileName
היא מנסה לאתר את שם הקובץ מסוף ה-URL ועד לסלש האחרון, אך אם יש לנו נקודה פסיק ";" אז שם הקובץ נקבע בין הסלש האחרון ועד לנקודה פסיק מה שנראה כך:
` /filename.extension;

Part 3

על סמך מידע זה אנו יכולים בעצם לשלוט על שם הקובץ שאנו רוצים שייבחר על ידי הדפדפן. לדוגמה:
שימוש בטכניקה הקודמת ושינוי שם הקובץ ל-facebook_password.exe
ה-URL נראה כך:
` https://scontent-lhr3-1.xx.fbcdn.net/t31.0-8/14102894_1137188709676282_2198558191558447569_o.jpg/facebook_password.exe;.?dl=1

כמובן שקובץ exe לא ירוץ מכיוון שה-Header של הקובץ אינו תואם ל-PE Header, אך מה שאנו כן יכולים לעשות הוא לשנות את הקובץ לסיומת .hta אשר תבצע את קוד ה-javascript שהכנסנו לקובץ קודם לכן בחלק הראשון.

מה שיראה כך:
שינוי שם הקובץ ל-facebook_password.hta כל מה שנשאר לנו לעשות זה להעתיק את הלינק ולשלוח אותו, וכל מי שילחץ על הלינק יגרום להרצת הקוד שלנו שמאוחסן בשרתי ה-CDN של Facebook.

שליחת הלינק ב-messanger של פייסבוק

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

פתיחת הלינק מריצה את הקוד

Part 4

בהסתרת הלינק ב-messenger, ניסינו לבדוק האם ניתן להסתיר את הלינק ב-messenger בצורה כלשהי והגענו לטריק הבא:

נעלה את התמונה שמכילה את קוד ה-JavaScript שהראיתי קודם לכן, אך הפעם נעלה אותה ל-Messenger:
שליחת קובץ hta ב-messanger נשחק עם ה-content-type וסיומת הקובץ, עבור ה-content-type נבחר image/svg+xml ועבור שם הקובץ נבחר שוב .hta כך:

שינוי ה-content-type של הקובץ גורם לו להיראות כמו תמונה

נשלח את הקובץ ונראה שהפעם אנו אפילו לא צריכים לינק, אלא שלחנו תמונה רגילה ב-Messenger:

facebook מציג את הקובץ כתמונה

לחיצה על התמונה תוריד לנו את הקובץ ותשנה את שמו ל.hta מכיוון שזו הסיומת שבחרנו לקובץ והפעם היא כן מופיעה ב-content-disposition:

לחיצה על התמונה מורידה את הקובץ My_Facebook_password.hta

לחיצה על הקובץ תפעיל את הקוד ובכך ירוץ המחשבון:

ליצה על הקובץ מפעילה את הקוד

Share this post