wp2shell: איך שני באגים שקטים בליבת וורדפרס הפכו לפריצה מלאה בלי סיסמה אחת
חוקרי Searchlight Cyber חשפו השבוע פרצה בליבה עצמה של וורדפרס, לא בתוסף ולא בתבנית, שמאפשרת לתוקף אנונימי לגמרי להריץ קוד על שרת עם התקנה סטנדרטית ובלי אף תוסף מותקן. הצוות כינה אותה wp2shell, ווורדפרס הגיבה במהירות: גרסאות 6.8.6, 6.9.5 ו-7.0.2 יצאו ב-17 ביולי 2026 וסגרו את הפרצה, בלי שהחברה שגילתה אותה פרסמה בשלב הראשון פרטים על אופן הפעולה.
הפרצה היא שרשור של שני באגים נפרדים, כל אחד לא מסוכן כשלעצמו, שיחד סוגרים מעגל שלם מבקשת רשת ריקה ועד קובץ PHP זדוני שרץ על השרת. הפרטים המלאים על מנגנון הניצול התפרסמו רק אחרי שהתיקון יצא, כשחוקרים אחרים השוו בין הקוד הישן לחדש והבינו מה בדיוק תוקן.
מה זה בכלל ה-batch API של וורדפרס
ה-REST API של וורדפרס, ממשק תכנות שמאפשר לאפליקציות חיצוניות לקרוא ולכתוב תוכן באתר בלי לגעת בממשק הניהול, כולל מאז גרסה 5.6 משנת 2020 נתיב מיוחד בשם batch. הוא מאפשר לשלוח כמה פעולות בקריאת HTTP אחת במקום כמה קריאות נפרדות, למשל ליצור פוסט, לעדכן קטגוריה ולמחוק תגית באותה בקשה. זה חוסך עומס רשת לתוספים וללקוחות API שצריכים לבצע הרבה פעולות ברצף.
כדי לממש את זה, וורדפרס בונה בזמן הריצה שלוש רשימות מקבילות: אחת עם הבקשות עצמן, אחת עם תוצאות בדיקת התקינות של כל בקשה, ואחת עם המסלול וההרשאה שכל בקשה מתנתבת אליהם. שלוש הרשימות חייבות להישאר מסונכרנות מספר סידורי מול מספר סידורי, כי מנוע הדיספאץ' שולף מהן לפי אינדקס ולא לפי שם. הבאג שהחוקרים מצאו שובר בדיוק את הסנכרון הזה, וזה חדש לקוד שנוסף בגרסה 6.9 שיצאה בדצמבר 2025, לא לפיצ'ר הבאטצ' עצמו שקיים כבר שש שנים.
הבלבול במסלולים: איך אינדקס אחד שיצא מסנכרון פותח דלת
לפי הפירוק הטכני של Hadrian, הבאג יושב בפונקציה שמעבדת את הבאטצ', WP_REST_Server::serve_batch_request_v1. כשאחת הבקשות בתוך הבאטצ' פגומה מספיק כדי להיכשל כבר בשלב הפענוח הראשוני, למשל נתיב מוזר עם תווים לא תקינים, וורדפרס רושמת את הכישלון ברשימת התקינות אבל שוכחת להוסיף רשומה מקבילה לרשימת ההתאמות. מאותה נקודה ואילך כל בקשה שאחריה בבאטצ' מקבלת בטעות את המסלול ואת בדיקת ההרשאה שנועדו לבקשה שאחריה, לא לבקשה שהיא עצמה. זו לא טעות אימות רגילה, אלא כשל בזיהוי מי בכלל אמורים לבדוק.
תוקף שמכיר את הסדר הפנימי יכול לבנות באטצ' שבו בקשה תמימה, כזו שכל גולש אנונימי מורשה לבצע, תופסת בטעות את מקום הבדיקה של בקשה אחרת ברשימה, בעוד שבפועל מורץ קוד ההתנהגות של אותה בקשה אחרת לגמרי.
שרשור כפול: מהבאטצ' אל תוך WP_Query
בלבול אחד בלבד לא מספיק לביצוע קוד. הצעד הבא, כפי שמתואר בכתיבה הטכנית סביב קוד ההדגמה שפורסם ב-GitHub, מנצל את זה שנקודת הכניסה של הבאטצ' עצמה לא בודקת הרשאה ברמת הבקשה החיצונית. אפשר לקנן באטצ' בתוך באטצ': בקשה חיצונית שמכילה קריאה פנימית לנתיב הקטגוריות, ובתוכה עוד באטצ' פנימי. הבלבול החיצוני מאפשר לבאטצ' הפנימי לרוץ בלי אימות בכלל, והבלבול הפנימי גורם לבקשת הקטגוריות התמימה להתנתב בפועל אל הפונקציה שמטפלת בפוסטים.
התוצאה: פרמטר author_exclude שמגיע מבקשת הקטגוריות, נוחת בתוך WP_Query, מחלקת הליבה שבונה כמעט כל שאילתת מסד נתונים בוורדפרס. שם מחכה הבאג השני. הפרמטר המקביל בשכבה הפנימית, author__not_in, אמור להתקבל כמערך של מספרים, ורק אז וורדפרס מנקה אותו. כשהוא מגיע כמחרוזת בודדת במקום מערך, וורדפרס מדלגת על הניקוי ומדביקה את הקלט הגולמי ישירות לתוך סעיף NOT IN בשאילתת SQL.
מ-SQL Injection להרצת קוד על השרת
SQL Injection (הזרקה של פקודות מסד נתונים דרך שדה קלט שלא סונן כראוי) מספיקה כשלעצמה כדי לשלוף נתונים רגישים מהטבלאות, כולל גיבוב הסיסמה של מנהלי האתר. אבל כדי להגיע להרצת קוד מרחוק (RCE, מצב שבו תוקף מפעיל פקודות משלו על שרת של מישהו אחר), קוד ההדגמה שפורסם מדגים ניצול שנשען על הרשאת FILE במסד הנתונים, הרשאה שקיימת בברירת מחדל בהתקנות MySQL רבות אצל ספקי אחסון משותף. עם ההרשאה הזו אפשר להשתמש בפקודת INTO OUTFILE כדי לכתוב קובץ חדש לתוך תיקייה שנגישה מהאינטרנט ולבחור שהתוכן שלו יהיה קוד PHP. ברגע שהקובץ נכתב, בקשת HTTP רגילה אליו מריצה את הקוד וסוגרת את המעגל: מבקשה אנונימית ריקה ועד שורת פקודה על השרת.
התיקון של וורדפרס טיפל בשני הצדדים: השלמת הרישום ברשימת ההתאמות גם כשבקשה נכשלת מוקדם, כך שהאינדקסים נשארים מסונכרנים, ולצדו מנגנון הגנה שמונע כניסה חוזרת לתהליך הבאטצ' מתוך בקשה שכבר נמצאת בתוכו, מה שסוגר את דרך הקינון הכפול. הפרמטר author__not_in תוקן בנפרד כדי לדרוש תמיד מערך.
מי גילה את הפרצה, ומה קרה מאז
את בעיית הבלבול במסלולים גילה אדם קיוס (Adam Kues) מ-Searchlight Cyber דרך תוכנית הבאונטי של וורדפרס ב-HackerOne. את בעיית ה-SQL Injection דיווחו בנפרד שלושה חוקרים אחרים בשמות TF1T, dtro ו-haongo, כל אחד בלי לדעת על השני. השרשור של שני הדיווחים הנפרדים לניצול אחד מלא נעשה רק אחרי שוורדפרס תיקנה את שתי הבעיות באותו מחזור עדכונים.
Searchlight פרסמה בהתחלה רק את קווי המתאר של הפרצה, כתבה במפורש שאינה משחררת פרטים טכניים בשלב ההוא, והציעה כלי בדיקה עצמאי באתר wp2shell.com לבדוק אם אתר מסוים חשוף. אחרי שהתיקון יצא, חוקרים אחרים קראו את ההבדל בין הקוד הישן לחדש, שחזרו את המנגנון ופרסמו ניתוח טכני מלא, ובעקבותיו גם קוד הדגמה עובד. שני ה-CVE קיבלו ציוני חומרה שונים מאוד בין גופי הניקוד: NVD מציגה עבור CVE-2026-63030, באג הבלבול במסלולים, ציון 9.8 קריטי לפי WPScan אך רק 7.5 גבוה לפי CISA, ועבור CVE-2026-60137, ה-SQL Injection, המספרים כמעט מתהפכים: 5.9 בינוני לפי WPScan מול 9.1 קריטי לפי CISA. הפער נובע מכך שכל גוף ניקוד בוחר אם לזקוף לזכות הבאג הבודד את מלוא הפגיעה של השרשור המלא או רק את מה שהבאג עצמו מאפשר בבידוד.
ההקשר הרחב יותר
רוב פריצות האתרים המבוססים על וורדפרס מגיעות דרך תוספים או תבניות של צד שלישי, לא דרך הליבה עצמה. פרצת הרצת קוד בליבה, שפוגעת בהתקנה נקייה בלי אף תוסף, היא אירוע נדיר יחסית, ומזכירה שהמנוע שמריץ חלק ניכר מהאתרים באינטרנט עדיין חושף שטח קוד שגדל עם כל תכונה חדשה, כולל ה-REST API שהוצג במקור כדי להקל על פיתוח חיצוני. בדיקה מול רשימת החולשות המנוצלות בפועל (KEV) שמנהלת CISA מראה ששני ה-CVE עדיין לא נכנסו אליה נכון לכתיבת שורות אלה. אבל קוד הדגמה פומבי כבר קיים, וזה בדרך כלל מקצר משמעותית את הזמן עד שמתחילות סריקות ניצול בשטח.
מה זה אומר בפועל: כל אתר שמריץ וורדפרס גרסה 6.8, 6.9 או 7.0 צריך לעדכן מיד לגרסה 6.8.6, 6.9.5 או 7.0.2 בהתאמה, גם אם אין בו תוספים כלל, כי הבאג יושב בליבה. עד לעדכון, חסימת הנתיב wp-json/batch/v1 ו-rest_route=/batch/v1 בחומת אש או בתוסף שמגביל גישה ל-REST API מספקת הגנה זמנית סבירה.
תגובות