אימות PAM עובד ידנית אך נכשל כשירות OpenVPN
התחברות PAM שעובדת ממעטפת root ונכשלת תחת systemd מצביעה על העוזר, לא על הסיסמה. הפרידו הרשאות, גישה לקבצים ו-SELinux.
אימות דרך PAM יכול להצליח כשמריצים את הלקוח ידנית ולהיכשל עם AUTH_FAILED ברגע ש-systemd מפעיל את אותו פרופיל. הסיסמה אינה משתנה בין ההרצות האלה; מה שמשתנה הוא מערכת האילוצים סביב התהליך ששואל את PAM. PAM עונה דרך עוזר שהיכולת שלו לשנות את אישורי הזהות של עצמו היא בדיוק מה שיחידה מוקשחת מסירה. מצאו את העוזר הזה ואת הגבול שהוא צריך לפני שנוגעים ברשימת יכולות.
unix_chkpwd הוא הקובץ הבינארי שעונה לשאלת PAM
עם אימות סיסמה מקומי, pam_unix ממעט לקרוא את קובץ ה-hash בעצמו. הוא מריץ את unix_chkpwd, שהפצות מתקינות כ-setuid root או setgid shadow כדי שאפשר לבדוק סיסמה אחת בשם תהליך שאין לו מה לחפש בקריאת /etc/shadow. הסירו את היכולת להשיג את הזהות הזו והעוזר לא יוכל להשלים את הבדיקה, ולכן המודול מחזיר כשל אימות פשוט.
אז הכשל נראה כמו בעיית אישורי זהות בכל יומן שהלקוח רואה. זו המלכודת: הלקוח מדווח מה PAM אמרה לו, ו-PAM מדווחת רק שהמחסנית אמרה לא. הרצת העוזר ידנית כ-root גם היא לא מוכיחה דבר, כי היא מתרגלת מסלול הרשאות שהשירות אף פעם לא משתמש בו. קראו את היחידה ש-systemd טען בפועל, ואחר כך את יומן המערכת עבור הניסיון שביצע. שתי התצוגות האלה מפרידות בין בעיית ארגז חול לבעיית סיסמה שגויה.
קראו את היחידה לפני שאתם קוראים את יומן הסיסמאות
יחידה ארוזה יכולה לשאת drop-in שלא כתבתם, והתצורה האפקטיבית היא התוצאה הממוזגת. הדפיסו את היחידה כפי ש-systemd רואה אותה, יחד עם הגדרות ארגז החול שקובעות אם העוזר יכול להעלות את עצמו. בדיקה לקריאה בלבד מספיקה בשלב הזה.
systemctl cat [email protected]
systemctl show [email protected] -p User -p Group -p CapabilityBoundingSet -p AmbientCapabilities -p NoNewPrivileges
journalctl -u [email protected] -b
הפקודה הראשונה מראה מאין מגיעים היחידה וה-drop-in שלה, וזה חשוב כי עקיפה שהוספה לפני חודשים שורדת יותר מהזיכרון של הסיבה שהיא קיימת. השנייה מדפיסה את ערכי ארגז החול שהשניים הבאים תלויים בהם.
השוו את מה שאתם רואים למארח עובד מאותה הפצה וגרסה. הבדל ב-User, ב-CapabilityBoundingSet או ב-NoNewPrivileges הוא רמז מוחשי; הבדל בהערות פרוזה אינו. רשמו את הערכים לפני שמשנים משהו, כי השוואה מאוחרת מול מטרה נעה לא מוכיחה דבר. אם השירות רץ בחשבון לא מורשה עם קבוצת bounding ריקה, לעוזר כמעט אין במה לעבוד.
טרנספורמציית אישורים, גישה לקבצים ומדיניות הם שלושה גבולות
התחילו במה שקורה במהלך exec. קובץ בינארי setuid או קובץ הנושא יכולות קובץ מפעיל טרנספורמציית אישורים, ולכן הזהות שנוצרת אינה המשך של הקורא. קבוצת ה-bounding מגבילה מה קובץ כזה רשאי לרכוש, ו-NoNewPrivileges חוסם לגמרי רכישת setuid, setgid ויכולות קובץ. כל אחד מהשלושה יכול לעצור את העוזר בעוד שכל פונקציית שירות אחרת נשארת שלמה.
לאחר מכן, הפרידו עקיפה שיקול-דעת רחבה מגישת קריאה צרה. CAP_DAC_OVERRIDE מדלג על בדיקות שיקול-דעת כמעט בכל מקום, בעוד CAP_DAC_READ_SEARCH מכסה קריאה ומעבר בספריות עם סמכויות נוספות משלו. אלה כלים שונים עם רדיוס השפעה שונה, ואף אחד מהם אינו איות של תנו ל-PAM לקרוא את קובץ ה-shadow.
לבסוף, שאלו אם המדיניות המחייבת בכלל מתירה את הפעולה. יכולת Linux אינה עוקפת את SELinux, והחזקה באחת לא מוכיחה דבר על כלל type enforcement. התייחסו לסירוב גישה לקובץ ולסירוב יכולת כראיות שונות, כי הן משמות תיקונים שונים ולעיתים קרובות בעלים שונים. קראו כל שדה ברשומה ולא רק את המילה denied. איזה קובץ הרצה ביצע את הפעולה, באיזה דומיין הוא רץ, איזה סוג יעד הוא נגע בו, איזו מחלקת אובייקט הייתה מעורבת ואיזו הרשאה המדיניות סירבה — כל אחד מצמצם את התשובה בדרך אחרת.
חלצו את הסירוב מיומן הביקורת
במארח אוכף, עקבות הביקורת הם המקום היחיד שמתעד סירוב. שאלו את האירועים האחרונים וקראו אותם מול חותמת הזמן של ההתחברות הכושלת, לא מול השעה שבה התחלתם לחקור.
ausearch -m AVC,USER_AVC -ts recent
תוצאה ריקה אינה הוכחה לחפות. היא אומרת שאו שהביקורת אינה אוספת, או שהחלון שלכם פספס את הניסיון, ושני האפשרויות חייבות להישלל לפני שתשללו את המדיניות.
מדריך פתרון הבעיות של SELinux של Red Hat מציב בדיקות תווית ותצורה לפני יצירת כללי allow, וזה גם הסדר הנכון לבעיה הזו. קובץ עוזר מתויג שגוי ופער במדיניות מייצרים סירובים דומים אך דורשים תיקונים שונים. יצירת כלל allow לטעות תיוג מסתירה את התקלה ומשאירה לעדכון החבילה הבא להחזיר אותה. אשרו את התווית על העוזר המותקן לפני כתיבת כל מדיניות.
הוכיחו את השינוי בהתחברות אחת שהתקבלה ואחת שנדחתה
שנו רק את הגבול שהדגמתם, ואז בדקו את שתי התוצאות. חשבון שצריך להאומת חייב להצליח, וחשבון עם סיסמה שגויה חייב עדיין להיכשל. שינוי שגורם לכל ניסיון להצליח הסיר את האימות במקום לתקן אותו.
בצעו ניסיון חיבור אמיתי וקראו את היומן מיד לאחר מכן. השורה שמוכיחה את התיקון היא רשומת pam_unix(openvpn:auth) עבור הניסיון הזה: היא צריכה להראות אימות מוצלח, ואף רשומת AVC לא צריכה לשאת את אותה שנייה.
journalctl -u [email protected] -n 30 --no-pager
ss -tnp | grep openvpn
מנהרה עובדת זקוקה גם לחיבור מחדש, כי שינויי ארגז חול שוברים את החיבור השני לעתים קרובות יותר מהראשון. בדקו את ה-socket שהוקם לאחר החיבור מחדש ואשרו שאותה יחידה עדיין משרתת אותו.