Checklist מינימלי לאבטחת אפליקציות Windows

· עודכן בתאריך: · · פיתוח Windows, אבטחה, architecture, C# / .NET, Win32

היסטוריית עדכונים (גרסה ראשונה, פורסמה בתאריך 14 Mar 2026)
פרסום ראשון
לצטט את המאמר הזה(DOI: 10.5281/zenodo.22173448)

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

Go Komura (2026). Checklist מינימלי לאבטחת אפליקציות Windows. KomuraSoft LLC. https://doi.org/10.5281/zenodo.22173448 https://comcomponent.com/he/blog/windows-app-security-minimum-checklist/

DOI (הגרסה האחרונה)
10.5281/zenodo.22173448
DOI (הגרסה הזו)
10.5281/zenodo.22173449

הורידו את ה-checklist בגרסת Excel

תוכן הקובץ הזה זהה ל-checklist שלפני release בפרק 4 (8 קטגוריות, 32 סעיפים). ההבדל היחיד הוא שיש בו שדות מילוי Status ו-Notes, ושני גליונות Checklist-ja / Checklist-en שמכילים גם יפנית וגם אנגלית. אם קוראים תוך כדי בדיקה — פרק 4 מספיק. אם מחלקים כרשומת review — גרסת ה-Excel.

כשמדברים על אבטחת אפליקציות Windows, הנושא נוטה להתנפח מהר. Zero Trust, EDR, SBOM (Software Bill of Materials), ניהול certificates, ניהול פגיעויות. כל אלה חשובים, אבל בעבודה מעשית יש לפני כן כמה יסודות שלא כדאי לפספס.

בפרט, באפליקציות מהסוג הבא, סגירת הפרצות הבסיסיות עוזרת יותר מ”הגנה מתקדמת”.

  • אפליקציות desktop ב-WPF / WinForms / WinUI
  • אפליקציות Win32 ב-C++ / C#
  • שילוב עם מכשירים, שילוב קבצים, חיבור ל-DB, כלי הפצה פנימיים בארגון
  • אפליקציות עסקיות עם מנגנון update אוטומטי
  • הרכב שכולל Windows Services או קבצי EXE נלווים

בפיתוח אפליקציות Windows, מעשי יותר להתחיל מסגירת פרצות שברור שהן מסוכנות, ולא לנסות להשלים הכול בבת אחת.

כאן נעבור לפי סדר תכנון, מימוש, הפצה ותפעול על הנקודות שברור שלא כדאי לפספס.

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

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

1. קודם המסקנה

  • הדבר הראשון שלא כדאי לפספס: לא לדרוש הרשאות Administrator שלא לצורך, לחתום, לא להחזיק secrets ב-plaintext, ולא לבטל אימות certificates.
  • באפליקציות Windows, קבצי ההפצה עצמם הם משטח תקיפה. בטוח יותר להתייחס גם ל-EXE / DLL / MSI / MSIX / מודול update אוטומטי.
  • ServerCertificateValidationCallback => true, connection string ב-plaintext, טעינה רשלנית עם LoadLibrary("foo.dll"), והרצת SQL בשרשור מחרוזות — כל אלה מוטב להימנע מהם גם ברף המינימלי.
  • אם רק חלק מהעבודה דורש הרשאות Administrator, בטוח יותר להפריד רק את אותו חלק ל-process נפרד או ל-Windows Service, ולא להעלות את הרשאת כל האפליקציה.
  • כדאי להניח מראש שאפליקציה שמופצת ב-Windows תזדקק לחתימה + timestamp. זה נותן לא רק אמינות בעיני המשתמשים, אלא גם זיהוי שיבוש והסבר תפעולי נוח יותר.
  • secrets שנשמרים מקומית משתמשים לפי הצורך ב-DPAPI / ProtectedData או ב-Credential Locker. לפחות כדאי לצאת ממצב שבו הם ב-appsettings.json ב-plaintext.
  • לא כל כמות לוגים טובה. השארת tokens, סיסמאות, connection strings, מידע אישי וגוף request מלא בלוג כמו שהוא הופכת את הלוג עצמו לגורם התקרית.

אבטחה מינימלית אינה הוספת features מיוחדים אלא הימנעות מהשארת התנהגות default מסוכנת או מימוש רשלני.

התפיסה של אבטחה מינימליתתרשים שמראה שאבטחה מינימלית אינה הוספת features מיוחדים אלא הימנעות מהשארת התנהגות default מסוכנת או מימוש רשלני.זה לא המוקד המינימליזה המוקדהוספת feature מיוחדאבטחה מינימליתלא להשאיר default מסוכן או מימוש רשלני

איור 2: הרף המינימלי אינו הוספת features, אלא הימנעות מהשארת default מסוכן או מימוש רשלני.

ב-diagram, solid line מציינת relation שתמיד מתקיים ו-dashed line מציינת relation מותנה (התנאים מופיעים בהסבר של כל relation ב-detail page). הרשימה המלאה של ה-relations (סה”כ 21, כולל evidence ו-certainty) וההגדרות של ה-concepts המרכזיים נמצאות ב-detail page של ה-knowledge map (ביפנית). Data: JSON-LD / Turtle

2. היקף המאמר ומשמעות “מינימום”

2.1. ההיקף שהמאמר מטפל בו

המאמר מתייחס לאפליקציות Windows מהסוג הבא:

  • אפליקציות desktop ב-WPF / WinForms / WinUI
  • אפליקציות Win32 ב-C++ / C#
  • כלי הפצה פנימיים בארגון, כלי שילוב מכשירים, כלי ניטור
  • הרכב שכולל EXE נלווה, Windows Service ו-updater
  • תוכנה עסקית שמופצת ב-EXE / MSI / MSIX

“המינימום” כאן אינו הצורה הסופית שעוברת audit, אלא הסעיפים שהיעדרם גורם לתקלה בשגרה.

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

איור 3: “המינימום” כאן אינו הצורה הסופית שעוברת audit, אלא הסעיפים שהיעדרם גורם לתקלה בשגרה.

גם הנחות היסוד לדוגמאות הקוד מוגדרות מראש. דוגמאות C# מניחות .NET 8 ואילך, ודוגמאות C++ מניחות Win32 API. גם ב-.NET Framework 4.8 התפיסה נשארת זהה, אך יש מקומות שבהם הכתיבה המומלצת השתנתה. הדוגמה הבולטת היא סביבת ServicePointManager בסעיף 3.6, שבקוד חדש עבר לתפיסה שמשתמשת ב-IHttpClientFactory וב-HttpClient. כשקוראים מחדש קוד מדור ישן, שימו לב במיוחד לנקודה הזו.

2.2. מה לא נכלל

מצד שני, יש דברים שנשארים מחוץ למוקד המאמר.

  • תכנון Zero Trust כלל-ארגוני
  • תפעול כולל של EDR / SIEM / DLP / MDM
  • hardening מפורט של kernel drivers
  • תכנון הצפנה מאפס
  • ניתוח איומים מתקדם או הליכי forensics

כלומר, לא “מדיניות אבטחה עצומה של הארגון כולו”, אלא קו יסוד שקשה למפתחי אפליקציות Windows להשמיט בכוחות עצמם לפני release.

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

איור 4: המאמר לא עוסק במדיניות כלל-ארגונית, אלא בקו היסוד שהמפתח יכול לתפוס בעצמו לפני release.

3. קודם ה-checklist

לפני דיון מפורט, נציג טבלה שמאפשרת מבט כולל. כבר ממנה אפשר לזהות באילו מקומות כדאי לבדוק שוב.

3.1. התמונה הכוללת

מה בודקים מה עושים כמינימום תקלה טיפוסית
הרשאת הרצה asInvoker כברירת מחדל, ורק עבודה שדורשת elevation מופרדת הפיכת כל האפליקציה ל-requireAdministrator
אמינות ההפצה code signing ל-EXE / DLL / MSI / MSIX, כולל timestamp הפצה במצב לא חתום
update קיבוע מקור ה-update, וזיהוי שיבוש דרך HTTPS ואימות חתימה דריסה ישירה אחרי הורדה ב-HTTP
secrets לא להחזיק secrets בקוד המקור או ב-Configuration ב-plaintext, שימוש ב-DPAPI / Credential Locker וכדומה הצבת API keys או connection strings בקובץ Configuration ב-plaintext
תקשורת שימוש ב-HTTPS ולא ביטול אימות certificates דילוג קבוע על אימות certificates עם return true
קלט חיצוני validation מלא של SQL, קבצים, IPC, URI, CSV, JSON וכדומה מעבר חופשי בטענה “זה כלי פנימי בארגון”
טעינת DLL נתיב מוחלט, SetDefaultDllDirectories, סדר חיפוש בטוח הסתמכות על הספרייה הנוכחית עם LoadLibrary("foo.dll")
לוגים מיסוך tokens, סיסמאות, PII, והפרדה בין שגיאה למשתמש לשגיאה פנימית הצגה או שמירה ישירה של פרטי exception או connection string
תלויות עדכון שוטף של SDK, NuGet, VC++ runtime, תלויות OSS קיבוע לשנים ללא מעקב אחר מידע על פגיעויות

3.2. הרשאה כברירת מחדל היא asInvoker

זו הנקודה הראשונה שכדאי לבחון מחדש באפליקציית Windows. הרצת כל האפליקציה בהרשאות Administrator גורמת לכך שבאגים, החלפת DLL, קריאה שגויה של קבצי Configuration ופגמים בקלט חיצוני רצים ישירות בהרשאה חזקה.

הסכנה בהרצת כל האפליקציה בהרשאות Administratorתרשים שמראה שהרצת כל האפליקציה בהרשאות Administrator גורמת לכך שבאגים, החלפת DLL ופגמים בקלט חיצוני רצים ישירות בהרשאה חזקה.הרצת כל האפליקציה בהרשאות Administratorבאגיםהחלפת DLLקריאה שגויה של Configuration / פגם בקלטרץ ישירות בהרשאה חזקה

איור 5: כשמעלים הרשאה של כל האפליקציה, כל פגם שהוא רץ בהרשאה חזקה.

המדיניות הבסיסית היא כזו:

  • אפליקציית UI רגילה רצה ב-asInvoker
  • רק עבודה שדורשת הרשאות Administrator מופרדת ל-process נפרד או ל-Windows Service
  • elevation מתבצע רק ברגע הנדרש
  • גם הקלט שמועבר ל-EXE הנלווה או ל-service מאומת

אם מדובר באפליקציית desktop שרגילה רק לצפייה ולעריכה, ורק ההתקנה או שינוי הגדרות firewall דורשים הרשאות Administrator, בטוח יותר לרכז את מה שדורש elevation ב-broker, ולא להפוך את כל האפליקציה ל-requireAdministrator.

<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
  <security>
    <requestedPrivileges>
      <requestedExecutionLevel level="asInvoker" uiAccess="false" />
    </requestedPrivileges>
  </security>
</trustInfo>

“נוח כשהכול רץ כ-Administrator” בדרך כלל חוזר לפגוע בהמשך. הרצה בהרשאה מינימלית, עם הוצאה החוצה רק של הפעולות שבאמת דורשות זאת, מצמצמת משמעותית את רדיוס הפגיעה.

מבנה של הפרדת עבודה שדורשת elevationתרשים שמראה שאפליקציית UI רגילה רצה ב-asInvoker, שרק עבודה שדורשת הרשאות Administrator מרוכזת ב-broker ב-process נפרד או ב-service, ושה-elevation מתבצע רק ברגע הנדרש.פנייה רק ברגע הנדרשאפליקציית UI (asInvoker)broker (EXE נפרד / service)ביצוע רק של פעולות שדורשות elevationגם הקלט ל-broker מאומת

איור 6: בדרך כלל מריצים ב-asInvoker, ורק פעולות שדורשות elevation מרוכזות ב-broker, כך שרדיוס הפגיעה מצטמצם.

3.3. חתימה על הבינארי ועל ההתקנה

ב-Windows, אמינות קבצי ההפצה קובעת הכול. המשתמש נוגע לא בקוד המקור, אלא ב-EXE, DLL, MSI, MSIX וב-updater. אם אלה לא חתומים, גם זיהוי השיבוש, גם האמינות בעיני המשתמש וגם ההסבר התפעולי נחלשים.

מה שהמשתמש נוגע בו הם קבצי ההפצהתרשים שמראה שהמשתמש נוגע לא בקוד המקור אלא ב-EXE, DLL, installer ו-updater, ושאם אלה לא חתומים זיהוי השיבוש וההסבר התפעולי נחלשים.מה שהמשתמש נוגע בוEXE / DLLMSI / MSIXupdaterבלי חתימה — זיהוי שיבוש והסבר חלשים

איור 7: משטח התקיפה הוא קבצי ההפצה עצמם, ובלי חתימה מאבדים את הבסיס לאמינות.

לכל הפחות, כדאי לבדוק את אלה:

  • לחתום על EXE / DLL / MSI / MSIX
  • לחתום לא רק על ההתקנה אלא גם על בינאריים נלווים שמשמשים ל-update
  • להוסיף timestamp
  • לכלול בתהליך ה-release את פקיעת תוקף ה-certificate ואת הליך החידוש

חתימה שאין לה timestamp נוטה לגרום לקושי באימות אחרי פקיעת תוקף ה-certificate. במקום “חתמנו וזהו”, יציב יותר לכלול בהליך ה-release חתימה + timestamp יחד.

חתימה ו-timestampתרשים שמראה שחתימה בלי timestamp גורמת לקושי באימות אחרי פקיעת תוקף ה-certificate, ושחתימה יחד עם timestamp שנכללת בהליך ה-release יציבה יותר.חתימה בלבדקושי באימות אחרי פקיעת תוקף ה-certificateחתימה + timestampפחות קושי באימות אחרי הפקיעהנכלל בהליך ה-release

איור 8: לא מסתפקים בחתימה בלבד — כוללים גם timestamp בהליך ה-release.

אם משתמשים ב-MSIX, חתימת החבילה היא הנחת יסוד. גם בהפצת MSI / EXE, כדאי לפחות לחתום על גוף ה-installer ועל הבינאריים המרכזיים שרצים.

3.4. קיבוע מסלול ה-update והוספת זיהוי שיבוש

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

מסלול ה-update נמצא בשימוש הארוך ביותרתרשים שמראה שמסלול ה-update נמצא בשימוש ממושך יותר מההתקנה הראשונית, ושאם הוא נעשה ברשלנות ה-updater נהיה החוליה החלשה ביותר.בשימוש רק פעם אחתבשימוש ממושך לאורך זמןההתקנה הראשוניתlifetime של האפליקציהמסלול ה-updateברשלנות הוא נהיה החוליה החלשה

איור 9: מסלול ה-update נמצא בשימוש ארוך יותר מההתקנה הראשונית, וברשלנות הוא נהיה החוליה החלשה.

סביב ה-update, כדאי לחשוב מראש לפחות על חמישה דברים:

  • קבלת קובץ ה-update תמיד דרך HTTPS
  • אימות חתימה או hash של קובץ ה-update שהתקבל
  • מניעת החלפה חופשית של כתובת ה-URL של מקור ה-update בקוד או ב-Configuration
  • חתימה גם על מודול ה-update עצמו
  • קביעת נוהל rollback או שחזור במקרה כשלון

אם אפשר לבחור ב-MSIX + App Installer, קל יותר לרכז את מנגנון ה-update קרוב יותר ל-OS. מצד שני, אם יש updater עצמאי, יש לוודא גם את בטיחות התקשורת וגם את האמיתות של קובץ ההפצה. HTTPS לבדו שומר על “ערוץ התקשורת”, אבל לא מבטיח “האם הקובץ הזה באמת פרסום שלנו”.

שני הדברים שמאמתים ב-updateתרשים שמראה שב-updater עצמאי צריך לוודא גם את בטיחות התקשורת דרך HTTPS וגם את האמיתות של קובץ ה-update דרך חתימה או hash.קבלת קובץ ה-update ב-HTTPSאימות חתימה או hashהחלה רק אחרי מעבר האימותHTTPS שומר רק על ערוץ התקשורתבדיקה האם זה פרסום שלנו

איור 10: גם אם מקבלים ב-HTTPS, האמיתות היא שאלה נפרדת — מיישמים רק אחרי מעבר אימות חתימה או hash.

3.5. לא להחזיק secrets בקוד המקור או ב-Configuration ב-plaintext

זו נקודה שקל מאוד להיכשל בה בעבודה מעשית. בטענה “זה כלי פנימי בארגון” או “בכל מקרה רק מפיצים exe”, נוטים להשאיר connection string, API key, credentials לתיקייה משותפת ו-token קבוע בקוד המקור או בקובץ Configuration.

לכל הפחות, כדאי להימנע מהצבות כאלה:

  • API key שכתוב ישירות בקוד המקור
  • סיסמה ב-plaintext ב-appsettings.json או ב-app.config
  • connection string שנכנסה ל-repository
  • תכנון שבו מפתח הפענוח וה-ciphertext נמצאים באותו מקום
  • credentials קבועים ומשותפים לכל המשתמשים, במקום per-user

באופן מעשי, באפליקציית Windows יש בעיקר ארבע אפשרויות:

  • רוצים לשמור Windows credentials באפליקציית packaged desktop / משפחת WinUI, לשקול Credential Locker
  • רוצים לשמור secret מוצפן מקומית ב-Win32 / .NET, להשתמש ב-DPAPI / ProtectedData
  • היעד תומך ב-Windows authentication או integrated authentication אם אפשר, לא להחזיק סיסמה באפליקציה כלל
  • ניהול ה-secret אפשרי בענן או בצד השרת להעדיף תכנון שלא מטמיע secret ארוך-טווח בצד הלקוח

ב-C#, לפחות שימוש ב-DPAPI כמו להלן כבר עדיף בהרבה משמירה ב-plaintext. אם כותבים גם את השמירה וגם את הקריאה בהלוך-חזור, זה נראה כך:

// C# / .NET 8. ‏ProtectedData מיועד ל-Windows בלבד, וב-‏.NET
// דרוש חבילת NuGet בשם System.Security.Cryptography.ProtectedData.
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

public static class SecretStore
{
    // גם בפענוח נדרש אותו ערך. אפשר גם null, אך יותר בטוח להוסיף ערך.
    private static readonly byte[] Entropy = [0x4b, 0x53, 0x2d, 0x76, 0x31];

    public static void Save(string path, string secretText)
    {
        byte[] plaintext = Encoding.UTF8.GetBytes(secretText);

        byte[] ciphertext = ProtectedData.Protect(
            plaintext,
            Entropy,
            DataProtectionScope.CurrentUser);

        // אפשר גם לשמור את מערך הבייטים ישירות לקובץ, אך אם מכניסים לקובץ תצורה, עדיף Base64.
        File.WriteAllText(path, Convert.ToBase64String(ciphertext));

        CryptographicOperations.ZeroMemory(plaintext);
    }

    public static string Load(string path)
    {
        byte[] ciphertext = Convert.FromBase64String(File.ReadAllText(path));

        // אם לא אותו משתמש ואותו entropy כמו בזמן השמירה, יתקבל CryptographicException.
        byte[] plaintext = ProtectedData.Unprotect(
            ciphertext,
            Entropy,
            DataProtectionScope.CurrentUser);

        try
        {
            return Encoding.UTF8.GetString(plaintext);
        }
        finally
        {
            CryptographicOperations.ZeroMemory(plaintext);
        }
    }
}

צד הקריאה נראה כך:

string path = Path.Combine(
    Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
    "SampleApp",
    "token.dat");

Directory.CreateDirectory(Path.GetDirectoryName(path)!);

SecretStore.Save(path, "example-api-key");
string restored = SecretStore.Load(path);

מה שחשוב כאן הוא לא “הצפנו אז זה בטוח”, אלא לקבוע בתכנון מי יכול לפענח. יש הבדל משמעותי בין CurrentUser ל-LocalMachine. CurrentUser מאפשר פענוח רק למשתמש שביצע את השמירה, ו-LocalMachine מאפשר לכל מי שנמצא באותה מכונה. אם ה-service רץ תחת חשבון נפרד, או שיש תפעול שמחליף בין משתמשים, חשוב לקבוע את זה מראש — אחרת ייתקעו אחר כך במצב של “לא ניתן לקרוא” או “ניתן לקרוא יותר מדי”.

מי יכול לפענח ב-DPAPIתרשים שמראה שהגדרת ה-scope של DPAPI ל-CurrentUser מאפשרת פענוח רק למשתמש השומר, ואילו LocalMachine מאפשר פענוח לכל מי שבמכונה, ולכן צריך לקבוע זאת מראש בתכנון.CurrentUserLocalMachineקביעה בתכנון: מי יכול לפענחרק המשתמש השומר יכול לפענחכל מי שבמכונה יכול לפענחבלי קביעה מראש — תקיעה מאוחר יותר

איור 11: ב-DPAPI, בחירת ה-scope קובעת מי יכול לפענח, ולכן קובעים זאת מראש.

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

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

איור 12: מפתח ה-DPAPI נמצא בפרופיל המשתמש, וכשהפרופיל לא טעון לא ניתן לפענח.

בחיבור ל-SQL Server, בסביבה מקומית לפעמים אפשר להעדיף Windows authentication כברירה ראשונה. אם בכל זאת חייבים לכלול credentials ב-connection string, לפחות כדאי לשמור על Persist Security Info=False ולא להשאיר אותם בקובץ Configuration ב-plaintext.

3.6. תקשורת מבוססת HTTPS, ולא לבטל אימות certificates

פרצה שהוכנסה “רק לזמן הפיתוח” נשארת כמות שהיא בפרודקשן. תקלות תקשורת הן בדרך כלל בדיוק הדפוס הזה.

במיוחד קוד ו-Configuration מהסוג הבא נוטים להישאר במוצר הסופי:

  • ServicePointManager.ServerCertificateValidationCallback += ... => true
  • HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
  • הפצה עם בדיקת revocation של certificates מבוטלת
  • קוד שמניח certificate עצמי-חתום לפיתוח, שנשאר בפרודקשן

המדיניות המינימלית פשוטה:

  • תקשורת בפרודקשן היא HTTPS
  • לא לדלג באופן קבוע על אימות certificates
  • אם נדרשת הקלה חריגה באימות, להגביל אותה ל-host ול-certificate ספציפיים
  • להסיר קוד עקיפה של פיתוח בוודאות דרך תנאי build או Configuration
  • ב-.NET, לשים לב גם לבדיקת revocation

הדוגמה השגויה הטיפוסית נראית כך:

ServicePointManager.ServerCertificateValidationCallback +=
    (_, _, _, _) => true;

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

הכתיבה משתנה לפי גרסת .NET

הצבת הגדרה גלובלית ב-ServicePointManager היא סגנון כתיבה מתקופת .NET Framework. בקוד חדש, טבעי יותר לקבל HttpClient מ-IHttpClientFactory, ואם צריך הגדרה סביב TLS, להחזיק אותה בצד SocketsHttpHandler או HttpClientHandler.

עם זאת, מסוכן להזניח בטענה “זה API ישן שכבר לא משפיע”. בתיעוד של Microsoft כתוב ש-ServicePointManager.ServerCertificateValidationCallback מ-.NET 9 ואילך ממופה אל RemoteCertificateValidationCallback של SocketsHttpHandler.SslOptions. כלומר, שורה בודדת של => true איפשהו עלולה להשפיע גם על התקשורת של HttpClient.

המסלול שבו callback ישן משפיע על תקשורת של היוםתרשים שמראה שה-ServerCertificateValidationCallback של ServicePointManager ממופה מ-.NET 9 ואילך לאימות של SocketsHttpHandler, ולכן שורה בודדת שמחזירה true עלולה להשפיע גם על תקשורת HttpClient.ממופה מ-.NET 9 ואילךcallback האימות של ServicePointManagerהאימות בצד SocketsHttpHandlerמשפיע גם על תקשורת HttpClientשורה בודדת שמחזירה true עלולה לחלל את כל התקשורת

איור 13: דילוג אימות שהוצב ב-API ישן משפיע, דרך המיפוי, גם על תקשורת HttpClient של היום.

כשרוצים להקל חריגית על האימות, לא כדאי הגדרה גלובלית שמשפיעה על כל ה-process, אלא צורה שמוגבלת רק ל-handler הספציפי.

// C# / .NET 8. דוגמה שמטפלת חריגתית רק במארח ואישור ספציפיים.
// גם אם מקלים לצורך פיתוח, בלי להגביל את היעד זה כמו לבטל את האימות גלובלית.
using System;
using System.Linq;
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

// טביעת האצבע של האישור המצופה. אפשר גם לקרוא מתצורה.
const string ExpectedThumbprint = "2B0C4E6A8D1F3B5D7F9A1C3E5A7C9E1B3D5F7A91";

var handler = new HttpClientHandler
{
    CheckCertificateRevocationList = true,   // בודקים גם ביטול. ברירת המחדל היא false
    ServerCertificateCustomValidationCallback = (request, certificate, chain, errors) =>
    {
        if (errors == SslPolicyErrors.None)
        {
            return true;
        }

        // מקבלים רק את המקרה שבו "המכשיר הזה לא בוטח ב-CA הפנים-ארגוני".
        // אישור שלא הגיע או חוסר התאמה בשם מארח — לא מקבלים
        if (errors != SslPolicyErrors.RemoteCertificateChainErrors)
        {
            return false;
        }

        // מקבעים את הצד השני ואת האישור
        if (request.RequestUri?.Host != "device.internal.example"
            || certificate is null
            || !string.Equals(certificate.Thumbprint, ExpectedThumbprint,
                              StringComparison.OrdinalIgnoreCase))
        {
            return false;
        }

        // גם באישור המוצמד לא מקבלים פקיעת תוקף או ביטול.
        // ברגע שמקבלים זאת, נפתח מסלול שממשיך להשתמש ב"אישור שבוטל אחרי דליפת מפתח"
        return chain is not null
            && chain.ChainStatus.All(s =>
                   s.Status is X509ChainStatusFlags.NoError
                            or X509ChainStatusFlags.UntrustedRoot
                            or X509ChainStatusFlags.PartialChain);
    },
};

using var client = new HttpClient(handler);

את ExpectedThumbprint נותנים כקבוע או מתוך Configuration, לפי ה-thumbprint של ה-certificate הרלוונטי.

החשוב כאן הוא לא “להתעלם” מ-errors ומ-chain.ChainStatus. אם מחזירים true רק בגלל שה-thumbprint תואם, זה ימשיך לעבור גם אם ה-certificate פג תוקף, וגם אחרי שהמפתח דלף וגרם ל-revocation. certificate pinning פירושו “מאמינים רק לעותק הזה”, לא “מאמינים לעותק הזה בכל מצב”. בקוד שלמעלה, מה שמותר הוא רק UntrustedRoot ו-PartialChain (כלומר ה-CA הפנימי בארגון לא מותקן במכשיר הזה), בעוד ש-NotTimeValid (פג תוקף) ו-Revoked (בוטל) נדחים ישירות.

כדי לבדוק revocation נדרש CheckCertificateRevocationList = true (ברירת המחדל היא false, ואז ה-revocation לא נבדק). מצד שני, אם ה-CA הפנימי בארגון לא מפרסם לא CRL ולא OCSP, ייחסם עם RevocationStatusUnknown. זו התנהגות נכונה. אם אין אמצעי לבדוק revocation, יש למלא את הפער באחת משתי דרכים: לקצר את תוקף ה-certificate, או להכין מראש מסלול שמאפשר להפיץ מחדש את הערך המוצמד. המצב המסוכן ביותר הוא “pinning של certificate לטווח ארוך בלי אפשרות לבטל אותו”.

זרימת ההחלטה בעת certificate pinningתרשים שמראה שכשאין שגיאה מקבלים, שגיאה שאינה שגיאת שרשרת נדחית, ומתוך שגיאת שרשרת בלבד מאמתים hostname ו-thumbprint, ואף מצב שרשרת של פקיעת תוקף או revocation נדחה.Noneשגיאה שאינה שגיאת שרשרתרק שגיאת שרשרתחוסר התאמההתאמהפג תוקף / בוטל וכדומהרק CA פנימי בארגון לא מותקןבדיקת errorsמקבליםדוחיםהתאמת hostname ו-thumbprintבדיקת מצב השרשרת

איור 14: גם ב-certificate pinning לא מתעלמים — זרימת ההחלטה דוחה ישירות פקיעת תוקף ו-revocation.

3.7. להתייחס לכל קלט חיצוני כ”קלט לא מהימן”

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

  • נתיבי קבצים
  • CSV / Excel / JSON / XML
  • command-line arguments
  • named pipe / socket / COM / RPC / gRPC
  • מחרוזת שמועברת ל-DB
  • ערכי Registry
  • clipboard
  • URL / deep link
  • נתונים שחוזרים ממכשיר חיצוני או מ-SDK

בפרט, שלושה דברים שלא כדאי לפספס:

  1. תמיד לפרמט SQL לא לבנות SQL בשרשור מחרוזות.
  2. לנרמל נתיב קובץ לפני שימוש לא להשתמש בנתיב שהוזן על ידי המשתמש ישירות למחיקה, דריסה או חילוץ.
  3. להוסיף מגבלת גודל ובדיקת פורמט בקריאת קובץ חיצוני “נפתח בהצלחה” לא אומר “בטוח”.

בדוגמת SQL, זה מה שכדאי להימנע ממנו:

var sql = "SELECT * FROM Users WHERE Name = '" + userName + "'";

גם ברף המינימלי, כדאי להתקרב לזה:

using System.Data;
using Microsoft.Data.SqlClient;

using var cmd = connection.CreateCommand();
cmd.CommandText = "SELECT * FROM Users WHERE Name = @name";
cmd.Parameters.Add("@name", SqlDbType.NVarChar, 256).Value = userName;

ההנחה “כלי פנימי בארגון, אז הקלט מהימן” היא הנחת יסוד מסוכנת למדי. בפועל, CSV שבור, שמות קבצים בלתי צפויים, נתוני DB ישנים, טעויות הקלדה בתפעול, ו-JSON חצי-מוגמר שנכתב על ידי כלי אחר — כל אלה נכנסים כרגיל.

גם לכלי פנימי בארגון מגיע קלט שבורתרשים שמראה שגם בכלי פנימי בארגון נכנס באופן שגרתי CSV שבור, שם קובץ בלתי צפוי, נתוני DB ישנים, טעויות הקלדה ו-JSON חצי-מוגמר, ולכן מתייחסים לכל קלט חיצוני כלא מהימן.CSV שבורקלט אל האפליקציהשם קובץ בלתי צפויטעות הקלדה / נתונים ישניםvalidation כקלט לא מהימן, בכל נקודת כניסה

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

3.8. לא להשאיר את מקור טעינת ה-DLL עמום

זו מלכודת אופיינית ל-Windows. כשטוענים DLL רק לפי שם, כמו LoadLibrary("foo.dll"), ייתכן שבהתאם לסדר החיפוש ייטען DLL ממקום לא רצוי.

מה שצריך לעשות ידוע מראש:

  • כשאפשר, לציין נתיב מוחלט ל-DLL
  • להגדיר בשלב מוקדם את SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
  • להוסיף במפורש יעדי חיפוש עם AddDllDirectory
  • להימנע מתכנון שמעביר את תוצאת SearchPath ישירות ל-LoadLibrary
  • לא להסתמך לחלוטין על safe DLL search mode

לדוגמה, ב-native code, תכנון שמכניס את הבא בשלב מוקדם של אתחול ה-process הוא אפשרות טובה:

SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);

ואז רושמים עם AddDllDirectory רק את תיקיות החיפוש הנוספות הנדרשות.

זה נוטה להישאר בלי טיפול כי “בדרך כלל זה עובד”, אבל אם ספריית העבודה משתנה ביעד ההפצה, או ש-DLL של מוצר אחר נכנס ל-PATH, זה נשבר בשקט. זה עוזר לא רק לאבטחה, אלא גם למניעת תקלות.

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

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

3.9. לא לחשוף secrets בלוגים וב-exceptions

חשוב להגדיל את הלוגים לצורך חקירת תקלות. אבל הלוג עצמו נוטה גם להיות בית קברות ל-secrets.

סביב הלוגים, כדאי לבחון מחדש לפחות את אלה:

  • לא להוציא ללוג סיסמאות, Bearer token, API keys
  • לא להוציא connection string מלאה
  • למסך מידע אישי ותוכן נתונים עסקי
  • להפריד בין פרטי exception למשתמש לבין לוג פנימי
  • לא להפעיל בפרודקשן לוג PII שמיועד ל-debug
  • לבדוק מחדש הרשאות מיקום שמירה של dump או trace

ב-.NET העדכני, קל יותר גם לסדר את הנושא סביב redaction. לפחות כדאי לוותר על “מייצרים מחרוזת מכל דבר ורושמים כמו שהוא ללוג”.

כמה כשלים אופייניים:

  • שמירת גוף request/response של HTTP במלואו
  • הוצאת token או כל ה-headers בעת כשלון authentication
  • הצגת הודעת exception ישירות ב-MessageBox
  • כריכת לוגים רגישים במלואם בקובץ ZIP לתחזוקה

תצוגת שגיאה מתחלקת, למשל, כך:

  • למשתמש: “החיבור לשרת נכשל. בדקו את הגדרות הרשת ואת ה-URL.”
  • ללוג פנימי: ה-host שנכשל, סוג שגיאת TLS, correlation ID, stack trace, מספר ניסיונות חוזרים

רק ההפרדה הזו כבר משפרת משמעותית את האיזון בין דליפת מידע ליכולת חקירה.

הפרדת תצוגת השגיאהתרשים שמראה שכשמתרחשת שגיאה, למשתמש מוצגת רק הנחיה תמציתית, ואילו ללוג הפנימי נשמר מידע חקירתי כמו ה-host שנכשל, סוג השגיאה, correlation ID ו-stack trace.התרחשות שגיאהלמשתמש: הנחיה תמציתית בלבדלוג פנימי: host, סוג, correlation ID וכדומהשמירה על יכולת חקירה בלי לדלוף מידע

איור 17: רק הפרדה בין תצוגה למשתמש ללוג פנימי כבר משפרת את האיזון בין דליפה ליכולת חקירה.

3.10. לא להזניח ספריות תלויות וכלי פיתוח

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

מספר הפריטים לבדיקה עצמו לא גדול.

  • לשמור על SDK / runtime של .NET בגרסה נתמכת
  • לבדוק תקופתית עדכוני תלויות NuGet / OSS
  • ב-C++, לנהל גרסאות של רכיבי הפצה חוזרת של runtime ו-DLL חיצוניים
  • לכלול בדיקת מידע פגיעויות בבדיקה שלפני release
  • להכין smoke test שלא ייהרס בעדכון תלויות

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

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

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

3.11. איך בודקים כל פריט

Checklist מתפקד רק כשהוא מלווה בשיטת אימות. עבור הפריטים בסעיפים 3.2 עד 3.10, הנה שיטות שאפשר להפעיל בפועל לפני release.

מה רוצים לבדוק שיטת בדיקה
האם מבקשים elevation שלא לצורך לבדוק במניפסט של האפליקציה את הערך של requestedExecutionLevel. במקור — app.manifest, בקובץ ההפצה — לבדוק עם Sigcheck של Sysinternals או בעורך משאבים
חתימה ו-timestamp להריץ ב-PowerShell את Get-AuthenticodeSignature .\app.exe ולבדוק שה-Status הוא Valid ושיש TimeStamperCertificate. לבדוק אחד-אחד גם DLL נלווה וגם updater
האם ביטלנו אימות certificates לחפש בכל קוד המקור את ServerCertificateValidationCallback, DangerousAcceptAnyServerCertificateValidator, ServerCertificateCustomValidationCallback, CheckCertificateRevocationList
כתיבה ישירה של secrets לחפש Password=, ApiKey, Secret, Token, ConnectionString. גם קוד נוכחי וגם היסטוריית ה-repository
בניית SQL לחפש שרשור מחרוזות שמכיל "SELECT, "INSERT, + , ולבדוק שהוא עובר דרך Parameters.Add
מקור טעינת ה-DLL ב-Process Monitor, לצמצם ל-process הרלוונטי, ולסנן Path ends with .dll ו-Result is NAME NOT FOUND. כך רואים לאן ובאיזה סדר חיפשו, ובודקים שלא נצפו תיקיות בלתי רצויות
פגיעויות ידועות בתלויות להריץ dotnet list package --vulnerable --include-transitive
האם secrets מופיעים בלוג להריץ פעם אחת, ולחפש בלוג שיוצא Bearer , Password, Authorization

חיפוש קוד יכול להתבצע עם rg (ripgrep) או עם חיפוש של Visual Studio. להרצה מרוכזת, זו הצורה:

Get-ChildItem -Recurse -Include *.cs,*.vb,*.cpp,*.h,*.config,*.json |
    Select-String -Pattern 'ServerCertificateValidationCallback|DangerousAcceptAnyServerCertificateValidator|Password=|ApiKey' |
    Select-Object Path, LineNumber, Line

חשוב לתעד את עצם הבדיקה. אם משאירים תיעוד גם של “בוצע חיפוש” וגם של “0 תוצאות”, ב-release הבא צריך לבדוק רק את ההפרש.

התועלת בתיעוד הבדיקהתרשים שמראה שכשמתעדים בדיקה שלפני release גם כאשר התוצאה היא 0 תוצאות, ב-release הבא צריך לבדוק רק את ההפרש.ביצוע בדיקה לפני releaseתיעוד: בוצע חיפוש · 0 תוצאותב-release הבא מספיק לבדוק רק הפרש

איור 19: תיעוד עצם “הבדיקה שבוצעה” חוסך ב-release הבא — מספיק לבדוק את ההפרש.

4. Checklist שלפני release

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

4.1. הרשאות, אופן הרצה

פריט לבדיקה בדיקה הערה
ההרצה הרגילה פועלת ב-asInvoker □  
עבודה שדורשת הרשאות Administrator מופרדת ל-EXE נפרד / Windows Service וכדומה □  
אם משתמשים ב-service, חשבון ההרצה אינו חזק מהנדרש □  
האחריות מופרדת בין %ProgramFiles% לנתוני משתמש □  

4.2. הפצה, חתימה

פריט לבדיקה בדיקה הערה
EXE / DLL / MSI / MSIX / updater חתומים □  
החתימה כוללת timestamp □  
פקיעת תוקף ה-certificate והליך החידוש נכללים בזרימת ה-release □  
נקבעה שיטה לאימות hash ולזיהוי שיבוש בקובצי ההפצה □  

4.3. update

פריט לבדיקה בדיקה הערה
קבלת ה-update מתבצעת ב-HTTPS □  
אחרי הורדה מתבצע אימות חתימה או hash □  
התכנון מקשה על החלפה שרירותית של URL מקור ה-update □  
קיימת מדיניות rollback או ניסיון חוזר במקרה כשלון update □  

4.4. secrets

פריט לבדיקה בדיקה הערה
סיסמה, API key, connection string אינם כתובים ישירות בקוד המקור □  
לא נשמר secret בקובץ Configuration ב-plaintext □  
secret שנדרש לשמירה מקומית מוגן ב-DPAPI / Credential Locker וכדומה □  
היכן שאפשר, נעשה שימוש ב-Windows authentication או ב-credentials של המשתמש □  

4.5. תקשורת

פריט לבדיקה בדיקה הערה
תקשורת בפרודקשן משתמשת ב-HTTPS □  
DangerousAcceptAnyServerCertificateValidator או => true לא נשארו בקובץ ההפצה □  
בדיקת revocation ואימות hostname נלקחים בחשבון □  
קוד או Configuration שמניחים certificate פיתוח לא הוזנחו בפרודקשן □  

4.6. קלט, גישה לנתונים

פריט לבדיקה בדיקה הערה
SQL מפורמט □  
לקלט מ-command-line, קובץ, IPC, URI וכדומה יש מגבלה ובדיקת פורמט □  
טיפול בנתיבים מנורמל ומונע חריגה מהשורש □  
הודעת exception לא מוצגת ישירות למסך □  

4.7. DLL וסביבת הרצה

פריט לבדיקה בדיקה הערה
מקור טעינת ה-DLL מוגדר במפורש □  
סדר החיפוש נשלט עם SetDefaultDllDirectories / AddDllDirectory וכדומה □  
טעינת DLL לא מסתמכת על הספרייה הנוכחית או על PATH □  
ידוע אילו קבצים דרושים לטעינה דינמית ביעד ההפצה □  

4.8. לוגים, תפעול

פריט לבדיקה בדיקה הערה
tokens, סיסמאות, PII אינם מופיעים בלוג □  
לוג פנימי מופרד מהודעה למשתמש □  
הרשאות מיקום שמירה של dump / trace / log נבדקו מחדש □  
מצב העדכון של SDK וספריות תלויות נבדק □  

5. הנחות שגויות נפוצות

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

5.1. “זה כלי פנימי בארגון אז זה בסדר”

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

5.2. “זה HTTPS אז זה בטוח”

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

5.3. “הצפנו אז זה בטוח”

בלי לסגור את מיקום מפתח הפענוח, הרשאת הפענוח, גבול המשתמש וגבול המכונה, ההצפנה לבדה לא מספיקה. בפרט, אם משתמשים בערך שמוגן ב-LocalMachine בהנחה שהוא “secret per-user”, זה יגרום בהמשך לבלבול.

5.4. “אם נגדיל את הלוגים נוכל לחקור”

אם הלוגים רבים אבל tokens ומידע אישי זולגים בהם, זו כשלעצמה תקרית. אם רוצים יכולת חקירה, קודם כדאי לקבוע מה נשמר ומה מוסתר.

5.5. “אם נריץ כ-Administrator זה ייפתר”

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

הסוף של "אם נריץ כ-Administrator זה ייפתר"תרשים שמראה שהרצה קבועה בהרשאות Administrator נוחה בהתחלה אך מקשה בהמשך סביב UAC, הפצה, תמיכה, גבולות הרשאה, טעינת DLL ומיקום שמירת קבצים, ושהרשאה מינימלית יציבה יותר לטווח הארוך.יציבה יותר לטווח הארוךאם נריץ כ-Administrator זה ייפתרנוח בהתחלהמקשה סביב UAC, הפצה ותמיכהמקשה סביב גבולות הרשאה ומיקום שמירההרצה בהרשאה מינימליתנמנעים מהקושי

איור 20: הנוחות בהרצה קבועה כ-Administrator היא רק בהתחלה, ולטווח הארוך הרשאה מינימלית יציבה יותר.

6. סדר עדיפויות כללי

אם קשה לעשות הכול בבת אחת, סדר העדיפויות נראה בערך כך.

הבסיס לסידור הוא מכפלה בין גודל הנזק במקרה תקרית לבין נמוכות עלות התיקון. פרק 3 מסודר לפי זרימת התכנון “הרשאות ← הפצה ← מימוש ← תפעול”, אבל כאן הסדר הוא “מהמסוכן ביותר”, ולכן הוא לא זהה לסדר הפרקים. מצורף הסעיף המתאים לכל פריט.

  1. בחינה מחדש של הרשאות Administrator (3.2) קודם כל, להפסיק את השימוש הקבוע ב-requireAdministrator. היקף הנזק משתנה בצעד אחד, בעוד שינוי התכנון עצמו לרוב קטן.
  2. חתימה ו-timestamp (3.3) לסגור את אמינות קבצי ההפצה. מספיק לשלב בהליך, ואם נדחה לאחר מכן נדרשת הפצה מחדש.
  3. הוצאת secrets (3.5) להוציא secret מקוד המקור ומ-Configuration ב-plaintext. הנזק בעת דליפה גדול, ואחרי הדליפה אין דרך חזרה.
  4. תיקון HTTPS + אימות certificates (3.6) להסיר מקובצי ההפצה סוגים של => true. לרוב מספיקה הסרה בלבד, ובהזנחה כל התקשורת בלתי מהימנה.
  5. בחינה מחדש של קלט SQL / קובץ / IPC (3.7) לצמצם שרשור מחרוזות וקלט לא מאומת. הכמות רבה, אך אפשר לתקן מקום אחד בכל פעם.
  6. קיבוע טעינת DLL (3.8) להפסיק טעינה לפי שם בלבד והסתמכות על PATH. זה תיקון של חלק מתהליך ההפעלה, ומועיל גם למניעת תקלות.
  7. מיסוך לוגים (3.9) לוודא שהלוג לא נהיה אסון משני בעת תקרית. יש הרבה מקומות פלט, ולכן זה לוקח זמן.
  8. קביעות של עדכון תלויות (3.10) להפוך לתהליך קבוע שנבדק בכל release. לא נגמר בפעם אחת — העבודה היא להפוך את זה למנגנון.

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

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

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

בסדר הזה, נוח להתקדם במובן של “קודם לסגור את החורים שברור שהם מסוכנים”.

7. סיכום

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

הרף המינימלי, במשפט אחד לכל נקודה:

  • לא להריץ את כל האפליקציה בהרשאות Administrator
  • לחתום על קבצי ההפצה וה-update, כולל timestamp
  • לא להחזיק secrets בקוד המקור או ב-Configuration ב-plaintext
  • גם עם HTTPS, לא לבטל אימות certificates
  • לא לתת אמון בקלט חיצוני כמו SQL, קובץ, IPC וכדומה
  • לא להשאיר את מקור טעינת ה-DLL עמום
  • לא לחשוף secrets בלוגים
  • לא להזניח ספריות תלויות

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

8. מקורות

מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.

העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.

המאמר קשור ישירות לשירותים הבאים.

שאלות נפוצות

שאלות נפוצות בפניות בנושא המאמר.

מה כדאי לבדוק ראשון באבטחת אפליקציות Windows?
ארבעה דברים: לא לדרוש הרשאות Administrator שלא לצורך, לחתום על הקוד, לא להחזיק secrets ב-plaintext, ולא לבטל אימות certificates. אבטחה מינימלית אינה הוספת features מיוחדים אלא הימנעות מהשארת התנהגות default מסוכנת או מימוש רשלני. דילוג קבוע על אימות certificates, connection strings ב-plaintext, טעינת DLL שמסתמכת על הספרייה הנוכחית, והרצת SQL בשרשור מחרוזות — אלה דברים שכדאי להימנע מהם גם ברף המינימלי.
אסור להריץ את כל האפליקציה בהרשאות Administrator?
כדאי להימנע מכך. הרצת כל האפליקציה בהרשאות Administrator גורמת לכך שבאגים, החלפת DLL, קריאה שגויה של קבצי Configuration ופגמים בקלט חיצוני רצים ישירות בהרשאה חזקה. המדיניות הבסיסית היא שאפליקציית UI רגילה רצה ב-asInvoker, שרק ה-processes שדורשים הרשאות Administrator מופרדים ל-process נפרד או ל-Windows Service, ושההעלאה בהרשאה מתבצעת רק ברגע הנדרש.
איפה כדאי לשמור API keys ו-connection strings?
קודם יוצאים ממצב שבו הם יושבים ב-plaintext בקוד המקור או בקבצי Configuration כמו appsettings.json. secrets שנשמרים מקומית משתמשים לפי הצורך ב-DPAPI / ProtectedData או ב-Credential Locker. חשוב גם למסך בלוגים tokens, סיסמאות, connection strings ומידע אישי, כי אחרת הלוג עצמו נהיה גורם התקרית.
אפליקציה שמופצת פנימית בארגון גם צריכה code signing?
כדאי להתייחס לזה כהנחת יסוד. באפליקציות Windows, קבצי ההפצה עצמם (EXE / DLL / MSI / MSIX / מודול update אוטומטי) הם משטח תקיפה. code signing עם timestamp נותן זיהוי שיבוש, אמינות בעיני המשתמשים, ונוחות בהסבר תפעולי. גם מנגנון ה-update צריך לקבע את מקור ה-update, ולזהות שיבוש דרך HTTPS ואימות חתימה.

פרופיל הכותב

עמוד היכרות עם כותב המאמר.

Go Komura

מנהל KomuraSoft LLC

מתמחה בפיתוח תוכנה עבור Windows, ייעוץ טכני וחקירת תקלות, בעיקר בפרויקטים עם מערכות קיימות ובאגים שקשה לשחזר.

קישורים ציבוריים

חזרה לבלוג