פרוטוקול OAuth2 הוא אחד הפרוטוקולים הנפוצים לזיהוי ואימות משתמשים. בשל הפופולריות הרבה שלו וכמות המחקרים שבוצעו בו עם השנים, הוא לגמרי שווה פוסט נפרד על כיצד הוא עובד ומה הם סיכוני האבטחה הרבים שצריך להיות מודעים אליהם בעת שימוש בו, סיכונים אותם נחקור בעת ביצוע מחקרי חולשות ומבדקי PT.
למעשה, קיימים מספר דרכים למימוש OAuth2 (נקראים Grant Types), חלקם כאלו שכבר הוצאו בשימוש והיו רלוונטיים בעיקר בגרסאות קודמות וחלקם כאלו שפעילים וניתנים לאיתור גם היום. בפסקאות הבאות אציג חלק מהם.
Authorization Code Grant
זוהי למעשה הדרך המקובלת לממש OAuth 2.0 ובמסגרתה מתבצע התהליך הבא (בצורה פשטנית):
- אנו לוחצים באתר על "התחברות באמצעות גוגל".
- האתר שולח אותנו לשרת האימות של Google עם הפרטים הבאים (אלו פרטי החובה):
- response_type=code (בקשה לקוד הרשאה)
- client_id=abc123… (מזהה של האתר מול Google)
- redirect_uri=yourapp[.]com/callback (הכתובת שאליה Google תחזיר את המשתמש)
- scope=email profile (מה האתר מבקש לגשת אליו)
- Google יבקשו מאיתנו הרשאה לשיתוף פרטינו (בהתאם ל-scop) ובמידה ואישרנו ייצור Authorization Code (אם כבר התחברנו בעבר, הוא יזכור את ההרשאות שנתנו ויתקדם אוטומטית).
- Google יבצעו Redirect מחדש לאתר (לכתובת שצוינה ב-redirect_uri) עם ה-Autorization Code (בדרך כלל באמצעות בקשת get).
- האתר יעתיק את הקוד מה-URL ויעביר אותו לשרת יצירת ה-Tokens של Google בבקשת POST ע"מ לספק לנו Access Token (לצד הclient_secret של האתר אשר מאמת את האתר כמורשה).
- Google יחזירו Access Token לשרת כ-Respose.
- האתר ייגש ל-User Info Endpoint של Google עם ה-Access Token ע"מ לקבל מידע על המשתמש.
- השרת של Google יחזירו את המידע על המשתמש לאתר.
- האתר יבדוק האם המשתמש קיים (אם כתובת המייל כבר רשומה במערכת) ובמידה וכן יכניס את המשתמש.
חשוב לדעת שמקובל להוסיף ל-OAuth2 תהליך נוסף בדרך שקצת משנה את ה-Flow שהצגתי, הלא הוא OpenID Connect.
במסגרת OpenID Connect, בשלב ה-scop האתר מבקש גם openid והמשמעות של כך היא שבשלב 6, יחזור אליו לא רק ה-Access Token, אלא גם כל פרטי המשתמש חתומים ב-JWT (כך שהוא לא יצטרך לבצע את שלב 7 ולפנות ל-User Info Endpoint). כך אנו חוסכים שלב בתהליך ובעיות אבטחה פוטנציאליות.

Authorization Code + PKCE
כפי שניתן להבין, ה-Authorization Code הוא הקוד שמאמת את הרשאת האתר לגשת לפרטי המשתמש והוא זה שמאפשר את יצירת ה-Access Token. על מנת שתוקף לא יוכל ליירט אותו (באמצעות מתקפת MITM לדוגמא), בשלב 5 האתר (צד השרת) צריך להעביר לשרת יצירת ה-Tokens לצד ה-Authorization Code גם את ה-Client Secret שיאמת שהוא מורשה לקבל את ה-Access Token.
מה קורה באתר שהוא ללא Backend כלל ואז תוקף יכול ליירט את אותו client secret? במקרה כזה מוסיפים PKCE (ר"ת של Proof Key for Code Exchange) לתהליך.
במסגרת זה, לפני שלב 2 מייצר האתר 2 ערכים נוספים: code_verifier (מחרוזת אקראית סודית) ו-code_challenge (ערך ה-verifier כשעליו מבוצע SHA256). כעת, בשלב השליחה לשרת האימות של Google, שולח האתר בנוסף לכל הפרטים לשרת האימות של Google גם את ה-code_challenge ו-code_challenge_method (סוג ה-Hash).
לאחר מכן, בשלב 5, האתר ייגש ל-Google ע"מ לקבל את ה-Access Token, אך יוסיף לבקשה גם את ה-code_verifier (אותו קוד שהוא ייצר בתחילת התהליך, אך שמר אותו בדפדפן ול-Google העביר רק ערך מגובב שלו).
כעת שרת האימות של גוגל יבדוק האם ביצוע גיבוב של SHA256 (או כל Method אחר בהתאם למה שנשלח מהאתר) על ה-code_verfier מחזיר ערך תואם ובמידה כן, יספק את ה-Access Token לאתר.

שתי השיטות האחרונות שהצגתי הן למעשה השיטות הנפוצות והמקובלות למימוש תהליך OAuth2, אך בואו נדבר גם על שיטות ישנות יותר:
Implicit Grant
זוהי שיטה שכבר יצאה משימוש בגרסת 2.1 של OAuth2 והייתה השיטה הנהוגה באפליקציות ללא צד שרת קודם השיטה המאובטחת שעושה שימוש ב-PKCE.
בשיטה זו, כאשר לא הייתה גישה לשרת Backend לשם ניהול סודות. בשיטה זו, במקום להשתמש ב-Authorization Code ולבצע שלב נוסף של החלפה, התהליך כולו התבצע בבת אחת, אך ורק באמצעות Access Token שהוחזר ישירות לדפדפן.
בשיטה זו, האתר הפנה את המשתמש ל-Authorization Server של Google עם בקשה מסוג response_type=token (ולא code).
אם המשתמש אישר את הבקשה, Google הייתה מחזירה את המשתמש ל-redirect_uri, אך הפעם עם ה-Access Token ישירות בתוך כתובת ה-URL, ב-Fragment (לאחר #, חשוף רק בדפדפן).
על-אף השימוש ב-Fragment (שלא מאוחסן ב-Cache ולא פגיע ל-Referer Leakage) זוהי כמובן שיטה לא מאובטחת בעליל, בשל כך שה-Access Token נשלח ישירות בבקשת get והוא עדיין זמין לשלל מתקפות בצד הלקוח.

Client Credentials
זוהי שיטה שונה מהשיטות הקודמות בכך שהיא כלל אינה מערבת את המשתמש בקצה. כלומר, אין כאן תהליך שבו המשתמש לוחץ על "התחברות עם Google" או מאשר גישה לפרטיו. שיטה זו מיועדת למצבים בהם שרת (לו נקרא ה-Client בתהליך) מעוניין לגשת למשאב שמוגדר על שרת אחר (Resource Server) בשמו של עצמו ולא בשם משתמש.
למשל: שירות שרוצה למשוך נתונים סטטיסטיים מ-API של ספק צד שלישי כחלק מתהליך אוטומטי, ללא מעורבות של משתמש ועם הרשאות שיש לו מול API זה.
בתהליך זה:
- השרת (Client) שולח בקשה ל-Authorization Server של שירות הצד ג' עם הפרטים הבאים:
- grant_type=client_credentials
- client_id=abc123…
- client_secret=xyz456…
- scope=read (משתנה בהתאם לרמות וסוגי ההרשאה שהוגדרו ע"י ה-Authorization Server).
- ה-Authorization Server מאמת את זהות ה-Client באמצעות ה-client_id וה-client_secret.
- במידה והפרטים תקינים, השרת מחזיר ל-Client את ה-Access Token.
- כעת ה-Client יכול להשתמש ב-Access Token כדי לשלוח בקשות מאובטחות ל-Resource Server (למשל API מוגן).

חולשות אבטחה במבנה וביישום OAuth2
מטבעו, בשל כך שה-OAuth2 הוא Token מסוג Bearer token (או JWT כפי שמחזירים חלק מהשירותים כמו Google ו-Azure) או Opaque שמצריך פנייה לשרת האימות בכל בקשה (כפי שמחזיר Facebook), הוא בעל "סיכון אבטחה" מובנה: מי שמקבל גישה ל-Token, מקבל גישה לחיבור לחשבון המשתמש.
בשלב הזה זה כבר תלוי כיצד המפתח הגדיר את תהליך ה-OAuth2 ופה אפשר להיתקל בהמון ליקויים.
OAuth CSRF
בעת מימוש תהליך OAuth2 כפי שהדגמתי אותו יש "ליקוי אבטחה" מובנה. אמנם ה-Access Token נשלח בבקשת POST, אבל ה-Authorization Code נשלח בבקשת GET בתחילת התהליך.
ופה יש בעיה שמזכירה קצת את מתקפת ה-CSRF: במידה ותוקף מצליח להשיג את ה-URL עם ה-Authorization Code או מבצע בעצמו תהליך התחברות ושולח לקורבן את ה-Authorization Code, הרי שהאתר שמממש את ה-OAuth2 לא יודע שהמשתמש שממשיך את התהליך הוא לא המשתמש שהתחיל אותו והוא מחבר את המשתמש לחשבון של ה-Client ID שנשלח לו בתחילת התהליך.
ע"מ לפתור את זה, RFC 6819 מציין כי יש להשתמש במנגנון state אשר יוצר ערך אקראי המזהה את המשתמש בשלב הלחיצה של המשתמש על "התחבר באמצעות Google" ומאחסן אותו בצד השרת, ב-session או ב-cookie. בשלב 2, בעת שליחת הבקשה, על פרמטר ה-state להישלח עם הבקשה. בשלב 3, Google יחזיר את ה-Authorization Code לצד ערך ה-state ובשלב זה האתר בודק שהערך זהה לערך ששמור אצלו (אם הערך שונה או שלא שמור ערך כמו במקרה של המתקפה שהצגנו, הוא דוחה את הבקשה).
חשוב לציין שעל-אף שבמרבית המקרים המתקפה הזו בפני עצמה לא נחשבת לחמורה מדיי (שכן בסופו של דבר הקורבן נכנס לחשבון התוקף ולא להיפך), היא עלולה להוות חוליה בשרשרת של מתקפה גדולה יותר. מלבד-זאת, תחשבו על תרחיש שבו באתר אשר מממש אימות בדרך רגילה יש אפשרות לקשר את חשבון המדיה החברתית של המשתמש ולאחר מכן להתחבר דרכו. במידה ותוקף יבצע חיבור רגיל לאתר ולאחר מכן ילחץ על חיבור באמצעות הרשת החברתית, אך לא יעביר את הבקשה הלאה ובשלב קבלת ה-Code יעביר את ה-URL לקורבן, כאשר הקורבן ילחץ על הלינק הוא יעביר את בקשת ה-Code הלאה ויקשר את חשבון המדיה החברתית של התוקף לחשבון שלו, כך שהתוקף יוכל להתחבר לחשבון של הקורבן בקלות.
עבור הדגמת המתקפה בהתאם לדוגמא האחרונה שציינתי (אפשרות של קישור חשבון מדיה חברתית), אציג את המעבדה של PortSwigger המדגימה את מתקפה זו. ראשית, נזין את שם המשתמש והסיסמא wiener:peter בדף ה-Login אשר יופיע בעת לחיצה על "my account":

לאחר מכן נלחץ על Attach a social profile:

ונוכל לראות ב-Burp את תהליך ה-OAuth2, ללא שימוש ב-state:

בשלב זה "נזין את פרטי המדיה החברתית" המדומים, peter.wiener:hotdog:

נבצע התנתקות וניגש מחדש לחלון ה-Login. עכשיו נוכל לראות בתחתית שלו גם אפשרות להתחברות באמצעות חשבון המדיה החברתית:

נלחץ על אפשרות זו ונתחבר אוטומטית. כעת ב-My Account נלחץ שוב על Attach a social profile ונבצע את תהליך ההתחברות באמצעות המדיה החברתית מחדש, הפעם לאחר הפעלת Intercapt.
חשוב לזכור: אנו לא רוצים לבצע את ההתחברות בפועל, אלא רק לקבל את "הקוד" ולאחר שקיבלנו את הקוד, לבצע Drop לבקשה ולגרום ל-Admin או למשתמש אחר להשתמש בקוד זה, באמצעות שליחת הלינק אליו או מימוש iframe.
נבצע העתקה של ה-URL בבקשה זו (oauth-linking) ולאחר מכן נבצע לה Drop (ע"מ שהקוד יישאר בתוקף):

כעת נבצע iframe ל-URL זה באמצעות ה-Exploit Server ונבחר באפשרות Deliver exploit to victim:

כעת, לאחר שה-Victim (משתמש ה-admin במעבדה זו) נכנס לאתר עם ה-code שלנו, אם נלחץ שוב על "Login with social media", נוכל לראות שהתחברנו לחשבון ה-Admin ויש לנו אזור של "Admin panel":

OAuth Redirect Attacks
מתקפה זו מתמקדת בשלב 2, ב-redirect_uri ששולח האתר ל-Authorization Server. ע"פ ה-RFC 6819, על ה-Authorization Server לוודא ש-redirect_uri תואם ל-registered_redirect_uri, אותה כתובת שעל מפתח האתר להזין בעת ההרשמה לשירות ה-OAuth2.
אלא שמפתחים רבים (או Authorization Servers) לא עושים זאת וחלקם, על-אף שהם עושים זאת, מזינים ככתובת מותרת את הדומיין הכללי ולא URI ספציפי. המשמעות? אם אין בדיקה מספקת של ה-registered_redirect_uri, תוקף יוכל להכניס אחת משלו / לאתר Open Redirect באתר (אפילו בסאב-דומיין) ולהכניס אליו את הכתובת שלו. כך, הוא יקבל את ערך ה-code לאתר שלו ויוכל להמשיך את התהליך מול שרת האימות ולהזדהות בשם הקורבן.
הנה התרחיש המלא:
- התוקף עורך את בקשת ההתחברות (שלב 2 ב-Authorization Code Grant) ומזין כ-redirect_uri כתובת בשליטתו (או כתובת מהימנה של האתר עם redirect לאתר שבשליטתו).
- התוקף שולח את הקישור לקורבן.
- הקורבן לוחץ על הקישור, רואה שאפליקציה מהימנה מבקשת הרשאה ונותן לה.
- ה-code נשלח לאתר התוקף.
- התוקף פונה לשרת ה-Tokens עם הקוד בבקשה לקבל access_token (או פונה ישירות לגישה, במקרה של Implicit Grant).
- שרת ה-Tokens מאמת את הקוד ונותן לתוקף גישה בשם המשתמש.
אז-כן, התרחיש הזה הוא תלוי בהגדרה של תהליך ה-OAuth2 ע"י האתר. בתהליך Authorization Code Grant נכון יש client_secret שמונע מהתוקף לבצע את שלב 5 אם אין בידיו את ה-secret. לעומת-זאת, במימושים כמו Implicit Grant ואפילו במימוש המאובטח של Authorization Code + PKCE התרחיש הזה לגמרי ריאלי. תחשבו על זה, בתהליך ה-PKCE, ה-code_verfier נוצר בתהליך הראשוני אותו התוקף מבצע ועל כן הוא יישמר אצלו.
ע"מ להימנע מכך, על המפתח להגדיר התאמה מדויקת ל-redirect_uri ולא להסתפק בהתאמת הדומיין הראשי. בנוסף, חשוב תמיד לשלוח גם מזהה (כמו client_secret) שיאפשר לוודא ששולח הבקשה ל-access token הוא אותו שרת ששלח הבקשה ל-code.
חשוב-לציין שמבחינתנו כחוקרי חולשות, חשוב תמיד שננסה את מגוון הטכניקות האפשרי למעקף של התאמת הדומיין, טכניקות דומות למה שאנו מנסים בעת ניסיון לממש SSRF, מעקף CORS או Host Header Injection. לדוגמא: גם-אם ניסיתם URI אחר והבקשה נחסמה, יכול להיות שההגנה שהמפתח הגדיר הייתה שהדומיין צריך להתחיל בצורה זהה לדומיין באתר ולא התאמה מלאה (לדוגמא, להתחיל ב-website ואז במידה ודומיין ה-callback שלכם יתחיל במילה website תצליחו לבצע מעקף של ההגנה). בנוסף, יכול להיות שהמפתח החריג מצבים שבהם הדומיין מתחיל ב-localhost ובמקרה כזה שוב תוכלו לעשות התאמות ולבצע מעקף.
טכניקות נוספות יכולות להיות שימוש כפול בפרמטרים (הזנת פעמיים redirect_uri ואולי ההגנה מתבצעת כלפי הראשון בלבד), ניסיון להטעות עם סכמת ה-URI (לדוגמא, https://default-host.com@evil.com, וכך השרת מבצע ולדיציה וסומך על השדה שמיועד ל-username, בעוד כתובת האתר היא בפועל מה שמופיע לאחר ה-@) וכן הלאה.
בנוסף וכפי שכבר הזכרתי בתחילת הפסקה, גם אם ההגנה היא על הדומיין כולו (אך לא על נתיב ספציפי בלבד), איתור Open Redirect באתר הנחקר יוכל לשמש את התוקף לביצוע בקשה מורשית וקבלת ה-Code. אפשרויות נוספות הם ניצול מתקפות שעלולות להוביל ל-Referer Leakage מפניית ה-GET אשר שולחת את הקוד ובכך התוקף יוכל להיחשף לקוד.
Scope Upgrade
כפי שפירטתי במבוא, בשלב 2 של תהליך ה-Authorization Code Grant נשלח ה-scope, ערך המבטא אלו הרשאות האתר מבקש בחשבון המשתמש. אלא שיש שירותים אשר ממשים את את תהליך ה-OAuth2 בצורה לא נכונה ושולחים שוב פרמטר scop גם בעת שליחת ה-Access Token.
במידה וה-Autorization Server מתייחס לפרמטר ה-scop בבקשה האחרונה כקובע, הרי שלאחר כל תהליך ההזדהות שבוצע ע"י הלקוח, תוקף (שמפעיל את האתר הזדוני אליו המשתמש נכנס) יוכל לשנות את פרמטר ה-scop מ-read ל-write לצורך הדוגמא ולקבל הרשאות גבוהות יותר בחשבון המייל שלו (שוב, לצורך הדוגמא), מבלי שהמשתמש כלל אישר אותם.
ע"מ למנוע זאת, ההמלצה היא להיפטר מהפרמטר scope בבקשת Access Token בתהליך, או לפחות לוודא שתכולתו תואמת את זו שנבחרה בהתחלה.
Client Confusion
חולשה זו רלוונטית יותר בשירותים אשר עושים שימוש ב-Implicit Grant והיא מתמקדת בתהליך של שליחת ה-Access Token ע"מ לאמת את המשתמש. בשלב זה, על האפליקציה לוודא שה-Token שקיבלה התקבל מתוך תהליך ההרשאה שביצע הלקוח ולא מתוך תהליך הרשאה של הלקוח באתר אחר לדוגמא.
אם לא מתבצע אימות כזה (שהוא בדרך כלל באחריות האתר ולא באחריות ה-Authorization Server), תוקף יוכל ליצור אתר, לממש בו OAuth2 באמצעות שירות זהה לשירות שעושה בו שימוש האתר הנתקף, לקבל Access Token של משתמשים ולהשתמש בו בשביל להתחבר לחשבון המשתמשים באתר הנתקף.
דוגמא לפגיעות הזו ניתן לראות במחקר שביצע חוקר ה-Security אביעד כרמל מ-Salt Security בשורה של שירותים נחשבים, כגון Vidio, Bukalapak ו-Grammarly. שירותים אלו עשו שימוש ב-OAuth2 של Facebook, אך לא אימתו שה-Access Token של המשתמש הונפק עבור השירות שלהם, אלא רק וידאו מול Facebook שה-Access Toekn תקין ומזהה את המשתמש הספציפי. בצורה זו, תוקף יכל להשתמש ב-Access Token של המשתמש שהונפק באתר שלו ולהתחבר לחשבון המשתמש בשירותים אלו. את פירוט המחקר המלא וההבדלים בין הדרך ש-Grammarly מימשה את ה-OAuth2 לבין הדרך ששאר השירותים עשו זאת, תוכלו לקרוא במחקר המלא.
Redirect Scheme Hijacking
מתקפה זו מתמקדת בעיקר בשימוש ב-OAuth2 באפליקציות Mobile. באפליקציות Mobile נהוג להשתמש ב-custom schemes שהם שהם URI ייעודיים (לדוגמה: myapp://oauth2/callback) שממופים ע"י מערכת ההפעלה לפעילות/מסך באפליקציה. הבעיה: כל אפליקציה יכולה “להירשם” לאותה סכימה, ולכן אפליקציה זדונית יכולה לציין סכימה של אפליקציה לגיטימית וליירט את ה-Authorization Code.
ע"מ למנוע-זאת, רצוי להשתמש ב-Authorization Code + PKCE (אותו הצגתי בשלב המבוא) אשר יוצר גם code_verifier אליו אפליקציית התוקף לא תהיה חשופה. אפשרות נוספת (וטובה לשימוש בלי קשר) היא שימוש ב-App-Links ב-Android וב-Associated-Domains ב-IOS, שתי טכניקות אשר יוצרות קישור בין דומיין לאפליקציה, כך שרק אפליקציה מאומתת (שאומתה בשרת האתר) תוכל לפתוח את ה-URI.
חשוב לציין שב-IOS וב-Android הוטמעו מנגנוני הגנה שמטרתם לצמצמם את המתקפה, כגון מנגנון ה-ASWebAuthenticationSession ב-IOS אשר ייתן עדיפות ל-schema של האפליקציה שפתחה את ה-session ומנגנון ה-autoVerify שמאפשר לחייב אימות דומיין ב-Android. אלא שמתקפה זו עדיין רלוונטית במצבים בהם ההגנות לא מומשו. עוד על המתקפה תוכלו לקרוא בהרחבה כאן ובנוסף, במחקר שפורסם ע"י החוקרים אוון קונלי וג'וליאן אהרנס ביוני 2024 ומציג דרכים למעקף ההגנות של IOS למתקפה זו.
Mutable Claims
מתקפה זו עוסקת במקרה בו מתבצע שימוש בפרוטוקול הזדהות (כגון OpenID Connect) לצד ה-OAuth2. פרוטוקול OpenID Connect מוסיף ID Token חתום ב-JWT אשר משתמש בשדות כמו sub (קיצור של Subject, זהו מזהה ייחודי של המשתמש) או email כדי לזהות משתמשים.
ככלל, הדרך הרשמית המוגדרת היא לבצע שימוש ב-sub, אך במידה ומפתח האתר מסתמך על שדות אחרים, כגון שדה ה-email ע"מ לזהות את המשתמש (ולא על ה-sub שהוא מזהה ייחודי), אך לא דורש אימות לכתובת הדוא"ל, תוקף יוכל ליצור חשבון עם כתובת דוא"ל זהה ללא אימות הכתובת (לדוגמא ב-Azure Active Directory) ולקבל את הרשאות המשתמש האחר או במקרים גרועים יותר, להחליף את כתובת הדוא"ל לאחר מכן למרות שאין לו שום קשר אליה.