שיטפון סקרייפר בתוכנית החינמית של Cloudflare: מה עצר אותו, ומה השארנו כבוי
תפעול
בוקר אחד, אחד מאתרי הדפים שלנו הפסיק להיפתח. Cloudflare הציבה למבקרים שגיאת 524, השרת המקור לקח בין שתיים לשש דקות לכל דף, ואף אחד לא עדכן אותנו במשך כ-10 שעות. הגורם היה סקרייפר שעבר דרך פרוקסים ביתיים בקצב של כ-225 בקשות לשנייה.
צורת השיטפון
- אלפי כתובות, אחת עד שלוש בקשות מכל אחת. חיבורי אינטרנט ביתיים בארה"ב, ספרד, צרפת, ברזיל, בריטניה ומדינות נוספות, סוג התעבורה שרשתות פרוקסים ביתיים משכירות.
- מזהי דפדפן Chrome במכשירי Mac, עם מספרי גרסאות מ-103 עד 151.
- כמעט אף פעם לא עוגייה או Referer. מתוך 2,697 בקשות שלקחנו לדגימה בשרת, 4 נשאו עוגייה ו-4 נשאו Referer.
- שני סוגי לקוחות. כמחצית שלחו ללא כותרות
Sec-Fetch-*וללא רמזי לקוח: סקריפט עם מחרוזת מזהה דפדפן שהועתקה. החצי השני שלח כותרות דפדפן מלאות: דפדפן ללא ממשק משתמש (headless).
כל דף עובד עבור כל בקשה, ולכן כל עובד (worker) תור מאות עיבודים והגיע למגבלת ה-CPU שלו.
למה מגבלות לפי כתובת לא יכלו לעזור
מגבלת קצב סופרת בקשות לפי כתובת. עם אחת עד שלוש בקשות מכל אחת מאלפי כתובות, אף כתובת בודדת לא מתקרבת למגבלה. חסימת מדינות הייתה חוסמת את המבקרים שלנו. מה שהסקרייפר לא יכל לזייף בזול היה להתנהג כמו דפדפן שכבר ביקר באתר.
בשולי Cloudflare: שלוש כללים חינמיים
התוכנית החינמית מאפשרת חמישה כללי WAF מותאמים אישית לכל אזור (zone). השתמשנו בשלושה, כולם עם אתגר מנוהל (managed challenge) במקום חסימה, כך שאדם שנתפס בטעות יכול להמשיך בלחיצה:
- דפדפן מזויף: בקשת GET שהמזהה שלה מכיל
Chrome/ואין בה כותרתSec-Fetch-Mode. כל גרסת Chrome מ-2019 ומעלה שולחת אותה. - ענני סקרייפרים: בקשה לדף מרשתות Alibaba, Tencent ו-Huawei Cloud, מהן הגיעה רוב התעבורה שאתגרנו.
- מצב חירום, כבוי כברירת מחדל: בקשת GET ללא עוגייה כלל וללא Referer. אדם שכבר ביקר באתר, או שמגיע דרך קישור, עובר ללא הפרעה; סקרייפר שלא שומר עוגיות נתקל באתגר בכל בקשה. אנו מפעילים אותו רק בזמן שיטפון, כי הוא גם אתגר אדם שמקליד את הכתובת בדפדפן חדש לגמרי.
שלושתם מעבירים את הבוטים המאומתים של Cloudflare, סוכני משתמש שמצהירים על עצמם כרובוטים, הכתובות שלנו, נתיבי ה-API וה-webhook, וקבצים שאינם דפים. יומיים לאחר מכן השיטפון נמשך עדיין: בשעה אחת כלל הדפדפנים המזויפים ביצעו אתגר ליותר מ-10,000 בקשות. 10,000 האירועים שהחזירה Cloudflare לשאילתה הגיעו מ-9,627 כתובות, שאף אחת מהן לא עברה את 4.
במקור: שער באפליקציה
החצי חסר-הראש (headless) כולל כותרות דפדפן מלאות, ולכן כללי הקצה לבדם לא תופסים אותו בזמן שכלל החירום כבוי. באפליקציה עצמה שמנו תוכנת ביניים קטנה לפני מצייר הדף:
- סוכן משתמש של Chrome ללא
Sec-Fetch-Modeוללא רמזי לקוח מקבל 403 לפני שמתבצעת עבודה כלשהי; - זר, ללא עוגיות שלנו וללא Referer, מקבל שירות רק כאשר לעובד יש קיבולת פנויה, בכמה דפים בשנייה. מעבר לכך, דף 503 בגודל 400 בייט מגדיר עוגיה וטוען מחדש לאחר שתי שניות. אדם מקבל את הדף בניסיון השני; סקרייפר שלא שומר עוגיות לעולם לא מקבל אותו.
בדקה הראשונה השער דחה כ-6,700 דפדפנים מזויפים ודחה כ-6,700 זרים, תוך מתן שירות לזרים בכ-9 דפים בשנייה. בקשות איטיות ירדו לאפס, ה-CPU של העובדים ירד מהמגבלה שלהם ל-12 עד 15 אחוז, והזיכרון שלהם ירד מ-4 עד 5 גיגה-בייט לכל אחד ל-0.65 גיגה-בייט.
מה השארנו כבוי
- מצב Bot Fight. הוא מבצע אתגר לתעבורה אוטומטית ולא ניתן לדילוג עליו עם כלל מותאם אישית בתוכנית החינמית, והוא תפס את ה-webhooks של ספק התשלומים שלנו.
- מצב Under Attack לכל האזור. הוא מעביר כל מבקר, לקוח API ו-webhook דרך דף הביניים. כלל החירום עושה את אותו תפקיד רק עבור בקשות דפים של זרים.
- כלל הגבלת הקצב של התוכנית החינמית. הוא סופר לפי כתובת, דבר שהשיטפון הזה לעולם לא מפעיל.
- חסימת זוחלי AI. אנחנו רוצים להימצא על ידי עוזרי חיפוש מבוססי AI, והזוחלים האלה לא היו הבעיה.
המסקנה
שיטפון דרך פרוקסים ביתיים קל לזהות לפי מה שחסר בו: כותרות fetch של דפדפן, עוגיות ו-Referers. בצעו אתגר לכך בקצה, שמרו על זרים בתוך מה שהעובדים שלכם יכולים לרנדר, וודאו שמשהו מחוץ לשרת שם לב כאשר דף מפסיק להיפתח. Night Watch אורז את שלושתם: השמירה, הכללים עם מתג אוטומטי עבור כלל החירום, והשער עבור אפליקציות אינטרנט ב-Python.