לכל הפוסטים

קוד "בטוח" זה לא אומר כלום: איך כרטיס QR אחד נכנס לאירוע פעמיים

קובץ PDF עם קוד QR הוא תמונה, לא נעילה — אפשר לצלם אותו ולהעביר לחבר. מה שבאמת מונע כניסה כפולה לא כתוב בתוך הקוד, אלא קורה בשרת ברגע שהוא נסרק בדלת.

לובי כנס עם משתתפים סורקים קודי QR מהטלפון שלהם בדלפק הרישום

לפי Action Fraud, מרכז הדיווח הלאומי להונאות ופשעי סייבר של משטרת סיטי לונדון, נזקי הונאת כרטיסים בבריטניה עלו ב-47% בין 2023 ל-2024 — מ-6,694,955 ליש"ט ל-9,794,034 ליש"ט, על פני 9,826 דיווחים (עלייה של 11% בכמות הדיווחים עצמה). מתוכם, 1.6 מיליון ליש"ט אבדו ספציפית להונאת כרטיסי הופעות באותה שנה.

המספרים האלה מגיעים מתעשיית כרטוס בשלה בהרבה משלנו, אבל הבעיה שהם מתעדים כמעט אף פעם אינה תחכום פלילי. היא הרבה יותר פשוטה: כרטיס שהוא, בפועל, רק תמונה. PDF עם קוד QR זה כמו כל תמונה אחרת — קל לצלם, קל להעביר בוואטסאפ, קל לשלוח לשלושה חברים בדיוק כמו לאחד.

תמונה לא יודעת שהיא כבר נוצלה. השאלה האמיתית היא מה קורה כשמישהו סורק אותה בדלת — ושם הרבה מערכות כרטוס גנריות פשוט לא בודקות שום דבר.

מה שבאמת כתוב בתוך קוד ה-QR (וזה כמעט שום דבר)

כשמערכת כרטוס יוצרת קוד QR לאורח, עומדות בפניה שתי אפשרויות: לקודד בתוך התמונה עצמה משהו "חכם" — שם, מספר כרטיסים, איזושהי חתימה — או לקודד בה רק מזהה חסר משמעות, ולהשאיר את כל הידע האמיתי בצד השרת.

הבחירה השנייה היא הבחירה שקובעת בפועל כמה שווה הקוד. אצלנו כל אורח מקבל טוקן אקראי באורך של 10 עד 14 תווים — אותיות גדולות וקטנות בלבד — שנבדק מול כל הטוקנים שכבר קיימים לפני שהוא נמסר. באורך של ארבעה תווים בלבד, לוח האותיות נותן כ-7.3 מיליון צירופים אפשריים; נשמע הרבה, אבל דף שמאמת טוקן עונה תוך כ-50 מילישניות, כך שסורק אוטומטי היה חולף על פני כל המרחב הזה בפחות מיומיים. באורך 10 תווים המרחב קופץ לכ-1.4×10¹⁷ צירופים — ולפי אותו חישוב, סורק כזה "צריך יותר זמן מכפי שהיקום פעל עד היום".

והחלק החשוב באמת: מה שהתמונה מציגה הוא הטוקן החשוף בלבד. לא כתובת אתר, לא שם האורח, לא מספר הכרטיסים שלו. בלי חיפוש בצד השרת, לתמונה עצמה אין שום משמעות — היא לא "יודעת" כלום על עצמה.

התמונה לא יודעת שהיא כבר נוצלה — השרת כן

וזו בדיוק הנקודה שממנה מתחילה הבעיה של PDF גנרי. גם אם הקוד עצמו בלתי-ניתן-לניחוש, זה עדיין לא עונה על השאלה שבאמת קובעת הכול: מה קורה בפעם השנייה שסורקים אותו? אם התשובה חיה בתוך התמונה, היא תמיד תהיה "כן, תקין" — כי תמונה לא מתעדכנת בין סריקה לסריקה. הפתרון היחיד הוא שהתשובה תלויה במשהו שכן מתעדכן: שורה בבסיס נתונים, שנקראת מחדש בכל פעם שהקוד נסרק.

כרטיס-תמונה גנרי (PDF/JPG)כרטיס עם אימות בזמן סריקה
מה כתוב בתוך התמונה עצמהלרוב כלום מעבר לעיצוב ולקודטוקן אקראי בלבד, חסר משמעות בלי בדיקה מול השרת
מה קובע אם הכרטיס "תקף"שום דבר בתמונה לא משתנה אחרי ההנפקהשדה בבסיס הנתונים, שמתעדכן בכל סריקה
סריקה שנייה של אותו קודתלוי אם בכלל מישהו טורח לבדוק ידניתנבדקת מול השרת ברגע הסריקה, לא מול התמונה
שתי דלתות שונות, אותו קודרואות מה שהן רואות — בלי סנכרוןרואות את אותו סטטוס חי כמעט מיידית

מה קורה בפועל כשהסורק בדלת קורא את הקוד

בדלת, מכשיר הסריקה לא "קורא" את הכרטיס במובן הרגיל של המילה — הוא שולח את הטוקן לשרת, והשרת שולף מחדש, מהמסד, את שורת האורח לפי הטוקן ולפי האירוע הספציפי. זה קורה בכל סריקה, בלי יוצא מן הכלל. אין מצב שבו האימות מסתמך על מה שהיה כתוב בתמונה, פשוט כי בתמונה אין שום דבר לסמוך עליו.

הכרטיס שנוצל פעם אחת לא נוצל פעמיים

לכל אורח יש בבסיס הנתונים שדה שמייצג כמה כרטיסים הוא זכאי להם, ושדה נפרד שסופר כמה כבר נוצלו בפועל. בכל סריקה השרת מחשב את ההפרש. אם לא נשאר כלום, הסריקה נדחית עם תשובה ברורה, לא "שגיאה כללית": "כל הכרטיסים של אורח זה כבר נוצלו". בפאנל הבקרה של המפעיל הניסוח אפילו מפורש יותר — "ייתכן שמישהו אחר נכנס עם הקוד הזה".

ההבחנה החשובה כאן היא לא שהקוד "קשה לזייף". היא שהמערכת יודעת להגיד "זה כבר נוצל" ברגע שהוא כבר נוצל — לא בדיעבד, בדוח שמישהו יריץ למחרת.

המירוץ בין שתי דלתות, ואיך המערכת מכריעה בו

יש כאן תרחיש עדין: אם שתי דלתות סורקות את אותו קוד כמעט באותו רגע, שתי הבקשות מגיעות לשרת כמעט יחד, וכל אחת "רואה" את אותו מצב פנוי. בדיוק בשביל זה העדכון בשרת נכתב כבר מההתחלה ככתיבה מותנית ולא כקריאה-ואז-כתיבה רגילה: השורה מתעדכנת רק אם היא עדיין בדיוק במצב שנקרא רגע קודם, ואם מכשיר אחר כבר שינה אותה בינתיים — הבקשה השנייה קוראת שוב ומנסה מחדש, עד שמונה פעמים, עד שהיא מכריעה לפי התמונה העדכנית ביותר. כך רק בקשה אחת יכולה "לזכות" בכרטיס האחרון בכל רגע נתון, וכל דלת מחוברת רואה את אותו סטטוס "נכנס" של אותו אורח כמעט מיידית — לא רק הדלת שביצעה את הסריקה.

אירוע עם כמה לילות? כרטיס לא עובר בין דלתות

באירועים עם כמה מפגשים — כמה לילות של אותו פסטיבל, כמה מושבים של אותה הופעה — הכניסה נספרת בנפרד לכל מפגש בפני עצמו. כרטיס שתקף ללילה אחד נדחה אם מנסים להיכנס איתו למפגש אחר, כי הבדיקה לא מתייחסת רק לאורח, אלא לצירוף הספציפי של אורח ומפגש.

מצב לא-מקוון: מכשיר אחד לכל אירוע

כל הבדיקה שתוארה כאן מסתמכת על חיבור לשרת ברגע הסריקה. כשאין רשת בדלת, המערכת עוברת למצב לא-מקוון: מכשיר הסריקה שומר את הסריקות מקומית וממשיך להחליט על סמך המידע העדכני שיש לו. כדי לשמור על אותה רמת ודאות גם במצב הזה, המערכת מתירה מכשיר אחד בלבד לכל אירוע לפעול במצב לא-מקוון במקביל, כך שאין שתי החלטות עצמאיות שיכולות לסתור זו את זו. ברגע שהמכשיר חוזר להתחבר, כל סריקה שנרשמה מקומית נבדקת מחדש מול הרשימה המלאה והעדכנית של האורחים, כדי שכל כפילות תתגלה ותטופל מיד. יש גם הגבלה של עד 40 ניסיונות שגויים לשעה לכל כתובת IP על ניחוש טוקנים — בלי לגעת בתנועה האמיתית של אורחים אמיתיים בדלת.

המסקנה הפשוטה: כרטיס לא "בטוח" בגלל שאי אפשר לצלם אותו — אפשר תמיד לצלם תמונה. הוא בטוח, במידה שהוא כן, כי מישהו בודק בזמן אמת אם הוא כבר נוצל, ומעדכן את זה לפני שהסריקה הבאה מגיעה לדלת.

רוצים לבדוק איך זה מתנהג על הכרטיסים של האירוע הבא שלכם? דברו איתנונפתח בטאב חדש ונראה יחד מה קורה בפועל כשמישהו מנסה להיכנס פעמיים עם אותו קוד.