הפעם ש-PHP2Shell אכל לי את תיבת המייל
מייל אחד ב-13:06, פאץ' ב-17:00 שמכסה רק PHP 8.2 ומעלה, ו-500 אתרים של לקוחות שפזורים על עשרים ומשהו שרתים, 10 ספקי ענן ויותר פאנלי ניהול ממה שבא לי לספור.

שבוע לפני כל זה סוף סוף בניתי את הדבר שדחיתי שנה שלמה: סקריפט קטן שקורא פידים של אבטחה, זורק כל מה שלא נוגע בשרת או בתוסף שאני אחראי עליו, ושולח לי במייל את מה שנשאר. הייתי גאה בו כמו שגאים במדף שתלית לבד. הוא ישר, מעץ אמיתי, יחזיק פיל תינוק, ואף אחד חוץ ממני לא ישים לב אליו.
בשבוע הראשון שלו הוא הפיק שני מיילים על תוסף וורדפרס שלדעתי שלושה אנשים עוד מריצים. הרגשתי מאוד אחראי ושכחתי ממנו.
ב-21 באוקטובר, ב-13:06, הוא שלח את השלישי.

Patchstack דיווחו על שני CVE, ובשורת החבילה לא היה כתוב תוסף או תבנית. היה כתוב “PHP core”. השפה כולה. מישהו כבר הספיק לקרוא לזה PHP2Shell, וזה אף פעם לא סימן טוב.
באגים שמקבלים מיתוג לפני פאץ’ נוטים להרוס לי את השבוע.
קודם קפה
לא האמנתי. מערכת ההתראות שלי הייתה בת שבעה ימים. הרבה יותר סביר שפישלתי באיזה פילטר והיא צורחת על כלום. אז עשיתי מה שכל מהנדס סביר עושה עם מייל מפחיד שהוא לא סומך עליו: השארתי אותו פתוח על המסך והלכתי להכין קפה.
נעדרתי אולי שש דקות. חזרתי עם ספל מלא לאחד עשר מיילים חדשים ושתי שיחות שלא נענו.
המערכת הייתה בסדר. הבאג היה אמיתי. הרצת קוד מרחוק בחלק של PHP שמטפל בבקשות ווב, וזה בעצם הכול: PHP-FPM, mod_php, php-cgi, LiteSpeed, FrankenPHP. אם זה מריץ PHP ופונה לאינטרנט, זה בפנים.
המיילים המשיכו להגיע, בטון הזה של אנשים שכבר העבירו משהו לבוס ועכשיו צריכים שמישהו אחר יהיה אחראי. אנליסטים, סיסאדמינים, מנהלי פרויקטים ומנכ”לים, כולם מדביקים איזו התראה או כתבה שבעצם אומרת “זה נוגע לנו??”. רואה החשבון של מישהו שאל, אני נשבע, אם זה הדבר מהחדשות.
זה הצחיק אותי. שנים לאף אחד לא היה אכפת מגרסאות PHP, או מחולשות באופן כללי. אפשר להתקשר, לכתוב, לשלוח לינק לאקספלויט עובד, ועדיין לא תקבל שעה אחת לפצ’פץ’, כי למי אכפת? ואז זה מגיע לחדשות, ופתאום זה בראש סדר העדיפויות של כולם.
ומתחת לכל זה שאלה אחת: מתי זה מתוקן?
התיקון לא ממהר
עוד לא. הפרטים דלפו לפני שצוות PHP היה מוכן, והפאץ’ נקבע ל-17:00 שעון מקומי. ארבע שעות שבהן כולם יודעים ואף אחד לא יכול לעשות הרבה.
ארבע שעות, על הנייר. תזכרו את החלק הזה.
חלק מהממשלות אפילו לא ניסו. ספקית ה-IT הממשלתית של פינלנד, Valtori, פשוט כיבתה את אתרי הממשלה עד שהתיקון יגיע.

קודם העברתי את ההתראה דרך חלון צ’אט וביקשתי שתי שורות: מה פגיע, מה תוקן. אחר כך נכנסתי ל-php.net ומצאתי את הטקסט המקורי. ה-AI לא היה הולך לתקן שום שרת באותו יום, ולא התכוונתי לתת לו prompt של root באמצע תקרית חיה. הוא קיבל במקום זה את השכבה האמצעית המשעממת.
במקור הייתה רשימת גרסאות. 8.2, 8.3, 8.4, 8.5.
זו הייתה כל הרשימה. 8.1, 8.0, 7.4 וכל מה שלפניהן כבר מחוץ לתמיכה, אז כנראה שדפקו אותי. צוות האבטחה כן העלה ל-GitHub פאצ’ים מינימליים (backports) ל-7.4, 8.0 ו-8.1, וזה נהדר אם אתם מקמפלים PHP בעצמכם. אני לא מכיר אף לקוח שעושה את זה. רובם לא יודעים להגיד איזה PHP הם מריצים, וכמו שהתברר באותו אחר צהריים, גם אני לא.
למה זה פגע בי יותר ממה שהיה צריך
יש לי עבודה יומית כמפתח, ואני גם יועץ לחברות אחרות. חלקן חברים עם אתר וורדפרס וחלום. חלקן אני לא יכול להזכיר כאן.
אלה עסקים שונים לגמרי, אבל שני דברים נכונים כמעט לגבי כולם. הם עובדים בשעות עבודה, אז הפאץ’ ינחת בדיוק כשכל מי שיודע להתקין אותו בדרך הביתה. והרבה מהם לא יכולים פשוט להקפיץ PHP: יש תוסף מ-2019 שמת על 8 ותוסף שתלוי בתוסף הזה, או פאנל אחסון שמציע מה שהוא מציע, או “הבחור שהקים את השרת”, שעזב ב-2021 ולקח איתו את סיסמת ה-root (ברצינות).
אז זה לא היה שרת אחד. אלה היו השרתים של כולם, בבת אחת, ועוד בלי שום דבר להתקין.
המלאי שחשבתי שיש לי
פתחתי את Obsidian על מסך אחד ואת פאנל השרתים החצי אפוי שלי על השני, כי ככה אני נכנס לפאניקה בצורה מסודרת. אחרי עשר דקות הבנתי שאני בעצם לא יודע מה רץ איפה. הפאנל שלי מכיר בערך 15 שרתים. אנחנו מנהלים איפשהו בין 25 ל-30. אף אחד לא יודע את המספר המדויק.
ככה זה ברוב החברות שאני עובד איתן. אין DevOps, אין QA, ובדרך כלל אין סניור. פרטי השרתים יושבים באיזו הערה ב-monday.com, החשבון על שרת שאף אחד כבר לא צריך ממשיך להגיע כל חודש, וכל העבודה מתנהלת משריפה לשריפה. ידעתי בערך מה יש לנו. ו”בערך” אי אפשר לפצ’פץ’.
כשסכמתי הכול:
- בערך 500 אתרים של לקוחות
- 25 עד 30 שרתים, תלוי את מי שואלים
- 10 ספקי ענן
- יותר פאנלי ניהול משרתים
השורה האחרונה היא לא טעות. בחלק מהשרתים יש פאנל אחד, בחלק שניים, ובאחד מישהו הסיר פאנל עד החצי, ויתר, ואז הגדיר nginx ידנית מעל מה שנשאר. בשאר זה cPanel/WHM, Plesk, DirectAdmin, כמה שרתי CloudPanel, Elementor Hosting (כן, זה קיים), Cloudways, ואחסון משותף שבו יש לי רק התחברות FTP ותקווה.
האחסונים המנוהלים הם אתגר בפני עצמו. שם לא אתה מפצ’פץ’ את PHP, הם עושים את זה. אבל האם יעשו, ומתי? הם צריכים את האישור שלך קודם? יעבירו אותך ל-8.2 בעצמם, או ישאירו אותך על 7.4 כי זה מה שבחרת ב-2020? את כל השאלות האלה נזכרים לשאול את הספק בדיוק כשהבעיה כבר כאן.
כל אחד מהם מתקין PHP בדרך שלו, מתעדכן לפי לוח השנה שלו ומחביא קונפיגים בתיקייה האהובה עליו. הייתי צריך תשובה אחת לכל אתר: איזה PHP באמת מגיש את הדבר הזה?
php -v הוא לא התשובה
אם אתם לוקחים דבר אחד מהפוסט הזה, קחו את זה.
$ php -vPHP 8.3.12 (cli)מעולה, 8.3, נקבל את הפאץ’, הולכים הביתה. רק שזה ה-PHP של שורת הפקודה, זה שהטרמינל שלכם מצא ראשון. האתר אף פעם לא ביקש את דעתו.
ברוב השרתים עם פאנל מותקנות כמה גרסאות PHP זו לצד זו, וכל אתר בוחר את שלו מתפריט נפתח שמישהו לחץ עליו לפני שנים. הטרמינל יכול להגיד 8.3 בזמן שחצי מהאתרים על אותו שרת רצים בשקט על 7.4. לשאול שרת איזה PHP יש לו זה כמו לשאול בניין דירות באיזה צבע הקירות שלו. כל דירה צבעה בעצמה.
אז הפסקתי לשאול את השרתים ושאלתי את האתרים. הם שמחים לספר, אם יודעים איפה להסתכל:
- וורדפרס: כלים ← בריאות האתר ← מידע ← שרת. גרסת ה-PHP נמצאת שם, בלי שום shell. רוב 500 האתרים שלי הם וורדפרס, אז זה עשה את רוב העבודה.
- cPanel: MultiPHP Manager, טבלה אחת, כל הדומיינים, הגרסה של כל אחד. (יש גם אפשרות דרך שורת הפקודה, שכבר כתבתי עליה. באותו אחר צהריים זה החזיר את עצמו.)
- Plesk: לכל דומיין, תחת הגדרות PHP. ב-DirectAdmin וב-CloudPanel יש אותו דבר, בשמות אחרים ובמקומות אחרים, כי ברור.
- אחסונים מנוהלים כמו Cloudways ו-Elementor Hosting: בדשבורד שלהם, בהנחה שמוצאים את פרטי ההתחברות.
לכל השאר, האחסונים המשותפים עם התחברות FTP ותקווה, העליתי קובץ PHP של שורה אחת שמדפיס את הגרסה שלו, פתחתי אותו בדפדפן ומחקתי מיד. מכוער, מהיר, גמרנו.
את הסקריפטים החד-פעמיים האלה כתב ה-AI: אחד ל-CLI של cPanel, גרסה בטעם Plesk, וקובץ ה-PHP הפשוט למארחים שיש בהם רק FTP. הייתי כותב את שלושתם בסוף. “בסוף” היה הדבר היחיד שלא היה לי.
עד 15:30 הייתה לי רשימה אמיתית, והיא התחלקה לשלוש ערימות.
שלוש ערימות
| ערימה | PHP | התוכנית ל-21 |
|---|---|---|
| בני המזל | 8.2+ | לעדכן ב-17:00, לאתחל את FPM, לבדוק שהאתר עולה |
| האולי | 8.0–8.1 | לבדוק על 8.2 ב-staging, להקפיץ כשהגרסה המתוקנת מגיעה |
| הערימה המכוערת | 7.4 ומטה | אין תיקון הלילה. להפוך אותם למטרה פחות נוחה |
לפני כל זה הפרדתי בערך רבע מהרשימה: אתרים על PHP בסביבות 5, ובניות וורדפרס מותאמות ישנות. שום דבר לא היה מעדכן אותם ביום אחד, אז הם עברו ישר לתוכנית של הערימה המכוערת.
בני המזל (8.2+). התוכנית שלהם הייתה משעממת במובן הכי טוב. החלק הקשה היה להיות מוכן ב-17:00, כי חצי מהשרתים האלה שייכים לחברות שיהיו ריקות עד אז. ביליתי את אחר הצהריים באיסוף אישורים לגעת בשרתים שבדרך כלל לא נותנים לי להתקרב אליהם. חלק מהבעלים אישרו בזמן. השאר יראו את זה בבוקר, והאתרים שלהם קיבלו ללילה את הטיפול של הערימה המכוערת.
האולי (8.0 ו-8.1). קרוב מספיק ל-8.2 שהקפצת גרסה אולי פשוט תעבוד. אולי. איפה שלאתר היו טסטים הרצתי אותם, וזה קרה בערך באותה תדירות שאתם מנחשים. איפה שלא, העברתי עותק staging ל-8.2 והקלקתי עליו כמו לקוח רותח. אזהרות deprecation בכל מקום, אבל רובם שרדו ונכנסו לתור לעבור ברגע שהגרסה המתוקנת תגיע לפאנל. אלה שלא שרדו עברו לערימה המכוערת.
הערימה המכוערת (7.4 ומטה). אי אפשר להזיז בלי שכתוב, על פאנלים שלא יבנו 7.4, של בעלים שלא יאשרו את השכתוב הזה בלי ישיבה של שעה ומייל מהמנכ”ל. ועוד הרבע שהפרדתי, אתרי ה-8.x שנפלו ב-staging, ואלה שעוד חיכו לאישור. שום דבר כאן לא עמד להיתקן לפני 17:00, אז המשימה הייתה להפוך אותם למטרה פחות נוחה:
- Cloudflare מלפנים איפה שעוד לא היה, עם החוקים המנוהלים ל-CVE הזה מופעלים ברגע שהופיעו. לא מתקן כלום. קונה זמן.
- לסגור כל מה שאין לו מה לחפש בציבור. קבצי
phpinfo.phpישנים, פאנלי ניהול נשכחים, תיקיות/test/. מצאתי יותר ממה שנוח לי להודות. - לעקוב אחרי הלוגים. רוב מה שפגע בשרתים שלי באותו ערב היה גישושים, לא ניצול אמיתי, וזה התאים למה ש-Patchstack ראו. אבל “רוב” זה לא “הכול”, ואחרים דיווחו על מתקפות אמיתיות.
- להגיד לבעלים את האמת, בכתב. אתם על גרסה שאף אחד לא מפצ’פץ’. זה מה שעשיתי הלילה, זה כמה עולה תיקון אמיתי, זה התאריך שעד אליו אני צריך תשובה.
הסעיף האחרון לא טכני, ואין חשוב ממנו. פתרון עוקף בלי “זה זמני” בכתב הופך בשקט להגדרה הקבועה. ראיתי את זה קורה. עשיתי את זה בעצמי.
כתבתי גרסה כנה אחת של המייל הזה ונתתי ל-AI להתאים אותה לכל שאר הלקוחות, לכל אחד “הנה מה שעשיתי הלילה על השרת שלך” משלו. באותו שלב הייתי שרוף מכדי לנסח טוב בעצמי את הפסקה של “זה זמני, הנה כמה עולה תיקון אמיתי”, והיא הייתה הדבר הכי חשוב ששלחתי כל היום.
17:00
על הנייר, זה היה קו הסיום. הפאץ’ נחת בזמן: 8.2.35, 8.3.36, 8.4.27, 8.5.12.
ואז לזמן מה לא קרה כלום, כי פאץ’ ב-php.net זה לא פאץ’ על השרת שלכם. ההפצות והפאנלים בונים ומשחררים חבילות משלהם, כל אחד לפי השעון שלו. ודווקא האחסונים המנוהלים שדאגתי לגביהם קודם התבררו כמהירים:
| מאיפה מגיע ה-PHP | גרסה מתוקנת |
|---|---|
| אחסונים מנוהלים: uPress, Cloudways, Elementor Hosting | תוך שעות. לא היה לי מה לעשות |
| פאנלים על שרתים שאנחנו משלמים שינהלו: cPanel/WHM, DirectAdmin, CloudPanel | למחרת לכל המוקדם. חלק עוד משחררים |
פאנלים הופכים את ה-95% הקלים לקלים, וביום האחד שזה חשוב אתה מחכה ללוח הזמנים של מישהו אחר. זו עסקה סבירה. רק תדעו שעשיתם אותה.
בעבודה היומית, הגרסאות המתוקנות לרוב השרתים שלנו הובטחו לבוקר המוקדם של למחרת. אלה שעוד חיכו קיבלו חוק ב-Cloudflare ושורה ביומן של מחר, ובסביבות 18:00 כבר לא נשאר שם מה לעשות חוץ מלחכות.
הטלפון השתתק. כולם הלכו הביתה.
לשרתים של הלקוחות הפרטיים שלי לא הייתה הבטחה לבוקר, אז המשכתי איתם. בסביבות 01:00 שאלתי את ה-AI אם 8.1 קרובה מספיק ל-8.2 כדי להסתכן בהקפצה עיוורת. שם הבנתי שהגיע הזמן ללכת לישון.
הגרסאות שהובטחו לבוקר לא הגיעו כולן. כשאני כותב את זה ב-25 באוקטובר, ארבעה ימים אחרי הפאץ’, חלק מהשרתים האלה עדיין מחכים לשלהם. עד כאן ארבע שעות.
הקפה עוד עמד על השולחן, מלא וקר כמו אבן.
ה-PoC
באותו זמן, הוכחת היתכנות (PoC) עובדת הייתה שמועה. @lcfr_hax היה מהראשונים שהראו אחת בסרטון, והתמונה בראש הפוסט היא פריים ממנו: וורדפרס מקומי על PHP פגיע, האקספלויט מפיל pwned.txt לא מזיק, והדפדפן לידו מראה שהקובץ נחת. תחליפו את pwned.txt ב-web shell וקיבלתם את הדבר האמיתי.
אני לא מדביק כאן payload. אקספלויט עובד של PHP2Shell בפוסט בבלוג עוזר בדיוק לסוג אחד של קוראים, וזה לא אתם. הצורה שמדברים עליה מספיקה: use-after-free עמוק באופן שבו PHP מנקה אחרי בקשה. המפרש משחרר נתח זיכרון ואז ניגש אליו שוב, ובקשה בנויה במיוחד מחליטה מה יהיה שם באותו רגע. בקשה אחת לכל נקודת קצה של PHP, בלי התחברות, והתוקף מקבל הרצת קוד.
לא ראיתי דיווח על אתר שבאמת נפרץ ככה. ראיתי הרבה דיווחים על PoC סביר.
מה שקבע איך יעבור עליי השבוע היה הפער בין “PoC קיים” ל”הלקוחות שלי מטולאים”, והאם אספיק לסגור אותו לפני שמישהו שמחזיק אחד יגיע לשם קודם. אותם כלים שאיפשרו לי למיין 500 אתרים באחר צהריים עובדים באותה מידה גם בשבילם.
למה שום דבר לא ישתנה
רוב הסוכנויות הן כמו האור בניסוי הפיזיקה ההוא: הן מתנהגות יפה רק כשיש צופה. ביום רביעי כל העולם הסתכל, אז פתאום לכולם היה אכפת מגרסאות PHP. בשבוע הבא לאף אחד לא יהיה אכפת. המיילים של “זה נוגע לנו??” ייפסקו, ואתרי ה-7.4 יחזרו להיות שורה בישיבת תקציב שכל פעם נדחית לרבעון הבא.
הן לא מתנהגות יפה כי תיקון אמיתי עולה כסף וזמן. Wordfence, Kinsta ו-Patchstack (זה שהתריע לי) כל אחד מוכר פרוסה אמיתית ושימושית. חומת אש לא תעדכן לכם את ה-PHP. אירוח מנוהל לא ישכתב את התוסף מ-2019 שמת על 8. פיד פגיעויות יגיד לכם שאתם חשופים, לא איך לגרור אתר מ-WordPress 4.9 ל-7.1.3 בלי להפיל את הקופה בדרך.
המחיר, לאתר וורדפרס אחד:
- סריקה אחת (שרת, משתמשים, בסיס נתונים, תוספים, קוד מותאם): ארבע-חמש שעות ישרות, בערך $400, פעם אחת.
- להישאר מוגנים: להריץ את הסריקה הזאת כל חודש, קרוב יותר ל-$1,000 בחודש.
- עם תחזוקה, שינויים שהצטברו ואירוח מעל: שניים, אולי שלושה אלף בחודש.
על אתר עמוס שמוציא שינויים כל שבוע, החשבון הזה עובד. על אתר ממוצע זה קשה למכור. והחברות שיכולות לשלם על צוות אבטחה אמיתי קונות לעצמן בעיה אחרת: מהירות. אישור, חלון מתוזמן, code review, מעבר QA. עד ש”לעדכן PHP בפרודקשן” יעבור את כל זה, היתרון של ארבע שעות שהיה לתוקפים הוא כבר היסטוריה עתיקה. או עניים מכדי להיות מוגנים, או איטיים מכדי להיות יעילים. כמעט כולם הם אחד מהשניים.
מוכנים לפעם הבאה
שנים אני אומר ללקוחות “כדאי שנעדכן PHP” בטון שאומרים בו “כדאי להשתמש בחוט דנטלי”. מאז יום רביעי אני מחזיק במקום זה את הרשימה הזאת:
- מערכת התראות. פידים של אבטחה, מסוננים לשרתים ולתוספים שאתם אחראים עליהם. שלי החזירה את עצמה תוך שבוע: שמעתי על זה לפני הלקוחות שלי.
- מלאי ששואל את האתרים, לא את
php -v. אתר, שרת, פאנל, גרסת PHP, מי משלם את החשבון. אצלי זה עכשיו cron ששואל כל לילה כל שרת שאני מגיע אליו איזה PHP מריץ כל אתר, וכותב הכול לקובץ אחד. בפעם הבאה השאלה הראשונה תיקח עשר שניות, לא שעתיים. - תוכנית, שנכתבת עכשיו. מה בודקים ראשון, באיזה סדר, ומה זה “גמרנו”. תוכנית שנכתבת ב-13:10 באותו יום היא לא תוכנית. הצ’קליסט האבטחה לוורדפרס שלי הוא התחלה.
- גיבויים שכבר שחזרתם מהם לפחות פעם אחת.
- אישורים בכתב לפני שאתם צריכים אותם. “כן, קדימה, תעשה מה שצריך” שווה הרבה יותר ביום שלישי שקט מאשר ב-16:55.
- נוהל הסלמה. מי מתקשר למי, ולמי מותר להגיד “תכבו את האתר” כשאף אחד אחר לא עונה.
ואם אתם עדיין על 7.4: ה-backport נמצא ב-GitHub, ומישהו שאתם מכירים יודע לקמפל אותו. תבקשו יפה. תזמינו אותו לארוחת ערב.
תגובות
התגובות נשמרות ב-GitHub Discussions (דרך giscus), אז כדי להצטרף לשיחה צריך חשבון GitHub.