כשמדברים על בדיקות אבטחת תוכנה, האסוציאציה היא מיד ל-white-hat hacking ,penetration testing וכדומה. אבל מסתבר שגם בודקי התוכנה ה"רגילים" – בני תמותה כמונו - יכולים לתרום הרבה לשיפור הקשיחות של התוכנה והעמידות שלה לפריצה. בדיקות אבטחה מסוג זה הן במידה רבה הרחבה של בדיקות פונקציונאליות שאנו ממילא מבצעים, ולא תחום חדש לגמרי. לעיתים זה פשוט עניין של שימת לב נוספת להתנהגות התוכנה תוך כדי הרצת בדיקות שממילא אנו מתכננים.
אחת הפונקציות שהקשר שלהן לאבטחת תוכנה ברור ומובן אינטואיטיבית היא ה-log in. כל אתר שמספק שירות וחלק מאתרי התוכן כוללים פונקציה זו, וכל סט בדיקות פונקציונאליות של האתר מכיל מן הסתם גם בדיקות של מסך הכניסה למערכת. למשל, בדיקה שמשתמש רשום יכול להכנס רק אם סיפק סיסמה נכונה; שמשתמש לא רשום לא נכנס, ושיש יכולת שיחזור סיסמה ("שכחתי סיסמה").
כיוון ששדות הקלט (שם משתמש, סיסמה) מקבלות מחרוזות, הרי שגם נבדוק מקרי קצה: מחרוזת ריקה, מחרוזת באורך 1 ומחרוזת באורך מקסימלי.עוד סט בדיקות שעדיין "יושב טוב" בתוך התחום הפונקציונאלי הם הבדיקות של הקוד שמבטיח איכות מינימלית של הסיסמה (לפחות 8 תווים; לפחות ספרה אחת, אות גדולה ואות קטנה, וכו'). כללים אלה נועדו להקשות על ניחוש הסיסמה על ידי פורצים. עד כאן, הכל פונקציונאלי: עובד נכון או לא עובד. מכאן אציין עוד מספר בדיקות ש"מתרחקות" מבדיקת היכולת הבסיסית של אימות המשתמש ומטרתן לוודא שמסך הכניסה לא סובל מחולשות שיקלו על האקרים לחדור לאתר. בדיקות אלו מתייחסות גם לדרך המימוש של אימות זהות המשתמש, ולא רק ל"עובד\לא עובד".
זליגת מידע
על מנת לפרוץ לחשבון של מישהו, צריך לפחות שני פריטי מידע: שם המשתמש וסיסמה. אם סתם אנסה המון צירופים של שמות משתמש וסיסמה, צפויה לי הרפתקה ארוכה ומתסכלת עד שבמקרה אפול על צירוף נכון. על כל שם משתמש שאנחש, עלי לנסות את כל הסיסמאות שאני מנחש. כמה נחמד היה אם יכולתי לקבל רמז ששם המשתמש שנתתי אינו תואם אף אחת מהשמות שרשומים באתר ולכן אין טעם לנסות שם זה עם המון סיסמאות! הנה דוגמה לא טובה: אני מנסה לעשות log-in לשרת של חברה קטנה מרדמונד, ומכניס שם משתמש שאינו נכון. בתגובה, המערכת טורחת להודיע לי ששם המשתמש אינו נכון!:
התנהגות כזאת נקראת "זליגת אינפורמציה". המערכת מספקת מידע שאינו קריטי לי: הרי אני יודע את שם המשתמש שלי, ויכול לראות שעשיתי טעות בהקשה. מצד שני, הודעת השגיאה מכילה אינפורמציה פרטית שאולי אני רוצה לשמור בסוד. עצם העובדה שיש לי חשבון באתר מסויים יכולה לגרום לי מבוכה או צרות (ראו https://en.wikipedia.org/wiki/Ashley_Madison_data_breach ).
דרך אגב: בדקתי, וגם הכניסה לגוגל מתנהגת ככה... אני נותן לעצמי להניח שאולי בגלל ששם המשתמש החוקי הוא אימייל של המשתמש, שהוא לא בדיוק סוד, לכן אין בעיה עם זה. או שהחבר'ה האלה יודעים מה הם עושים, ויש להם הגנות מספקות אחרות מעבר לשם המשתמש. אבל בעצם עדיף שמסך הכניסה ידרוש גם שם משתמש וגם סיסמה, וכשאחד מאלה אינו נכון, יתן הודעה כללית, כמו שאני מקבל בהכנסת סיסמא שגויה אחרי שם משתמש נכון:
שימו לב שההודעה לא מספרת לי מה לא נכון – שם המשתמש, הסיסמה, או שניהם – וזו התנהגות נכונה. בדוגמה כאן זה קצת מצחיק כי אי אפשר להגיע למסך הסיסמה בלי להכניס שם משתמש נכון, אבל העקרון עדיין תופס – וצריך לבדוק שזו ההתנהגות של מסך הכניסה למערכת.
העקרון של מניעת זליגת מידע תופס גם לגבי התנהגויות אחרות במערכת: האם קריאה שגויה ל-API מייצרת הודעת שגיאה שמספקת לי מידע מיותר? האם נסיון גישה לדף שאינו קיים במערכת מספרת לי משהו שאוכל להשתמש בו למציאת פירצה (כגון: כתובת IP, הוורסיה של תוכנת השרת; מיקום מדוייק של קבצים על השרת, וכו')? כל אינפורמציה כזו מרחיבה את מה שפורץ פוטנציאלי יודע על האתר או התוכנה שלי, ואולי תספק לפורץ משהו שניתן לנצל לצורך חדירה לאתר. בקיצור, כלל טוב (גם לחיים): עדיף לדבר פחות.
גלישת חוצץ (Buffer Overflow)
"גלישת חוצץ" היא שגיאת תכנות המתבטאת בכך שתוכנית מחשב כותבת לאזור בזיכרון המחשב יותר מידע מאשר אותו אזור מסוגל להכיל. כתוצאה מכך "גולש" חלק מהמידע אל מחוץ לגבולות החוצץ, ומשנה נתונים שלא היו אמורים להשתנות. גלישת חוצץ עלולה , בין השאר, לאפשר הרצה של "קוד זדוני" הגורם לתוכנית לפעול באופן שלא תוכנן מראש"[1]. מבחינת אבטחת תוכנה, גם קריאה מאיזור שהוא מעבר למה שהוקצה לנתון מסוים, יכולה להוות פרצה הניתנת לניצול זדוני.
מה שמעניין הוא שהרבה פעמים הקוד – גם כשיש בו טעות שגורמת לגלישה – ירוץ בסדר גמור. יתכן שרק קומבינציה מאוד ספציפית של נתוני הרצה יגרמו לשגיאה שניתן יהיה לחוש בה באופן חיצוני. המשמעות של מצב זה היא שבמקרים רבים בדיקות פונקציונאליות לא יזהו שיש בעיה של גלישה.
אחת השיטות לבדוק אם יש בעיית גלישה, היא שימוש בטכניקה של fuzzing בשילוב עם וורסיה של הקוד שעבר מיכשור (אינסטרומנטציה) מיוחד.
(א) Fuzzing היא טכניקת בדיקות שבהם אלגוריתם מייצר מספר רב של קלטים שונים עבור התוכנה הנבדקת, שולח אותם לתוכנה, ובודק שהתוכנה לא נתקעה או התרסקה. אין כאן בדיקה פונקציונאלית האם התנהגות התוכנה מתאימה לקלט שנשלח – בעיקר כי הרוב המוחלט של הקלטים שהאלגוריתם מייצר הוא קשקוש מוחלט ואינו קלט הגיוני שאפשר לעשות איתו משהו. כל מה שאנו מנסים לעשות זה לייצר קלט שהתוכנה לא יודעת לזרוק או להתעלם ממנו. מצב כזה, שבו התוכנה "מתבלבלת" מהווה סיכון בחינת אבטחת תוכנה, כי יתכן שהתגובה של התוכנה תהיה לקרוס תוך זליגה של מידע, או להתקע לפני שהספיקה למחוק סודות (למשל, מפתחות הצפנה) מהזכרון.
(ב) מיכשור: שתי הפונקציות של הקצאת זכרון ושחרור זכרון (malloc; free) מוחלפות בקוד שבודק אם הכתיבות והקריאות נעשות רק מאיזורים בזכרון שמותר לתוכנה לגשת אליהן.. ברגע שהקוד מזהה גישה לאיזור אסור בזכרון (שזה מה שקורה בגלישת חוצץ), הוא מקריס את התוכנה. שיטה זו נקראית AddressSanitizer או בקיצור: Asan.
הרצה ארוכה של fuzzing (שעות או ימים!) מעלה את הסיכוי ליצירת שילוב של קלטים שיגרום לגלישת חוצץ. שילוב של הרצת fuzzing על קוד שעבר מיכשור של AddressSanitizer יגרום לקוד לקרוס ברגע שגלישה כזו קרתה, וכך נוכל לזהות שיש בקוד בעייה של גלישה.
פעילות בדיקה מסוג זה נופלת בין אחריות של קבוצת הבדיקות "הרגילה" לבין אחריות של מומחים באבטחת תוכנה. גישה פרקטית היא שפיתוח ה-fuzzer והמכשור של הקוד נעשה על ידי מומחי אבטחת התוכנה, והאחריות להרצת בדיקות אלה מידי פעם (נגיד, על כל וורסיה חדשה של התוכנה) מוטלת על קבוצת הבדיקות. אציין גם שהתמחות בטכניקת fuzzing מספקת מסלול קידום לאנשי בדיקות, שמאפשר להם לקחת אחריות גם על הצד המתקדם יותר של פיתוח ה-fuzzer.
מנסיון, גם הרצה של הבדיקות הרגילות (בלי fuzzer) על קוד שעבר מיכשור של AddressSanitizer מוצאת בעיות של גלישות זכרון. חלק מהגלישות לא מחייבות קלט מאוד ספציפי, ויקרו גם על הקלט שכבר קבעתם לבדיקה מסויימת. זה הופך את השיטה לכלי יעיל מאוד במציאת חולשות בקוד. יצירת הקוד המיוחד אינה קשה; העלות העיקרית של פעילות זו היא הצורך בהרצה של סבב בדיקות שאי אפשר להתחשב בתוצאותיו כיוון שהסבב מבוצע על קוד שעבר מיכשור והוא שונה מהקוד של המוצר הסופי (זו בדיוק אותה בעיה שיש כשמריצים בדיקות לצורך הערכת כיסוי הקוד – code coverage). שימו לב ששפת התכנות שבה מפתחים את המוצר משפיעה על הסיכון שהקוד סובל מגלישות. #C למשל כמעט ואינה רגישה לגלישה (צריך להתאמץ כדי לייצר את זה). C לעומת זאת, לא מוגנת כלל, אלא אם משתמשים בדגלים מסוימים בזמן הקומפילציה, כמו למשל fstack-protector-. מומלץ לחפש חומר בנושא ולראות מה הקומפיילר שלכם יכול לעשות בתחום זה (חפשו: “security hardening compilation flags”).
מתחת לפנס
לפעמים לא צריך להתאמץ יותר מידי כדי למצוא בעיות אבטחת מידע בקוד – כל מה שצריך זה מודעות ופתיחת עיניים. הנה כמה דוגמאות:
- הריצו את הקוד להחלפת סיסמה, תוך שאתם מקליטים (עם sniffer) את התעבורה בין המחשב שבו אתם משתמשים והשרת. וודאו שהסיסמה החדשה מוצפנת לפני שהיא נשלחת דרך הרשת אל השרת. לעיתים מה שעובר זה לא ממש הצפנה אלא digest (מעין "חתימה") של הסיסמא – שזה ממש לא הצפנה. אפשר להקליט את ה-digest, ושידור שלו ישירות לרשת (לא דרך דף ה-log in) במקום הכנסת הסיסמה, תעבוד יפה מאוד – השרת יחשוב שקיבל סיסמה נכונה.
- אם המוצר שלכם כותב קבצים זמניים או קבועים לדיסק, כדאי לפתוח אותם ולבדוק אם הם מכילים נתונים חסויים (נגיד, מספר כרטיס אשראי... מספר חשבון בנק...) ללא הצפנה.
- האם השרת שבניתם עובד עם cookies? איזה מידע נכתב בהם? האם יש שם מידע חסוי, שלא הוצפן ויכול להיקרא על ידי כל אדם עם גישה למחשב?
ישנן עוד הרבה בדיקות שחורגות מעבר לשאלה של "האם זה פועל". אפשר למצוא הרבה חומר ברשת שמלמד מה עוד כדאי לבדוק על מנת להבטיח שהתוכנה שלכם לא רגישה לפריצה או לשיתוף במידע חסוי.
לסיכום: שלושת הנושאים שהבאתי הם רק דוגמה לדברים שבעבר נחשבו כאחריות של מומחי אבטחת תוכנה (יש עוד!). המקום שהמחשב והחיבוריות לרשת ממלאים כיום בחיינו מצד אחד, והתגברות הסכנות לפריצה מצד שני, משמעותם שאבטחת מידע ותוכנה הם תחומים שכל בודק תוכנה צריך להבין. כמו כן סביר לדרוש שבדיקות בסיסיות של אבטחת תוכנה תהיינה כבר חלק מהבדיקות הרגילות של המוצר. לא לדאוג: עדיין תשאר עבודה למומחי הפריצות לחפש חורים במוצר – אבל זה יהיה משמעותית יותר יעיל אם הטעויות הפשוטות, שניתן לזהות בבדיקות ישירות ומתוך מודעות, מראש לא יכנסו למוצר שמומחים אלה יתקפו.
תודה לעמיתי יאיר נצר על ההערות לטור, ועל הפניות למקורות ברשת.
[1] https://he.wikipedia.org/wiki/גלישת_חוצץ
[2] https://github.com/google/sanitizers/wiki/AddressSanitizerAlgorithm
[3] למשל: https://owasp.org/www-project-top-ten/