תמונה אחת בפורמט AVIF מריצה קוד על שרת Next.js, והבאג עצמו יושב שתי ספריות מתחת
ב-20 באוגוסט פרסם צוות Next.js הודעה קצרה ויבשה: בעוד שישה ימים, ב-26 בחודש, תצא גרסת אבטחה שתסגור חולשה אחת בדרגת חומרה קריטית. ההודעה המוקדמת הזאת היא חלק מנוהל שהצוות הכריז עליו ביולי, שנועד לתת לארגונים כמה ימים להיערך לעדכון לפני שהפרטים יוצאים החוצה. חמישה ימים אחר כך התאריך זז אחורה. בהודעת העדכון כתבו Josh Story, Karim Rahal ו-Sebastian Silbermann שהגרסה "צפויה כעת לטפל בשתי חולשות בדרגת חומרה קריטית ולא באחת", ושהבעיה החדשה שזוהתה היא זו שהקדימה את הלוח.
החולשה השנייה הזאת לא נמצאת בקוד של Next.js בכלל. היא יושבת בספריית C שהמסגרת לא כותבת ולא מתחזקת, שתי שכבות מתחת לקוד שמפתחים באמת נוגעים בו, ומספיק להעביר דרכה תמונה אחת בנויה נכון כדי להריץ קוד על השרת. בלי להתחבר, ובלי שאף אחד ילחץ על משהו. (נוהל ההודעה המוקדמת נועד לתת לארגונים זמן להיערך, וכאן הוא הסתיים בכך שהצוות הקדים את התאריך שהוא עצמו פרסם.) בהמשך נראה מה בדיוק עושה התמונה הזאת, למה התיקון של Next.js היה לכבות פורמט שלם במקום לתקן אותו, ומה עושה החולשה השנייה שנסגרה באותה גרסה, זו שנוגעת רק למי שמריץ את השרת על Windows.
למה מסגרת אינטרנט בכלל נוגעת בתמונות
ל-Next.js יש רכיב שמטפל בתמונות אוטומטית. במקום להגיש את הקובץ המקורי כמו שהוא, השרת מקבל בקשה עם כתובת התמונה ועם פרמטרים של גודל ואיכות, מביא את התמונה, מקטין אותה ומקודד אותה מחדש לפורמט שמתאים לדפדפן שביקש. זה נשמע כמו פיצ'ר של ביצועים ולא כמו משטח תקיפה, אבל שימו לב מה קורה כאן בפועל: השרת שלכם מפרסר קובץ בינארי שהוא לא יצר, לפעמים כזה שהגיע מכתובת חיצונית.
את העבודה הזאת Next.js לא עושה בעצמה. היא נשענת על sharp, ספריית עיבוד התמונות הנפוצה בעולם ה-Node, ו-sharp נשענת בתורה על libheif כדי לקרוא קבצים בפורמטים שמבוססים על מבנה HEIF. אחד מהם הוא AVIF, פורמט תמונה מודרני שדוחס טוב יותר מ-JPEG ולכן מוצע כברירת מחדל כמעט בכל צינור אופטימיזציה של תמונות. שרשרת התלות הזאת היא כל הסיפור. מפתח שכותב אפליקציה ב-JavaScript מריץ, בלי לשים לב, קוד C שמפרסר מבנה קבצים מורכב מאוד.
שני ערוצי שקיפות באותה תמונה
כדי להבין את הבאג צריך להכיר רכיב אחד בתוך קובץ תמונה. חוץ מערוצי הצבע, קובץ יכול לשאת ערוץ נוסף שאומר לגבי כל פיקסל כמה הוא שקוף. לערוץ הזה קוראים ערוץ אלפא, ובזיכרון הוא נראה כמו כל ערוץ אחר: הקצאה אחת ברוחב שנקבע לפי עומק הסיביות שלו. ערוץ של 8 סיביות תופס בית אחד לכל דגימה, ערוץ של 10 סיביות תופס שניים.
הבעיה מתחילה כשלאותה תמונה נרשמים שני ערוצי אלפא בעומקים שונים. לפי ההודעה של libheif, הפונקציה transfer_channel_from_image_as לא דוחה ערוץ יעד שכבר קיים, כך שאפשר להגיע למצב הזה מלכתחילה, והפונקציה שמאתרת את הערוץ בזיכרון מחזירה תמיד את הרשומה הראשונה שמתאימה. כלומר הרשומה השנייה, זו עם המאפיינים השונים, פשוט לא נראית לשאר הקוד.
וכאן זה נשבר. כשהפונקציה scale_nearest_neighbor מקצה מקום לערוץ האלפא היא לוקחת את עומק הסיביות מהרשומה הראשונה, אבל אחר כך עוברת על כל הרשומות. כשהראשונה היא 8 סיביות והשנייה 10, ההקצאה נעשית בגודל של בית אחד לדגימה, ואילו הכתיבה עוברת למסלול שמתייחס לאותו חוצץ כאילו כל דגימה בו רחבה שני בתים. כל דגימה נכתבת בכפול מהמקום שהוקצה לה, וזאת גלישה מעבר לגבול ההקצאה בערימה.
מי שמייצר את השכפול בתוך הקובץ הוא מנגנון שמאפשר לפריט בקובץ HEIF להצביע על פריט אחר ולרשת ממנו את התוכן. הפריט המפנה גורם לפענוח מלא של הפריט המקורי, שחוזר עם ערוץ האלפא שלו כבר מחובר, ואז נוסף לו גם ערוץ האלפא של עצמו. התוצאה היא בדיוק שתי הרשומות שצריך: הראשונה ב-8 סיביות, השנייה ב-10.
ההודעה נותנת גם את הסדר גודל. בגיאומטריית פלט של 128 על 128 פיקסלים ההקצאה לערוץ האלפא היא 16,384 בתים, והכתיבה גולשת מעבר לה בכ-16,384 בתים נוספים, כלומר דריסה בגודל ההקצאה עצמה. "הצלחנו להשיג הרצת קוד באמצעות זה על כמה אפליקציות", כתבו החוקרים, והוסיפו שלא נדרשות אפשרויות API מיוחדות או דפוס קריאה חריג, אלא רק קובץ HEIC, HEIF או AVIF שבנוי כך. ההודעה מייחסת את הממצא ל-rootxharsh כמגלה ול-KarimPwnz כמתאם, ו-Vercel מזכה בשמו את צוות Hacktron.
התיקון היה לכבות את הפורמט
עד כאן הבאג עצמו. איך הוא נסגר מעניין לא פחות.
בצד של libheif יש תיקון אמיתי: הבעיה קיימת בגרסאות 1.23.1 ומטה ותוקנה ב-1.23.2, וההודעה מדרגת אותה 9.8 בסולם CVSS 3.1. אבל תיקון בספריית C צריך לעבור מסלול ארוך עד שהוא מגיע לאפליקציה שרצה בפרודקשן: לעדכן את libheif, לבנות מולה מחדש את sharp, ואז לעדכן את התלות באפליקציה עצמה. Next.js לא חיכתה למסלול הזה. הגרסאות המתוקנות, 15.5.24 בענף התחזוקה ו-16.3.3 בענף הפעיל, מכבות את האופטימיזציה של AVIF, ובלשון ההודעה הרשמית, "עד שתיקון במעלה הזרם יתפשט".
זה פתרון גס, והוא גם הפתרון הנכון. אם השרת לא מפרסר AVIF, הבאג ב-libheif לא נוגע לו. המחיר הוא שאתרים שהגישו עד עכשיו תמונות AVIF יקבלו פורמט אחר, כלומר קבצים כבדים יותר וקצת פחות ביצועים, וזה מחיר סביר לתקופת ביניים. ההודעה של Next.js על החולשה הזאת מדרגת אותה 9.5 בסולם CVSS 4.0 וקובעת שהיא נוגעת לכל הגרסאות מ-10.0.0 ועד 15.5.23, וכן לכל ענף 16 עד 16.3.2 כולל.
החולשה השנייה: רק Windows, ובלי מעקף
זו שהייתה בתוכנית מלכתחילה היא CVE-2026-75604, בציון CVSS 9.0. היא נוגעת לאפליקציות שמשתמשות גם ב-Pages Router וגם ב-App Router בלי Cache Components, וגם אז רק כשהשרת רץ על מערכת קבצים של Windows. Linux ו-macOS לא מושפעים, וזה הבדל נדיר מספיק כדי להיות שווה תשומת לב.
הסיווג שההודעה נותנת הוא CWE-22, מעבר בין תיקיות: הקוד מרכיב נתיב לקובץ מתוך קלט שמגיע מבחוץ ולא מנטרל אותו כמו שצריך, והאופן שבו Windows מטפל בנתיבים הוא מה שמעביר את זה מקריאת קובץ להרצת קוד. הווקטור שפורסם כולל AC:H, כלומר תנאי הניצול אינם טריוויאליים, ובכל זאת התוצאה היא הרצת קוד בלי אימות מוקדם. הגרסאות המושפעות הן 13.4 ומעלה עד 15.5.24, ו-16.0 עד 16.3.3. את הממצא מייחסת ההודעה ל-evolutionstorm ול-B0RI.
מה שכן, הנקודה כאן היא לא הציון אלא המשפט האחרון בהודעה: "אין מעקף ידוע לאפליקציות מושפעות שמתארחות על Windows. אתם צריכים לעדכן מיד אם השרת שלכם מתארח על Windows." אין דגל להוריד, אין הגדרה לשנות, אין נתיב לחסום ב-WAF. רק עדכון.
אז מי באמת חשוף כאן?
התשובה תלויה בעיקר בשאלה אחת: מי מריץ את השרת. Vercel פרסמה שאפליקציות שמתארחות אצלה מוגנות ולא דורשות שום פעולה מצד הלקוח, משתי סיבות נפרדות. היא כיבתה את אופטימיזציית ה-AVIF בשירות התמונות המנוהל שלה ברגע שזיהתה את הבעיה, וסביבת הריצה שלה ל-Next.js עובדת על Linux, כך שחולשת ה-Windows לא נוגעת לה מלכתחילה. מי שמריץ Next.js בעצמו, על שרת שלו או בקונטיינר שלו, לא מכוסה על ידי אף אחת מהשתיים.
לגבי חולשת ה-AVIF יש עוד סייג ששווה לשים לב אליו. הווקטור שפורסם כולל AT:P, שמשמעותו בסולם CVSS 4.0 היא שהניצול תלוי בתנאי כלשהו שאינו בשליטת התוקף. ההודעה לא מפרטת מהו התנאי, ובפועל השאלה המעשית היא האם האפליקציה בכלל מעבירה דרך רכיב האופטימיזציה תמונה שמקורה בתוקף. אתר שמגיש רק תמונות שהוא עצמו מארח נמצא במצב אחר מאתר שמאפשר למשתמשים להעלות תמונות, או שמשכתב תמונות מדומיינים חיצוניים.
ואחרון: אף אחת מההודעות לא מדווחת על ניצול בפועל, ואף אחת מהחולשות לא מופיעה ברשימת החולשות המנוצלות בפועל (KEV) של CISA, בגרסה שלה שפורסמה ב-27 באוגוסט. זה לא אומר הרבה על השבועות הקרובים. מסגרת בשימוש רחב מאוד, שתי הודעות פומביות שמתארות את הבאג לפרטיו, וגרסאות מתוקנות שמראות בדיוק מה השתנה בקוד: זה בדיוק הרגע שבו כלי ניצול מתחילים להופיע, לא לפניו.
מה זה אומר בפועל
אם אתם מריצים Next.js בעצמכם, בדקו איזו גרסה יש לכם ועדכנו ל-15.5.24 או ל-16.3.3 לפי הענף שאתם עליו. מי שהשרת שלו רץ על Windows צריך לעשות את זה קודם, כי שם אין שום צעד ביניים שקונה זמן. ואם אתם מפעילים בעצמכם שירות שמפרסר קבצי מדיה, בכל שפה ובכל מסגרת, שווה לבדוק גם איזו גרסה של libheif יושבת אצלכם בשרשרת: הבאג נמצא שם ולא ב-Next.js, וכל מי שקורא דרכה קבצי HEIC, HEIF או AVIF חשוף בדיוק לאותו דבר.
תגובות