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

זה בדיוק מה שקרה במשך כשבוע לגולשים באתרים שהשתמשו בפלטפורמת הפרסום Adform. החוקר Kevin Beaumont גילה שאחד מקובצי ה-JavaScript של החברה, כזה שנטען אוטומטית במיליוני עמודים, הוגש עם תוספת זדונית בסופו: קוד שמחליף כתובות של מטבעות קריפטו בזמן אמת. Adform אישרה את האירוע והסירה את הקוד ב-27 ביולי, אבל לא לפני שהוא כבר רץ בדפדפנים של מי-יודע-כמה אנשים.

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

איך קוד של חברת פרסום מגיע לאתר שלכם

Adform היא חברת ad-tech, מהסוג שרוב הגולשים לא מכירים בשם אבל פוגשים בכל יום. אתרים שמוכרים שטחי פרסום מטמיעים אצלם קטע קוד JavaScript של חברות כאלה, והקוד הזה אחראי לטעון את המודעות הנכונות, למדוד חשיפות ולעקוב אחרי ביצועים. הקובץ הרלוונטי כאן נקרא trackpoint-async.js, והוא מוגש מהשרת s2.adform.net שעד האירוע הזה איש לא הסתכל עליו פעמיים.

וכאן בדיוק נקודת התורפה של השרשרת. כשאתר מטמיע סקריפט של ספק צד-שלישי, הוא בעצם מריץ בדפדפן של הגולשים קוד שהוא עצמו לא כתב, לא ראה, ולא שולט בו. ברגע ש-Adform הגישה גרסה נגועה של הקובץ, כל אתר שמטמיע אותו הגיש את הקוד הזה הלאה, לכל מבקר, בלי לדעת שקרה משהו. לפי הנתונים ש-Adform עצמה פרסמה על 2025 מדובר בפלטפורמה שמגישה כמיליארד וחצי מודעות ביום ביותר מ-180 מדינות. גם אם רק חלק מהעמודים האלה טענו את הגרסה הנגועה בחלון הזמן הרלוונטי, החשיפה הפוטנציאלית כאן היא לא של אתר בודד.

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

אז איך מחליפים כתובת מתחת לאף?

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

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

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

למה אף מנוע זיהוי לא תפס את זה?

התוספת הזדונית לא הוגשה כקובץ נפרד וחשוד. היא נדחפה לסוף הספרייה הלגיטימית, אחרי קוד המעקב האמיתי שהמשיך לעבוד כרגיל. מבחוץ הקובץ נראה כמו trackpoint-async.js הרגיל, רק קצת ארוך יותר. לפי Beaumont, סריקה של הקובץ ב-VirusTotal לא החזירה ולו זיהוי אחד מכל עשרות מנועי האנטי-וירוס שרצו עליו.

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

מה בדיוק שלח הקוד החוצה

כאן מתחיל אזור אפור. לפי הניתוח של Beaumont ושל BleepingComputer, הסקריפט לא הסתפק בהחלפת כתובות אלא גם שלח לשרת מרוחק, בכתובת 84.32.102[.]230 בפורט 7744, את כתובת ה-IP של הגולש, את האתר שממנו הגיע ואת הנתיב שבו צפה. Adform, לעומת זאת, כתבה בהודעתה שלא מצאה ראיה לכך שהקוד העביר כתובות IP או מידע על האתרים לגורם חיצוני, אבל שהעברה כזאת ייתכן שהייתה אפשרית ושההיבט הזה עדיין בבדיקה.

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

כמה זמן זה נמשך

גם על משך האירוע יש פערים בין הגרסאות. הדגימה הישנה ביותר שנשמרה, לפי Archive.org, היא מ-26 ביולי בשעה 23:29, ו-Adform זיהתה והסירה את הקוד למחרת, ב-27 ביולי. אבל Beaumont מעריך שהקוד הזדוני הוגש בפועל במשך כשבוע לפני שמישהו שם לב. במילים אחרות, גם אם ההסרה עצמה הייתה מהירה ברגע שהתגלה, החלון שבו הקובץ הנגוע הוגש למבקרים נמדד בימים, לא בשעות.

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

מה זה אומר בפועל

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

ולמי שמנהל אתר יש כאן תזכורת לא נעימה: כל סקריפט צד-שלישי שאתם מטמיעים הוא קוד שאתם מגישים לגולשים שלכם באחריותכם, גם כשמי שכתב אותו הוא ספק מוכר וּותיק. שווה לדעת בדיוק אילו סקריפטים חיצוניים רצים באתר, ובמקומות שבהם זה ישים, לשקול הקשחות כמו Content-Security-Policy או Subresource Integrity שחוסמות טעינה של קובץ ששונה מאחורי הגב שלכם.