לאחר דיון באשכול הזה בפורום mitmachim.top התעוררתי לעניין שאין אף תוסף דפדפן שמבצע את הפעולה הפשוטה הזו: בודק כל כתובת שנכנסים אליה בשירות סריקת הדומיינים "וירוסטוטאל". וירוסטוטאל, למי שלא מכיר, מספקים דו"ח של כמה עשרות כלי סריקה - כולל אנטיוירוסים ידועים (ESET, קספרסקי וכו') לכתובות באינטרנט, עם אינדיקציות לגבי סיבות החשד (במקרה ויש חשד).

כשנכנסתי קצת לעניין ראיתי שלוירוסטוטאל יש API שמאפשר לקבל מידע על כתובות רצויות, בצורה יותר 'מתוכנתת'. לאחר עיון קצר מתברר שה-API שלהם מוגבל ל-500 קריאות בלבד ביום. בהתחלה זה היה נראה לי ממש מוגזם ולא פונקציונלי (כי כל מה שתכננתי היה בדיקה של הכתובת בכל מעבר של טאב, ואני עצמי עושה את זה להשערתי כ-2000 פעמים ביום...) אבל אחר מחשבה, הכנתי אפשרות שבעצם מתגברת על זה והכל להלן:

כתבתי תוסף דפדפן (בנתיים רק לפיירפוקס, בכרום צריך לסדר את הקוד קצת), שמנטר את השינויים שמתרחשים בטאבים, בכל שינוי של טאב הוא לוקח את כתובת הדומיין (לא הכתובת כולה, אלא רק הכתובת הראשית - אותה אנחנו צריכים והיא תהיה דומה לעוד המון טאבים אחרים, כמובן) ומבצע שליחה שלה ל-API של וירוסטוטאל, מקבל את התשובה ואם היא מכילה התראות מוירוסים - מתריע למשתמש.

הגדרתי בתוסף, שעד 4 מנועי סריקה שמזהים כוירוס - זה רק ישנה את סמל התוסף לאזהרה כזו, ויכתוב את המספר (נגיד 3, אם יש 3 מנועי סריקה שמזהים). מעל 4 זיהויים הוא כבר מוציא נוטיפיקציה-התראה של הדפדפן על הזיהוי.

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

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

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

פרקטי:

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

תמצאו בצד שמאל את מפתח ה-API שלכם כתוב בסטרינג ארוך של אותיות ומספרים

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

  1. הוספת הדומיין באופן ידני לרשימת האתרים האמינים.
  2. הגדרות השמירה האוטומטית.
  3. כפשוטו,
  4. הגדרות - כולל הגדרת ה-API.

כעת התוסף יעקוב אחרי פעילות מעבר הטאבים שלכם, ובכל פעם שתעברו טאב הוא ייקח את כתובת הדומיין של הטאב, יבדוק אותה (קודם במאגר, אחר כך ב-API של וירוסטוטאל) ויחווה לכם אם המיקום בטוח, או שיש עליו התראות.

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

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

קוד:

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

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

יש את קובץ הרקע background.js, שהוא בעצם הקוד של התוסף עצמו - מה שמפקד על כל הפעילות ומה שיכול להפעיל קבצים אחרים.

יש את קובץ ה-html שמרכיב את התפריט שבהגדרות, ואיתו צמוד לו קובץ ה-js שמבצע את הרישום של הקוד API שמתקבל מהלקוח, במאגר של התוסף. הקבצים - option.html, option.js.

ויש בסוף גם קובץ בשם cors.js, הקובץ הזה בעצם עוזר לתוסף לשלוח את הבקשות לוירוסטוטאל ולקבל את התשובות בלי להיתקל בבעיה המצויה בדפדפנים - CORS - זו בעצם קבוצה של הגדרות אבטחה מסוימות שהוכנסו בשנים האחרונות לדפדפנים המובילים על מנת להימנע מפירצות אבטחה בסיסיות. אנחנו צריכים כאן לשלוח ולקבל תגובות באמצעות הפונקציה fetch וכדי להתעלות מעל החסימה של CORS - הקובץ js הזה עורך את כל התגובות שמגיעות לתוסף ומוסיף להם header שעובד על הדפדפן כאילו התגובה מתירה גישת CORS. את כל הקוד שבו העתקתי מהתוסף המצוין הזה שהקוד שלו נמצא בגיטהב כאן. קרדיט..
הקובץ הזה הוא בעצם, גם קובץ רקע.

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

נסתכל על הקובץ background.js - בהתחלה הוא בעצם מעמיס לזיכרון פונקציה ארוכה בשם handleUpdated. בפונקציה הזו כלול כל הפעילות שאמורה לקרות כאשר קורה איזה שינוי בטאבים בדפדפן. בנתיים נעקוף על זה.

אחרי כל הפונקציה הזו מגיעה פונקציה נוספת שנשמרת לזיכרון - collectmenu - שהיא אחראית על עריכת התפריטים מחדש בכל פעם שקוראים לה (עריכת התפריט שנפתח כשלוחצים בלחצן ימני על סמל התוסף).

אחרי זה מגיע פקודה - שמוסיפה listener - מאזין - שמאזין לאירועים שקשורים ל-browserAction > במקרה שהאירוע הוא onClicked והיא מריצה בכזה מקרה פונקציה (בלי שם). המשמעות של browserAction היא - סמל התוסף. זה השם של האלמנט הזה, ואנחנו מאזינים לאירוע שבו לוחצים על סמל התוסף. כשקורה כזה דבר תרוץ הפונקציה שמה שהיא עושה זה:

chrome.storage.local.get(["api"], function (result) {
    if (typeof result.api === "undefined") {
      chrome.runtime.openOptionsPage();
    } else {
      var tab = chrome.tabs.query( { currentWindow: true, active: true }, (tabs) => {
          var url = new URL(tabs[0].url);
          if (!tabs[0].url || ["chrome:", "about:", "moz-extension:", "https://www.google.com", "https://www.virustotal.com"].some((p) => tabs[0].url.startsWith(p))) {
            return;
          } else {
            chrome.tabs.create({ url: "https://www.virustotal.com/gui/domain/" + url.hostname });
          }
        }
      );
    }
  });

נתחיל מהתחלה - chrome זה באופן כללי פניה ל-API של הדפדפן, לאחר מכן פונים למקטע storage ובתוכו לאחסון המקומי (כלומר של האפליקציה הנוכחית וזה במקרה שלנו - התוסף) - local - בפקודת get - לקבל נתונים מהאחסון הזה. הפרמטר שאנחנו מזינים לפקודה הוא שם של key מתוך האחסון הזה. (כאן אנחנו מבקשים את הkey בשם "api"). לאחר שקיבלנו את הערך הזה מהאחסון אנחנו מריצים עליו פונקציה (משרשרים לה את הפרמטר result שזה אומר - תוצאות הבקשה מהאחסון), שבודקת - אם קיים בתוך המיקום הזה באחסון משהו או שהוא ריק (undefined - לא מוגדר). במקרה שהוא לא מוגדר - כלומר המשתמש לא הכניס למאגר עדיין את ה-API שלו, אנחנו מובילים אותו להגדרות (הרי הוא לחץ על סמל התוסף, לשם הוא צריך להגיע).

במקרה שכן קיים API במאגר, אנחנו מבינים שהמשתמש לחץ על הסמל כדי לפתוח דוח של וירוסטוטאל על הדומיין הנוכחי - אז מקבלים קודם את הכתובת של הטאב הנוכחי (על ידי פנייה לפונקציה tabs.query עם פרמטרים של הטאב הנוכחי והפעיל בלבד), מבצעים עליו לוגיקה קטנה כדי שיהיה מוכן לשימוש כדומיין (רק אם מגדירים את המשתנה כ-url אפשר לבצע עליו מניפולציות כגון hostname שמוציא רק את הדומיין מתוך הסטרינג). בודקים שאנחנו לא בטאב מערכתי (כגון הגדרות של הדפדפן, או האתר של וירוסטוטאל עצמם וכדו') שבו לא נוכל באמת להוציא דוח מוירוסטוטאל, ופותחים את הכתובת שהוכנה בטאב חדש (chrome.tabs.create).

זה היה המאזין שמחכה ללחיצה על סמל התוסף.

לאחר מכן, מגיע window.onload - שכמו ב-html רגיל - מאזין לאירוע של - טעינה מלאה של הדף. ואז מריץ את הקוד. כאן הוא משמש להרצה של קוד שרוצים להריץ מיד כשהתוסף עולה.

window.onload = function () {
  console.log("Extension has started...");
  chrome.storage.local.get(["sites"], function (result) {
    if (typeof result.sites === "undefined") {
      console.log("no site list, starting one with example.com");
      chrome.storage.local.set({ sites: ["example.com"] }, function () {});
    } else {
      console.log("safe site list found, have " + result.sites.length + " sites in the list");
      return;
    }
  });
  chrome.storage.local.get(["collect"], function (result) {
    if (typeof result.collect === "undefined") {
      console.log("no collect safe sites choice, set as true by default");
      chrome.storage.local.set({ collect: true }, function () {});
    } else {
      console.log("collecting safe sites: " + result.collect);
      return;
    }
  });
  chrome.storage.local.get(["api"], function (result) {
    if (typeof result.api === "undefined") {
      console.log("no api key in storage, warning user");
      chrome.browserAction.setIcon({ path: "icons/warn.png" });
      chrome.browserAction.setTitle({
        title: `אישי. לחץ על הסמל כדי לגשת להגדרות API עוד לא הגדרת מפתח`,
      });
      chrome.storage.onChanged.addListener(function () {
        chrome.storage.local.get(["api"], function (result) {
          if (typeof result.api === "undefined") {
            return;
          } else {
            console.log("api key set: " + result.api);
            chrome.tabs.onUpdated.addListener(handleUpdated);
          }
        });
      });
    } 
    else {
      console.log("api key found: " + result.api);
      chrome.tabs.onUpdated.addListener(handleUpdated);
    }
  });
  chrome.contextMenus.removeAll();
  collectmenu();
};

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

chrome.storage.local.get(["sites"], function (result) {
    if (typeof result.sites === "undefined") {
      console.log("no site list, starting one with example.com");
      chrome.storage.local.set({ sites: ["example.com"] }, function () {});
    } else {
      console.log("safe site list found, have " + result.sites.length + " sites in the list");
      return;
    }
  });

בהתחלה מבקשים את המאגר, בודקים אם מה שהתקבל זה אכן בעל תוכן או ריק (undefined). במקרה ואכן ריק נוצר מאגר פייק כנ"ל, אחרת - לוג שהכל בסדר.

לאחר מכן בודקים אם קיימת הגדרה (באחסון) לגבי שמירת אתרים בטוחים בצורה אוטומטית. אם אין הגדרה כזו (לטוב או למוטב) יוצרים את ההגדרה שישמור (collect: true).

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

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

chrome.tabs.onUpdated.addListener(handleUpdated);

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

דבר ראשון הפונקציה מקבלת את הפרמטרים הבאים - tabId, tabInfo - מזה היא יודעת את פרטי הטאב עליו היא רצה. הפונקציה בודקת:

  1. אם כתובת הטאב מראה שזה טאב תפעולי כגון דפי ההגדרות בכרום (:chrome) או פיירפוקס (:about) - אם כן היא מחווה את זה למשתמש מגדירה את הסמל והטקסט של התוסף ומחזירה return שזה בעצם פשוט עוצר את הפונקציה.
  2. אם כתובת הטאב שייכת לחיפוש גוגל (https://google.com) או לוירוסטוטאל (אין מה לבדוק..) - התוסף לא בודק, ומחווה למשתמש שהכתובת מאושרת. וכמובן מחזיר return.
  3. כעת שהכתובת עברה את שני המחסומים האלו - התוסף פותח את מאגר הכתובות האמינות ובודק האם הסטרינג של הדומיין של הטאב הנוכחי, קיים במאגר. לפני שמתחילים צריך ליצור מהכתובת של הטאב אלמנט שניתן לבצע עליו מניפולציות של כתובת URL. לכן עושים ככה:
var domain = new URL(tabInfo.url);

ואז פותחים את המאגר ובודקים אותו מול השלב hostname שבתוך המשתנה domain שנוצר (hostname ייקח מה-URL את כתובת הדומיין בלבד ויזרוק את כל השאר):

chrome.storage.local.get(["sites"], function (result) {
    if (result.sites.indexOf(domain.hostname) > -1)

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

אם הכתובת לא נמצאה במאגר (else של ה-if שבקטע הקודם) מתחיל תהליך של שליחת הכתובת לבדיקה. מוציאים מהזיכרון את מפתח ה-API שמשמש לבדיקה מול וירוסטוטאל, מרכיבים אותו בבקשה (fetch) שולחים אותה ומקבלים את התשובה. כעת מתעסקים עם התשובה - צריך לבדוק שלא היו שגיאות בבקשה או תקלות אחרות, את זה בודקים עם קוד תגובה (response status).

204 - וירוסטוטאל מגבילים את השימוש ב-API כזכור, ובמקרה של עקיפה של ההגבלה - כל בקשה תקבל תגובה ריקה (no content) עם קוד 204. כך שבשלב ראשון בודקים אם הקוד הוא 204, אם כן מחווים למשתמש ו-return.

200 - תשובה תקינה. עכשיו פותחים את התשובה ובודקים את האובייקט data.positives, הוא זה שמכיל את כמות המנועים שזיהו את הכתובת כבעייתית:

if (data.positives > 0 && data.positives < 4)

גדול מ-0 אבל קטן מ-4. במקרה כזה מחווים למשתמש ונותנים לו אפשרות להכניס את האתר לרשימת האמינים.

if (data.positives > 3)

גדול מ-3 (כלומר 4 או יותר) - מוציאים התראה (chrome.notifications.create) ויוצרים את האפשרות להכניס לרשימת האתרים האמינים.

אם שני התנאים הללו לא מתקיימים (כלומר נקי) מחווים למשתמש שהאתר נקי, בודקים אם מוגדרת האפשרות של שמירה אוטומטית של אתרים נקיים לרשימה - אם כן, מכניסים את הכתובת לרשימה.

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

קצת רפרנס..

הקישורים הם ל-MDN.


כל הקוד נמצא בריפו הזה בגיטהב https://github.com/chaim-chv/virustotal-scanner-heb
מוזמנים לערוך ולשפר!


אם יש לכם איזו שאלה ❔✨ או כל תגובה 💬, הארה 💡 והערה ❕ שהיא על הפוסט - אשמח מאוד! אם תכתבו אותה בהערות כאן למטה
פשוט להתחבר עם חשבון גיטהב ולהגיב 🎉