אנטי-תבנית: שימוש בכמתים חמדנים במדיניות RegularExpressionProtection

אתם צופים במסמכי התיעוד של Apigee ושל Apigee Hybrid.
לעיון במסמכי התיעוד של Apigee Edge.

מדיניות RegularExpressionProtection מגדירה ביטויים רגולריים שמוערכים בזמן הריצה בפרמטרים של קלט או במשתני זרימה. בדרך כלל משתמשים במדיניות הזו כדי להגן מפני איומי תוכן כמו הזרקת SQL או JavaScript, או כדי לבדוק פרמטרים של בקשות שגויות כמו כתובות אימייל או כתובות URL.

אפשר להגדיר את הביטויים הרגולריים עבור נתיבי בקשות, פרמטרים של שאילתות, פרמטרים של טפסים, כותרות, רכיבי XML (במטען ייעודי (payload) של XML שמוגדר באמצעות XPath), מאפיינים של אובייקט JSON (במטען ייעודי (payload) של JSON שמוגדר באמצעות JSONPath).

המדיניות הבאה מסוג RegularExpressionProtection מגנה על ה-backend מפני מתקפות הזרקת SQL:

<!-- /antipatterns/examples/greedy-1.xml -->
<RegularExpressionProtection async="false" continueOnError="false" enabled="true"
  name="RegexProtection">
    <DisplayName>RegexProtection</DisplayName>
    <Properties/>
    <Source>request</Source>
    <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
    <QueryParam name="query">
      <Pattern>[\s]*(?i)((delete)|(exec)|(drop\s*table)|
        (insert)|(shutdown)|(update)|(\bor\b))</Pattern>
    </QueryParam>
</RegularExpressionProtection>

תבנית אנטי

הכמתים שמוגדרים כברירת מחדל (*,‏ + ו-?) הם חמדנים: הם מתחילים להתאים לרצף הארוך ביותר האפשרי. אם לא נמצאה התאמה, המערכת חוזרת אחורה בהדרגה כדי לנסות להתאים את התבנית. אם המחרוזת שמתקבלת בהתאמה לתבנית קצרה מאוד, שימוש בכמתים חמדנים עלול לקחת יותר זמן מהנדרש. זה נכון במיוחד אם מטען הנתונים גדול (עשרות או מאות קילו-בייט).

בדוגמה הבאה נעשה שימוש בכמה מופעים של .*, שהם אופרטורים חמדנים:

<Pattern>.*Exception in thread.*</Pattern>

בדוגמה הזו, מדיניות RegularExpressionProtection מנסה קודם להתאים את הרצף הארוך ביותר האפשרי – המחרוזת כולה. אם לא נמצאת התאמה, המדיניות חוזרת אחורה בהדרגה. אם המחרוזת התואמת קרובה להתחלה או לאמצע של המטען הייעודי (payload), שימוש בכמת חמדן כמו .* יכול לקחת הרבה יותר זמן וכוח עיבוד מאשר כמתים לא חמדנים כמו .*? או (במקרים פחות נפוצים) כמתים בעליים כמו .*+.

כמתנים לא חמדנים (כמו X*?, ‏ X+?, ‏ X??) מתחילים בניסיון להתאים תו בודד מתחילת המטען הייעודי (payload) ומוסיפים בהדרגה תווים. כמתחים קנייניים (כמו X?+, X*+, X++) מנסים להתאים רק פעם אחת את כל המטען הייעודי.

בהינתן הטקסט לדוגמה הבא לתבנית שלמעלה:

Hello this is a sample text with Exception in thread
with lot of text after the Exception text.

במקרה הזה, שימוש ב-.* חמדן לא יניב ביצועים טובים. הדפוס .*Exception in thread.* דורש 141 שלבים כדי להתאים. אם השתמשתם בדפוס .*?Exception in thread.* (שמשתמש בכמת מסוג reluctant), התוצאה הייתה רק 55 שלבים.

השפעה

שימוש בכמתים חמדנים כמו תווים כלליים לחיפוש (*) עם מדיניות RegularExpressionProtection עלול להוביל ל:

  • עלייה בזמן האחזור הכולל של בקשות API לגבי מטען ייעודי (payload) בגודל בינוני (עד 1MB)
  • זמן ארוך יותר להשלמת ההפעלה של מדיניות RegularExpressionProtection
  • בקשות API עם מטען ייעודי גדול (>‎1MB) נכשלות עם שגיאות 504 Gateway Timeout אם פג הזמן הקצוב מראש ב-Apigee Router
  • ניצול גבוה של CPU במעבדי הודעות בגלל כמות גדולה של עיבוד, שיכולה להשפיע על בקשות API אחרות

שיטה מומלצת

  • מומלץ להימנע משימוש בכמתים חמדנים כמו .* בביטויים רגולריים עם מדיניות RegularExpressionProtection. במקום זאת, כדאי להשתמש בכמתים לא חמדנים כמו .*? או בכמתים בעליים כמו .*+ (פחות נפוץ) בכל מקום שאפשר.

קריאה נוספת