ביקורת על 138 מאגרי GitHub שלי
טוקן שהיה פעיל תשעה חודשים, סוד שסריקת נתיבים פספסה וסריקת תוכן תפסה, ולמה החלפת האישור חשובה יותר מניקוי ההיסטוריה שהחזיקה אותו.
- אבטחה
- כלים
יש לי 138 מאגרים. רובם קטנים, רבים מהם בני שנים, וכמה מהם נכתבו בערב אחד כדי לבדוק רעיון אחד ומעולם לא נגעו בהם שוב. עשיתי ביקורת על כולם לאיתור סודות שנשמרו בקוד, בציפייה למצוא כלום, בעיקר כדי להצדיק את הכלים.
מצאתי דברים.
הטוקן בן תשעת החודשים
הגרוע ביותר היה טוקן API בקובץ .env, בתוך מאגר פרטי, עדיין תקף. הוא היה שם תשעה חודשים.
פרטי, ולכן איש מבחוץ לא קרא אותו. אבל ״פרטי״ הוא תכונה של המאגר היום, לא של כל מקום שבו ההיסטוריה הזאת אי פעם הייתה. בין הקומיט לבין הביקורת ההיסטוריה הזאת הייתה על מחשב נייד שכבר אינו שלי, במטמון של CI, ובתוך לפחות שני שכפולים שעשיתי ושכחתי.
הטוקן היה של שירות עם מסלול חינמי ובלי חיוב מקושר, וזו בדיוק הסיבה ששרד. שום דבר לא נשבר. שום התראה לא נדלקה. אין לולאת משוב שמספרת לך שאישור דלף כשהאישור לא עושה שום דבר יקר.
זה שסריקת נתיבים פספסה
המעבר הראשון שלי היה המתבקש: לרשום כל קובץ שאי פעם היה בהיסטוריה, ולהתאים את הנתיבים לרשימת דברים שלא אמורים להיות שם.
git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(rest)' \
| awk '$1 == "blob" { print $3 }' \
| sort -u \
| grep -E '(^|/)(\.env|\.env\..*|id_rsa|.*\.pem|.*\.p12|credentials\.json)#x27;זה מצא כמה קובצי .env ונתן לי מספר מספק לדווח עליו. זה גם פספס לגמרי את הממצא המעניין ביותר.
הפספוס היה קובץ בשם .env.production. בתבנית שלי היה \.env\..*, ולכן הוא היה אמור להתאים — אבל הקובץ נשמר בתוך תיקיית config/ במאגר אחד ובשורש במאגר אחר, והגרסה הקודמת והצרה יותר של התבנית שלי הייתה מעוגנת לשורש. כבר ״תיקנתי״ את התבנית עד שהרצתי את סריקת התוכן, אבל סריקת התוכן היא זו שבאמת אמרה לי שהקובץ משנה, מפני שסריקת נתיבים מספרת לך רק שלקובץ יש שם חשוד.
זה הלקח האמיתי: סריקת נתיבים אומרת לך איפה לחפש, סריקת תוכן אומרת לך מה יש לך. קובץ בשם .env.example מתאים לכל היוריסטיקת נתיב ולא מכיל כלום. קובץ בשם config/settings.local.js לא מתאים לכלום ומכיל מפתח חי.
אז המעבר השני קורא בלובים ומתאים לצורות של ערכים:
git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(rest)' \
| awk '$1 == "blob" { print $2, $3 }' \
| while read -r sha path; do
git cat-file blob "$sha" 2>/dev/null \
| grep -nHE '(AKIA[0-9A-Z]{16}|gh[pousr]_[A-Za-z0-9]{36,}|sk-[A-Za-z0-9]{32,}|-----BEGIN [A-Z ]*PRIVATE KEY-----)' \
| sed "s|^(standard input)|$path@$sha|"
doneזה איטי וזה מייצר התראות שווא — בלוב base64 בקובץ בדיקה יפעיל את התבניות שמבוססות אנטרופיה, וכל sk- בדוגמה ב-README מסומן. זו הפשרה הנכונה. התראת שווא עולה שלושים שניות של ביטול; החמצה היא אישור חי.
קודם להחליף. ניקוי ההיסטוריה הוא החלק שלא משנה.
האינסטינקט שלי כשמצאתי את הטוקן היה לשכתב את ההיסטוריה ולגרום לזה מעולם לא לקרות. git filter-repo, דחיפה כפויה, נגמר.
האינסטינקט הזה הפוך, והליכה אחריו מבזבזת את הזמן היחיד שמשנה.
האישור נפרץ ברגע שהוא נשמר בקומיט. לא כשהמאגר הופך לפומבי, לא כשמישהו מוצא אותו. אי אפשר לדעת מי שכפל, פיצל, שמר במטמון או שיקף את ההיסטוריה הזאת. GitHub שומר אובייקטים בלתי נגישים זמינים לפי SHA למשך זמן מה אחרי דחיפה כפויה. שכתוב היסטוריה משנה מה נוח למצוא. הוא לא משנה מה יצא.
ולכן הסדר הוא:
- להחליף את האישור. עכשיו, לפני כל דבר אחר. זה הצעד היחיד שמשנה את מצב האבטחה.
- לבדוק את היומנים, אם השירות שומר אותם, לאיתור שימוש שאינכם מזהים.
- ואז להחליט לגבי ההיסטוריה — וזו כעת החלטת סדר וניקיון בלי תאריך יעד, לא תגובה לאירוע.
שכתבתי את ההיסטוריה בסוף, מאותה סיבה שאני מנקה קוד מת: מי שיקרא את זה אחריי לא אמור למצוא מחרוזת בצורת סוד ולתהות אם היא חיה. אבל זו הייתה שעה של סידור, לא תיקון.
מניעה, מדורגת לפי כמה שזה באמת עזר
הוק pre-commit שקורא את התוכן שנשמר. הבקרה היחידה שעוצרת את הקומיט במקום לדווח עליו אחר כך. זולה ותפסה אותי פעמיים מאז.
.gitignore כתבנית, לא לכל פרויקט. כל אחת מהדליפות האלה הגיעה ממאגר שבו לא טרחתי, מפני שזזתי מהר וזה היה ״רק בדיקה״. gitignore גלובלי שמכסה .env* (חוץ מ-.env.example) מסיר את ההחלטה.
סריקה מתוזמנת. רצה שבועית, פותחת issue כשהיא מוצאת משהו. ערך נמוך יחסית להוק — עד שהיא נדלקת הסוד כבר בקומיט — אבל זה הדבר שתופס את המאגרים שאתם לא עובדים בהם, שהם בדיוק אלה שמחביאים טוקן בן תשעה חודשים.
אישורים קצרי-מועד בכל מקום שהשירות מציע אותם. הבקרה האפקטיבית ביותר בהפרש גדול, מפני שהיא הופכת את כל מחלקת הטעויות לכזו שמרפאת את עצמה. טוקן שפג תוך שעה הוא לא ממצא גדול.
המספר
מתוך 138 מאגרים: 4 סודות אמיתיים, 3 מהם במאגרים שלא פתחתי מעל שנה, 1 עדיין תקף.
אחד מתוך 138 הוא שיעור נמוך והוא גם לגמרי לא רלוונטי. ההתפלגות לא משנה. אישור חי אחד הוא הממצא, והוא היה במאגר שהכי פחות סביר שהייתי בודק ידנית.