בקשה אחת גומרת ל-PHP את הזיכרון, וההרצה מגיעה מהניקוי של PHP עצמו: שתי החולשות בציון 9.2 שתוקנו בשחרור חירום
ב-20 באוקטובר בערב עלתה ל-Hacker News הודעה קצרה שלפיה שתי חולשות הרצת קוד מרחוק ב-PHP, בציון 10.0, ייחשפו ביום חמישי. הכותב עצמו הוסיף שאין לו שום ראיה מעבר ל"סמכו עליי". תוך פחות משעתיים פרסם צוות האבטחה של PHP הודעה מקדימה משלו וקבע שחרור חירום למחרת ב-14:00 UTC, ולפי דיווח של The Hacker News חברת המחקר watchTowr אישרה באותו ערב שהדלף אמיתי ושהחולשות תחת embargo. ב-21 באוקטובר יצאו הגרסאות 8.5.12, 8.4.27, 8.3.36 ו-8.2.35, ואיתן התפרסמו שני ה-advisory-ים הרשמיים.
מה שכתוב בהם מעניין יותר מהציון. CVE-2026-109618 ו-CVE-2026-109619, שתיהן בציון 9.2 ב-CVSS v4.0, לא יושבות בפונקציה אחת שמפרסרת קלט לא נכון. הן יושבות במה ש-PHP עושה כשבקשה נכשלת: איך הוא נוטש בקשה שנגמר לה הזיכרון, ומה הוא משאיר אחריו ל-worker הבא. במקרה שאומת מקצה לקצה התוקף לא מתחבר, לא מנצל שום חולשה באפליקציה שרצה על השרת, ובכלל לא מגיע לקוד שלה. נעבור על שני המנגנונים, על הסיבה ש-Apache נפגע קשה יותר מ-PHP-FPM, ועל השרתים שלא יקבלו תיקון בכלל.
מה קורה כשנגמר ל-PHP הזיכרון באמצע בקשה
לכל בקשה ב-PHP יש תקרת זיכרון (ההגדרה memory_limit). כשבקשה חורגת ממנה, PHP לא מסיים אותה בצורה מסודרת אלא קופץ החוצה מהנקודה שבה הוא עומד ישר אל הקוד שמטפל בשגיאה, בעזרת קריאת longjmp של שפת C. הקפיצה הזאת מדלגת על כל מה שהיה אמור לרוץ בדרך.
כאן נכנסת הבעיה. חלק מהנתיבים בקוד הופכים זיכרון של הבקשה לנגיש לפני שסיימו לאתחל אותו, ואם ההקצאה נכשלת בדיוק באותו רגע, הקפיצה מדלגת על רישום החשבונאות שהיה אמור לסמן את היציאה המוקדמת. הזיכרון נשאר במצב לא תקין, ולפי ה-advisory זה קורה גם כשההקצאה שנכשלה בכלל לא קשורה לאותו זיכרון. אחר כך מגיעה פרוצדורת סגירת הבקשה, קוראת את המצב הזה ומסתעפת לפיו. זה כל התרחיש.
לגרום לזה אי אפשר סתם בגוף בקשה ענק, כי הבדיקה של post_max_size תחסום אותו. ה-advisory מונה כמה דרכים שנשארות בתוך המגבלות המוצהרות: בקשת multipart בלי אורך תוכן שמיש עוברת את הבדיקה הראשונית וממשיכה להאכיל את השרת בנתונים מפוענחים; מספר ה-header-ים בתוך חלק בודד של בקשת multipart אינו מוגבל, והייצוג שלהם בזיכרון כבד בהרבה מהגודל שלהם על החוט, כך שבקשה שעומדת ב-post_max_size מרוקנת את memory_limit; ושדות קובץ ריקים לא נספרים ב-max_file_uploads בכלל, אבל כן יוצרים מערכי metadata, ושמות שדה מקוננים מכפילים את העבודה.
למה דווקא Apache, ולמה PHP-FPM יוצא מזה בזול
ההבדל כולו בשאלה מה ה-SAPI, הרכיב שמחבר את PHP לשרת הווב, עושה כשאתחול הבקשה נכשל. מודול PHP של Apache (mod_php) קורא לפרוצדורת סגירת הבקשה גם כשהאתחול נפל, והפירסור האוטומטי של גוף הבקשה קורה כבר בשלב האתחול. אפשר לרוקן את תקרת הזיכרון לפני שרצה שורת קוד אחת של האפליקציה, והסגירה מעבדת את המצב הלא תקין. זה הנתיב שאומת: הרצת קוד מרחוק בלי אימות, בהרשאות של חשבון ה-worker של שרת הווב, מעל HTTP, מול mod_php בהגדרות קלט וזיכרון של ברירת מחדל ובתצורת prefork רגילה.
PHP-FPM מתנהג אחרת. כשהאתחול נכשל הוא מסיים את תהליך הבן, וזה חוסם את המסלול הזה. ה-advisory מדגיש שחריגת זיכרון אחרי אתחול תקין עדיין יכולה להגיע לאותו עיבוד של מצב לא תקין, אבל שלא הודגם exploit שלם ל-FPM. על FrankenPHP נכתב שניתוח קוד מצביע על סיכוי גבוה שהוא פגיע באופן דומה ל-mod_php, ועל LSAPI, שבנוי במודל של FPM, שסביר שקיימים exploit-ים תלויי אפליקציה אך הם לא אומתו. עוד worker-ים ועוד תעבורה מעלים את מספר הניסיונות שהתוקף צריך. הם לא מבטלים את התנאי.
החולשה השנייה: מה שנשאר מהבקשה שלפניכם
החולשה השנייה עובדת על זיכרון שכבר שוחרר. ה-worker של PHP חי יותר מבקשה אחת ומחזיק מצב גלובלי, שגיאות, session, אובייקטים ומצב של הרחבות, שחייב להתאפס לפני שהזיכרון של הבקשה מתפנה. אם האיפוס נקטע באמצע, למשל בגלל שגיאה פטאלית או חריגה מהזיכרון, נשאר מצביע אל נתונים של הבקשה שנגמרה. בקשה חדשה שתקרא אותו לפני שהאתחול הרגיל יחליף אותו קוראת בפועל מזיכרון משוחרר, ולכשל הזה קוראים use-after-free. עם פריסת heap מתוכננת הוא הופך להרצת קוד.
הניצול דורש שני צדדים שנופלים על אותו worker: בקשה שמשאירה אחריה את המצב, ובקשה אחרת שצורכת אותו. ה-advisory מאמת שני צרכנים כאלה. הראשון הוא אזהרות שנזרקות בזמן החלת הגדרות INI לפני אתחול הבקשה, ושינוי של error_reporting, שימוש באופרטור @ או כיבוי התצוגה וה-logging אינם מונעים אותן. השני הוא טיפול ב-session upload progress לפני האתחול. בצד המפיקים, המקרה החזק הוא ה-backtrace של שגיאה פטאלית ב-PHP 8.5 כשההגדרה fatal_error_backtraces דלוקה, וזה הבסיס ל-exploit המאומת מול mod_php. מסלול ה-session אומת גם מול mod_php וגם מול FPM.
התיקון, ומה שנראה כמו מיתון ואינו
התיקון מוציא את ה-longjmp ממסלול חריגת הזיכרון. במקומו PHP עובר ל-OOM רך ומושהה שמתבצע בנקודה בטוחה של המכונה הווירטואלית, וכשהשימוש בזיכרון מגיע לרף שני וגבוה יותר מופעל OOM קשה שקורא ל-_exit. הסגירה כבר לא רואה מצב לא תקין, ותקרות הזיכרון של PHP ממשיכות לעבוד כרגיל. שני ה-advisory-ים מזכים את אותם ארבעה מדווחים בכינויי ה-GitHub שלהם, lcfr-hax, sagitzadik-wiz, tomash-areion ו-amyb-oai, ושניים מהכינויים נושאים את השמות של Wiz ושל OpenAI. את התיקון כתבו ארבעה מפתחי ליבה: arnaud-lb, iluuu1994, bukka ו-ndossche.
שתי הגדרות שהסתובבו כמיתון לפני השחרור דורשות זהירות. memory_limit=-1 אמנם מוריד את הסיכון, אבל ה-advisory מזהיר שאם mmap או הקצאה אחרת תיכשל, ה-longjmp עדיין ירוץ, ושאסור להשתמש בהגבלות RLIMIT_AS ו-RLIMIT_DATA בכלל, כי הן דווקא מפעילות את אותו מסלול פגיע. ההגדרה session.upload_progress.enabled=0, שהוצעה כמיתון מיד אחרי הדלף, מטופלת ב-advisory כצעד חלקי בלבד, וחברת GMO Flatt Security שהציעה אותה הבהירה לפי דיווח של The Hacker News שאינה תיקון שלם ושצפויות עקיפות.
על ניצול בפועל אין עדיין דיווח. ה-FAQ של watchTowr רשם ב-21 באוקטובר "לא דווח על ניצול", ושתי החולשות אינן מופיעות ב-קטלוג ה-KEV של CISA במהדורה מ-20 באוקטובר. במקביל פרסמה Cloudflare שחרור חירום של ה-ruleset המנוהל שלה עם חוקים חדשים שחוסמים תבניות בקשה חריגות. (הסיכום בראש העמוד מדבר על ארבעה חוקים, והטבלה שמתחתיו מפרטת שבעה.)
883,717 שרתים שלא יקבלו תיקון בכלל
PHP מתקנת רק את ארבעת הענפים הנתמכים, 8.2 עד 8.5, ו-ה-ChangeLog הרשמי מראה את שני התיקונים תחת Core באותה שורה. ענפים 8.1, 8.0, 7.x ו-5.x הגיעו לסוף החיים ולא יקבלו תיקון מ-PHP עצמה.
ספירה של Mallory ב-Shodan מ-20 באוקטובר מראה מה זה אומר בשטח: 1,495,986 שרתים חשופים לאינטרנט מדווחים גרסת PHP ב-header ה-X-Powered-By שלהם, ומתוכם 883,717, שהם 59.1%, מדווחים 8.1 או מוקדם יותר. הפירוק הוא 368,570 על ענף 5.x, 329,358 על 7.x ו-185,789 על 8.0 ו-8.1. עוד כ-740 אלף שרתים מדווחים PHP בלי גרסה בכלל, ולכן Mallory מעריכה שהמספר האמיתי גבוה יותר. היא גם מסייגת: אלה ספירות banner ולא שרתים שנבדק עליהם שהם חשופים לחולשה, הפצות כמו Debian ו-Ubuntu מכניסות תיקונים אחורה בלי לשנות את מספר הגרסה, והרבה אתרים חולקים כתובת IP אחת, כך שמספר האתרים הנוגעים בעניין גדול בהרבה ממספר השרתים.
מה זה אומר בפועל
עדכנו ל-8.5.12, 8.4.27, 8.3.36 או 8.2.35, ותנו קדימות לשרתים שחשופים לאינטרנט ורצים על mod_php, כי זה הנתיב היחיד שהודגם מקצה לקצה בלי תלות באפליקציה. אם אתם על Debian או Ubuntu אל תשוו מספרי גרסה, כי התיקון נכנס שם אחורה בלי לשנות אותם, ובדקו את ה-changelog של החבילה לפי מספר ה-CVE. מי שנשאר על 8.1 או מוקדם יותר לא יקבל תיקון מ-PHP, ושם המסקנה היא שדרוג ענף או הוצאת המערכת מהאינטרנט, לא חוק ב-WAF. ואל תשכחו את ההתקנות שאף אחד לא זוכר: PHP שמגיע בתוך appliance, בתוך תוכנה לווינדוס, או בלוח ניהול כמו cPanel ו-Plesk.
תגובות