"אותו מחשב" אינו אותה סביבת הרצה — גבול המשתמש שמפריד AppData, HKCU, DPAPI ו-Credentials

· עודכן בתאריך: · · Windows, DPAPI, Registry, AppData, Credentials, Task Scheduler

היסטוריית עדכונים (גרסה ראשונה, פורסמה בתאריך 28 Aug 2026)
פרסום ראשון

האפליקציה קוראת את קובץ התצורה כשמפעילים מ-File Explorer, אבל Task Scheduler מדווח שהוא “לא נמצא”. הופכים אותה לשירות והיא כבר לא יכולה לעשות decrypt לסיסמה השמורה. הדפדפן על השולחן מחובר, אבל ב-CI הוא חוזר למסך ה-login.

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

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

תסמין מה להשוות קודם איפה לקרוא
קובץ התצורה או פקודה לא נמצאים משתמש ההרצה, נתיב AppData בפועל, משתני סביבה של המשתמש AppData ומשתני סביבה
ערך Registry שבטוחים שכתבתם חסר ה-hive של המשתמש ש-HKCU מצביע אליו HKCU
אפשר לקרוא את הקובץ, אבל אי אפשר לעשות decrypt ל-secret ה-scope של DPAPI ובעל ה-master key DPAPI
מצב ה-sign-in של הדפדפן לא עובר משתמש ההרצה, profile, המפתח המוגן ב-DPAPI profiles של דפדפן
credentials או תעודות שמורות לא ניתנים לשימוש הכספת ו-Certificate Store של חשבון ההרצה, סוג ה-logon של המשימה Credentials ותעודות
כונן Z: נעלם כשרצים כ-administrator ה-token ו-logon session לפני ואחרי elevation העלאת UAC
רוצים בדיקה מלאה לפני מעבר התלויות וההגדרה לכל צורת הרצה רשימת בדיקה לפי צורת הרצה, הליך חקירה

הנחות המאמר

פריט תוכן
קהל היעד מפתחים שהופכים אפליקציות עסקיות לשירותים, למשימות מתוזמנות או ל-jobs ב-CI, ואנשי תפעול שחוקרים בעיות “עובד אצלי”
סביבת בסיס Windows 10/11. קוד האימות רץ ב-PowerShell 5.1 ואילך
רמת קושי בינונית

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

יחידת הבסיס שמחלקת סביבת הרצה ב-Windows אינה המחשב אלא ה-access token וה-SID, כלומר “מי התהליך רץ כ.” AppData, HKCU, מפתחות DPAPI, profiles של דפדפן ו-credentials שייכים לסביבה של המשתמש הזה.

ההגדרות ומצב ה-sign-in שהכנתם תחת ה-logon האינטראקטיבי שלכם לא עוברים אוטומטית ל-SYSTEM או לחשבון שירות אחר. האזורים לנתונים ברמת המכונה הם ProgramData ו-HKLM, ושיתוף דרכם עדיין דורש לתכנן את הרשאות הגישה.

שני עולמות בתוך אותו מחשבאותו מחשב חולק רק HKLM ו-ProgramData, בעוד העולם של ה-SID שלכם והעולם של SID אחר לכל אחד יש AppData, hive של Registry ומפתחות DPAPI משלו, בלתי נראים זה לזה מעבר לגבול המשתמשגבול המשתמש: בלתי נראים זה לזהאותו מחשבמשותף: HKLM ו-ProgramDataהעולם של ה-SID שלכםהעולם של SID אחר (SYSTEM וכו')AppData, HKCU, מפתחות DPAPIAppData אחר, hive אחר, מפתחות אחרים

איור 1: באותו מחשב, משתמש הרצה אחר פירושו AppData אחר, Registry אחר ומפתחות אחרים.

עם זאת, אותו SID אינו מבטיח אותה סביבה. בודקים גם את סוג ה-logon של המשימה, את טעינת ה-profile ב-IIS, ואת ההבדל ב-logon session שיוצרת העלאת UAC. elevation בתוך אותו חשבון לא הופך את HKCU או את הכספת למשתמש אחר; מה שחשוב הוא לחשוב בנפרד אילו גבולות משתנים.

שאר המאמר מתקדם בסדר הזה: משתמש ההרצה כהנחה (פרק 2), חמשת הגבולות (פרקים 3 עד 7), הבדיקה לפי צורת הרצה (פרק 8), ותכנון וחקירה (פרק 9).

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

2. הנחה: “מי זה רץ כ” מחליט הכל

2.1 בודקים את ה-SID ואת ה-token, לא את שם המשתמש

המזהה ש-Windows משתמש בו כדי לזהות משתמש אינו שם המשתמש אלא ה-SID (security identifier). תהליך מחזיק access token, וה-token מכיל את ה-SID של משתמש ההרצה. ה-principal הזה הוא נקודת ההתחלה בכל פעם שחושבים על בדיקות ACL של קבצים, על ה-Registry הפיזי מאחורי alias, או על מפתחות הצפנה.

משתמשים ב-whoami /user לבדיקה הראשונה. מאשרים את התוצאה בסביבת ההרצה שבה הבעיה קורה, לא במסוף שלכם.

> whoami /user

USER INFORMATION
----------------

User Name       SID
=============== =============================================
desktop\you     S-1-5-21-3623811015-3361044348-30300820-1001

C:\Users\you הוא הצורה על הדיסק של ה-user profile הקשור ל-SID הזה. profile מורכב מקבוצת תיקיות כמו AppData ומ-hive ה-Registry של המשתמש NTUSER.DAT. כי הוא מועתק מ-profile ברירת המחדל ב-logon הראשון, profile של משתמש אחר מתחיל ממצב התחלתי שנפרד מהסביבה שהגדרתם.1

מנתיב ההפעלה עד ה-profileבין אם הופעל ב-double-click, ב-Task Scheduler או בשירות או IIS, התהליך מחזיק את ה-SID ב-access token שלו, וקבוצת נתוני ה-user profile הקשורה ל-SID הזה הופכת לסביבת ההרצה שלוdouble-clicktoken של התהליך (SID)Task Schedulerשירות או IISקבוצת profile הקשורה ל-SIDSID אחר פירושו profile אחר במצב התחלתי

איור 2: הנתיב שבו תהליך מופעל קובע את ה-profile של איזה SID הוא נושא בזמן הריצה.

2.2 לחשבונות שירות יש לכל אחד סביבה משלו

ל-Windows יש חשבונות מובנים שרצים בלי שבן אדם מתחבר. לכל אחד מהם יש סביבת הרצה עצמאית.2

חשבון SID לאן ה-profile וה-Registry מצביעים
SYSTEM (LocalSystem) S-1-5-18 profile תחת C:\Windows\System32\config\systemprofile. HKCU משויך למשתמש ברירת המחדל3
LocalService S-1-5-19 תחת C:\Windows\ServiceProfiles\LocalService. יש לו subkey משלו תחת HKEY_USERS4
NetworkService S-1-5-20 תחת C:\Windows\ServiceProfiles\NetworkService. כמו LocalService, יש לו profile ו-hive משלו4
IIS AppPool\<name> S-1-5-82-… זהות ייחודית ל-pool. ה-profile לא נטען כברירת מחדל5

ההבדל בין נתיבי ההפעלה של double-click, Task Scheduler, שירות ו-IIS הופך להבדל בסביבה של מי בשימוש. לא מניחים שההגדרות, המפתחות וה-credentials שהכנתם תחת ה-logon האינטראקטיבי שלכם קיימים גם ביעד ההרצה הזה.

2.3 שלושה תנאים לבדוק אחרי “אותו משתמש”

תנאי לבדוק דוגמה לאיפה ההבדל נראה
סוג logon עם S4U logon של משימה, הסיסמה לא נשמרת, והרשת ו-EFS אינם נגישים6
טעינת profile ב-IIS, loadUserProfile קובע אם AppData וה-hive של המשתמש זמינים7
logon session ו-elevation גם עם אותו SID, תהליך elevated עשוי לא לראות את כונני הרשת הממופים89

לא עוצרים בבדיקת ה-SID; משתמשים בשלושת אלה כדי לסדר מה שונה גם עם אותו חשבון. משימות מכוסות בפירוט בפרק 8, IIS בפרקים 4 ו-5, ו-elevation בפרק 7.

3. גבול 1: AppData — אותו משתנה סביבה מצביע למקום אחר

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

3.1 החלוקות של AppData ואיך משתמש ההרצה משנה אותן

AppData הוא תיקייה תחת ה-user profile. המטרות שלו מחולקות כך.10

אזור מטרה עיקרית
%APPDATA% (Roaming) הגדרות משתמש שצריכות לעקוב אחרי ה-profile כשהוא עושה roam
%LOCALAPPDATA% (Local) נתונים ו-caches מקומיים למכונה
AppData\LocalLow נתונים לתהליכים שרצים ב-integrity level נמוך

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

משתמש הרצה דוגמה לאיפה מחפשים את קובץ התצורה
משתמש A C:\Users\a\AppData\Roaming\MyApp
SYSTEM AppData תחת systemprofile

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

איך %APPDATA% נפתר תלוי במשתמשגם כשאותו קוד פותח %APPDATA%, הוא נפתר לנתיב תחת C:\Users אם משתמש ההרצה הוא אתם ותחת systemprofile אם הוא SYSTEM, וקובץ התצורה שלכם לא קיים באחרוןאתםSYSTEMאותו קוד: פותחים %APPDATA%משתמש ההרצה?C:\\Users\\you\\AppData\\RoamingAppData תחת systemprofileהקובץ ששמתם שם חסר

איור 3: משתני סביבה לא משקרים, אבל לאן הם נפתרים תלוי ב-token.

3.2 גם PATH שונה לפי משתמש

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

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

בוחרים את מיקום האחסון לפי הקוראיםנתונים שרק המשתמש הזה קורא הולכים ל-AppData או HKCU, נתונים שמשותפים לכל המשתמשים או לשירותים הולכים ל-ProgramData או HKLM, ולאחרונים מצטרף תכנון ACLרק המשתמש הזהכל המשתמשים, שירותיםמי קורא את הנתונים האלה?AppData, HKCUProgramData, HKLMשוקלים מחדש אם זה עשוי להפוך לשירותמתכננים הרשאות כתיבה ו-ACLs

איור 4: “אי אפשר לקרוא את זה ברגע שזה הפך לשירות” הוא התוצאה של דילוג על הענף הזה בזמן התכנון.

4. גבול 2: HKCU — “המשתמש הנוכחי” משתנה עם הקורא

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

4.1 HKCU הוא alias ל-hive של המשתמש

HKEY_CURRENT_USER (HKCU) אינו hive עצמאי; הוא alias שמנותב למיקום פיזי לפי המשתמש הקורא. בתהליך משתמש רגיל הוא מצביע למפתח של ה-SID הזה תחת HKEY_USERS. התוכן שלו הוא ה-NTUSER.DAT שנטען ב-logon.1

החריג הוא HKCU\Software\Classes, שהמיקום הפיזי שלו הוא קובץ hive נפרד, UsrClass.dat. הקובץ הזה חי תחת %LOCALAPPDATA%\Microsoft\Windows.11

ה-HKCU של LocalSystem משויך למשתמש ברירת המחדל (HKEY_USERS\.DEFAULT). כש-installer שרץ תחת חשבון administrator כותב ל-HKCU ושירות SYSTEM קורא מ-HKCU, שניהם מפנים למקומות שונים.3

מה ה-alias HKCU באמתכשאפליקציה פותחת HKCU, היא מנותבת למפתח ה-SID שלכם תחת HKEY_USERS בתהליך שלכם ולמפתח משתמש ברירת המחדל בתהליך LocalSystem, כך שהערך שה-installer כתב כבר לא נראההתהליך שלכםתהליך SYSTEMקוד האפליקציה: פותחים HKCUHKCU הוא alias למפתח האמיתיה-SID שלכם תחת HKEY_USERSHKEY_USERS\\.DEFAULTהערך שכתבתם לא קיים

איור 5: גם תחת אותו שם HKCU, משתמש אחר פירושו hive אחר נקרא ונכתב.

שמים הגדרות ברמת המכונה ב-HKLM. Microsoft לא ממליצה לגשת ל-HKCU משירות. אם צריך לקרוא הגדרות של משתמש, עושים impersonation למשתמש הזה ואז משתמשים ב-RegOpenCurrentUser.12

4.2 בודקים גם אם ה-profile נטען

מעבר לחשבון ההרצה, גם אם ה-profile נטען יכול להיות הבעיה.

IIS application pools רצים בלי לטעון user profile כברירת מחדל. הפעלת loadUserProfile הופכת את AppData ואת ה-hive תחת ה-profile לזמינים.7 ההגדרה הזו משפיעה גם על איפה נשמרים מפתחות DPAPI ו-ASP.NET Core Data Protection של הפרק הבא.

ההגדרה “Do not store password” של משימה נבדקת בנפרד, כמגבלה של סוג logon. גם עם אותו משתמש שצוין, S4U logon לא יכול להשתמש ברשת או ב-EFS. ההבדלים הקונקרטיים בין תצורות מסוכמים בפרק 8.6

5. גבול 3: DPAPI — ההצפנה נעולה עם “המפתח של המשתמש”

בעוד AppData ו-HKCU הם בעיות של הסתכלות במקום אחר, DPAPI מייצר בעיה שבה אפשר לקרוא את הקובץ אבל אי אפשר לעשות decrypt לתוכן. הפיכת אפליקציה שמשתמשת בסיסמה שמורה לשירות וקבלת CryptographicException היא דוגמה אחת.

5.1 בעל master key אחר פירושו אין decryption

DPAPI (Data Protection API) הוא מתקן ההצפנה ש-Windows מספק לאפליקציות. קריאה ל-CryptProtectData או ל-ProtectedData.Protect של .NET מאפשרת לאפליקציה להגן על נתונים בלי לשאת מפתח הצפנה בקוד שלה עצמה.13

זה לא הופך את המפתח למיותר, עם זאת. DPAPI משתמש ב-master key שמנוהל על ידי מערכת ההפעלה. ב-scope CurrentUser, master key אקראי לפי משתמש מוגן במפתח שנגזר מ-credentials של ה-logon ונשמר תחת ה-profile.14

שרשרת המפתחות של DPAPIה-master key האקראי של המשתמש מוגן במפתח שנגזר מ-credentials של ה-logon, ה-master key הזה מצפין את ה-secrets של האפליקציה, ו-master key של משתמש אחר לא יכול לעשות decrypt לאותו ciphertextמגן דרך מפתח נגזרמצפיןלא יכול לעשות decryptcredentials של logonה-master key שלכםsecret של האפליקציה (סיסמה שמורה וכו')master key של משתמש אחרנשמר תחת ה-profile

איור 6: הנתונים לא מוצפנים ישירות מה-credentials; מפתח שנגזר מה-credentials מגן על ה-master key.

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

# בסשן של משתמש A: מצפינים ב-scope CurrentUser ושומרים למיקום משותף
Add-Type -AssemblyName System.Security
$bytes = [Text.Encoding]::UTF8.GetBytes("secret")
$enc = [Security.Cryptography.ProtectedData]::Protect($bytes, $null, "CurrentUser")
[Convert]::ToBase64String($enc) | Set-Content C:\ProgramData\demo.bin

# כמשתמש אחר (למשל, אחרי שהפכתם ל-SYSTEM עם PsExec), מנסים לעשות decrypt
$enc = [Convert]::FromBase64String((Get-Content C:\ProgramData\demo.bin))
[Security.Cryptography.ProtectedData]::Unprotect($enc, $null, "CurrentUser")
# → CryptographicException: הנתונים אינם תקפים במצב הנוכחי

5.2 בוחרים את ה-scope ממי אמור להיות מסוגל לעשות decrypt

Scope יחידת ה-decrypt שיקול תכנון
CurrentUser ה-master key של המשתמש שהצפין את הנתונים אי אפשר לעשות decrypt ברגע שעוברים לחשבון הרצה אחר
LocalMachine מפתח משותף לאותה מכונה כל תהליך באותה מכונה יכול לעשות decrypt, לכן מגבילים מי יכול לקרוא את ה-ciphertext עם ACL על הקובץ

secret שגם שירות וגם המשתמש האינטראקטיבי קוראים צריך להיות מתוכנן מההתחלה כ-LocalMachine משולב עם ACL על הקובץ, או שהתהליך צריך לרוץ תחת החשבון של מי שהצפין. לא משנים את ה-scope רק כדי שהשגיאת decryption תיעלם; מחליטים מי מורשה לעשות decrypt. במכונות משותפות, גם מודעים לסיכון שהגנה ברמת המכונה רחבה מדי.15

איך בוחרים את ה-scope של DPAPIבוחרים את ה-scope CurrentUser אם רק המשתמש הזה אמור להיות מסוגל לעשות decrypt ואת ה-scope LocalMachine אם כמה principals באותו מחשב צריכים, ומגבילים את הקוראים של האחרון עם ACL על הקובץרק המשתמש הזהכמה principals באותו מחשבמי אמור להיות מסוגל לעשות decrypt?scope CurrentUserscope LocalMachinedecrypt נכשל כשרצים כמשתמש אחרמגבילים קוראים עם ACL על הקובץ

איור 7: בוחרים את ה-scope כהחלטת תכנון על מי עושה decrypt, לא כ”מה שקרה לעבוד”.

5.3 “שינוי” סיסמה ו”איפוס” הם שונים

המפתח ששומר על ה-master key תלוי ב-credentials של ה-logon. כשמשתמשים משנים את הסיסמה שלהם עצמם, ה-master key מוגן מחדש במפתח שנגזר מהסיסמה החדשה, והיכולת לעשות decrypt עוברת.

לעומת זאת, כש-admin מאפס את הסיסמה של חשבון מקומי, ciphertext מהעבר עשוי כבר לא להיות ניתן ל-decrypt. גם עם אותו חשבון, צריך לבדוק איך ה-credentials שונו.14

5.4 בודקים איפה המפתחות נשמרים גם ב-ASP.NET Core

גם קוד שמעולם לא קורא ל-DPAPI ישירות יכול להיות תלוי בגבול הזה. ASP.NET Core Data Protection שומר את ה-key ring שמשמש להגן על cookie authentication ודומים במיקום שתלוי בסביבה.16

סביבה לאן המפתחות הולכים והתוצאה
user profile זמין %LOCALAPPDATA%\ASP.NET\DataProtection-Keys. ב-Windows המפתחות מוצפנים ב-DPAPI
אין profile זמין, והאפליקציה מתארחת ב-IIS נופלים ל-Registry של HKLM, עם ACL לחשבון תהליך ה-worker
אף אחד לא חל המפתחות הופכים למפתחות זמניים מקומיים לתהליך. הם אובדים בהפעלה מחדש, ונתונים מוגנים כמו authentication cookies הופכים ללא תקפים

בודקים את loadUserProfile, setProfileEnvironment ואת מודל האירוח כקבוצה, ומבינים איזו תצורה קובעת איפה המפתחות מונחים. מה שחשוב אינו רק שהאפליקציה עולה, אלא אם היא יכולה להשתמש באותם מפתחות אחרי הפעלה מחדש.

בחירת scope ומתי להשתמש ב-Credential Manager במקום מכוסים בפירוט ב-“Secrets ביישומי Windows: DPAPI במקום plaintext ב-config”.

6. גבול 4: profiles של דפדפן — “מחובר” שייך למשתמש

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

6.1 יש שני גבולות: מיקום אחסון ומפתח

ה-profile של דפדפן מבוסס Chromium כמו Chrome או Edge חי כברירת מחדל בתיקיית User Data תחת %LOCALAPPDATA%. היסטוריה, cookies, הרחבות, סיסמאות שמורות וכן הלאה שייכים לסביבה של משתמש Windows הזה.17

cookies וסיסמאות שמורות מוצפנים במפתח הצפנה בתוך ה-profile, והמפתח עצמו מוגן ב-DPAPI. העתקת התיקייה למשתמש אחר או למכונה אחרת לכן נכשלת ב-decrypt כי המפתח לא תואם.

Chrome האחרון מניח App-Bound Encryption מעל זה. decrypt של המפתח עובר דרך שירות שרץ עם הרשאות SYSTEM, שמאמת לא רק את המשתמש אלא גם את זהות האפליקציה המבקשת.18 זה חל על דפדפנים מבוססי Chromium; המצב שונה לדפדפנים כמו Firefox שיש להם הגנת profile משלהם.

גבול מה קורה ביעד האוטומציה
גבול AppData למשתמש של סוכן CI או של שירות אין את ה-profile שאתם משתמשים בו בדרך כלל
גבול DPAPI גם אם התיקייה מועתקת, אי אפשר לעשות decrypt למפתח המוגן
שני הגבולות מאחורי מצב sign-in של דפדפןה-profile של הדפדפן חי תחת LOCALAPPDATA ושייך לגבול 1, ומפתח הצפנת ה-cookie מוגן ב-DPAPI ושייך לגבול 3, כך שגם ה-profile וגם המפתח לא עוברים למשתמש אחרprofile של דפדפןנשמר תחת LOCALAPPDATAמפתח הצפנת cookie מוגן ב-DPAPIמשתמש ההרצה של CI מקבל profile ריק ונפרדאי אפשר לקחת בהעתקת התיקייה

איור 8: “לקחת את מצב ה-sign-in איתכם” נחסם גם על ידי גבול 1 וגם על ידי גבול 3.

6.2 באוטומציה, הופכים את הדרך שבה מצב sign-in נוצר למפורשת

Selenium ו-Playwright מופעלים כברירת מחדל עם profile זמני חד-פעמי. כך שגם תחת אותו משתמש, מצב ה-sign-in של הדפדפן היומיומי שלכם לא בשימוש אוטומטית.

גם אם מציינים תיקיית profile קבועה, בעיות מיקום האחסון והמפתח של הפרק הקודם נשארות ברגע שעוברים ל-job ב-CI או לשירות תחת משתמש אחר. התיקון אינו להעתיק profile מחובר אלא אחד מאלה.

  • מקודדים את שלבי ה-sign-in לחשבון בדיקה.
  • משתמשים במנגנון storage-state של כלי האוטומציה כדי לשמור ולשחזר cookies ודומים במפורש.

זה שמשתמש אחר לא יכול להשתמש ב-cookies עם העתקה בלבד אינו אי-נוחות; זה גם גבול אבטחה. מתכננים את האוטומציה על ההנחה שהגבול הזה קיים.

7. גבול 5: Credentials ותעודות — לכל משתמש יש כספת נפרדת

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

7.1 Credential Manager הוא כספת לפי משתמש הרצה

Credential Manager, שאפשר לבדוק עם cmdkey /list, הוא כספת לפי משתמש.19 credentials שמורים נמצאים על הדיסק, אבל הם מוגנים ב-DPAPI ומשמשים תוכניות שרצות כמשתמש הזה.20

הכספת מחזיקה credentials שמורים לשרתי קבצים ולכונני רשת, tokens של Git ששמר git-credential-manager, סיסמאות שמורות לחיבורי RDP, secrets של אפליקציות שמשתמשות ב-Credential API, וכן הלאה.

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

עם זאת, משימה שמוגדרת עם S4U לא מתוקנת בהכנסת credentials בלבד. קודם בודקים את סוג ה-logon, ושוקלים תצורה ששומרת את הסיסמה או מעבר לחשבון שירות. סדר הצעדים מונח בפרק 8.

כספת ה-credentials היא לפי משתמשהכספת שלכם מחזיקה credentials של Git, credentials שמורים לשרתי קבצים וסיסמאות RDP, אבל הכספת של משתמש הרצת השירות ריקה אלא אם מכניסים credentials, וזה מה ששגיאת האימות באמתהכספת שלכםcredentials של Gitcredentials שמורים לשרתי קבציםסיסמאות RDP שמורותכספת משתמש הרצת השירותריקה אלא אם הוכנסו = שגיאת האימות האמיתית

איור 9: “האימות עובד אצלי” הוא רק קיצור ל”הכספת שלי זמינה.”

7.2 לתעודות, בודקים את ה-store ואת הרשאות המפתח הפרטי בנפרד

Store גבול ומטרה
Cert:\CurrentUser store לפי משתמש. המפתח הפרטי של תעודת לקוח שמונחת כאן מוגן לפי משתמש
Cert:\LocalMachine store ברמת המכונה. שמים כאן תעודות לשירותים, ונותנים לחשבון השירות גישת קריאה דרך ה-ACL של המפתח הפרטי

לתעודה שמשמשת שירות, התכנון הבסיסי הוא store של LocalMachine יחד עם ה-ACL על המפתח הפרטי.21 ה-store של המשתמש חי פיזית תחת HKCU\Software\Microsoft\SystemCertificates, כך שהוא יושב בתוך גבול HKCU.22

לפרטים, ראו “Certificate Store ב-Windows — CurrentUser או LocalMachine, לאן שמים”.

7.3 עם העלאת UAC, מפרידים “אותו חשבון” מ”חשבון אחר”

כשמשתמש administrator מתחבר עם UAC מופעל, נוצרים שני tokens מקושרים: token סטנדרטי עם הרשאות מוגבלות ו-token administrator מלא.8

mappings של כונני רשת הם לפי logon session. Z: עשוי להיות נראה ב-File Explorer ועדיין בלתי נראה לכלי שרץ כ-administrator.9

איך זה רץ מה משתנה ומה לא
העלאת UAC תוך הישארות באותו חשבון ה-SID זהה, ו-HKCU וכספת ה-credentials נשארים זהים. mappings של כוננים, שהם לפי logon session, עשויים להפוך לבלתי נראים
elevation או RunAs עם ה-credentials של חשבון administrator אחר גם ה-SID משתנה. HKCU והכספת הופכים לאלה של ה-administrator הזה. גם AppData, מפתחות ומצב דפדפן כפופים לגבול של המשתמש האחר

לא מניחים שכל “זה מפסיק לעבוד כשעושים elevate” הוא שינוי משתמש; קודם בודקים אם זה אותו חשבון.

הפיצול בתוך משתמש אחד שיוצרת העלאת UAClogon של administrator עם UAC מופעל יוצר שני tokens, token סטנדרטי ו-token elevated, וכונן רשת שממופה בצד ה-token הסטנדרטי אינו נראה לתהליך שרץ עם ה-token ה-elevatedlogon session נפרדlogon של משתמש administratortoken סטנדרטיtoken elevatedכונן Z: ממופה כאןכלי elevated לא רואה את Z:

איור 10: elevation יוצר “עולם אחר לאותו משתמש.” הגבול אינו רק על ה-SID.

8. רשימת בדיקה לפי צורת הרצה

8.1 למשימות, מסתכלים על סוג ה-logon אחרי חשבון ההרצה

ב-Task Scheduler, מה זמין משתנה עם סוג ה-logon גם כשאותו משתמש צוין.6

סוג logon מה לבדוק
Interactive token (InteractiveToken) תצורה שרצה בסשן שמחובר
Stored password (Password) תצורה שיכולה להשתמש ב-credentials גם כשלא אינטראקטיבית
S4U (סיסמה לא נשמרת) הסיסמה לא נשמרת, ומשאבי רשת וקבצים מוצפנים (EFS) אינם נגישים

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

פרטי ההגדרות מכוסים ב-“כשמשימות Task Scheduler לא רצות או יוצאות עם 0x1”.

סוג ה-logon של משימה כציר שנילתצורת הרצה של Task Scheduler יש סוג logon של interactive token, stored password או S4U, ועם S4U הסיסמה לא נשמרת והרשת ו-EFS אינם נגישיםInteractive tokenStored passwordS4U (סיסמה לא נשמרת)תצורת הרצת משימהסוג logonרץ בסשן המחוברcredentials שמישים גם לא-אינטראקטיביתלא מגיעים לרשת או ל-EFS

איור 11: גם עם אותו משתמש הרצה, איך הוא התחבר משנה מה זמין.

8.2 טבלת הבדיקה לפני שינוי יעד ההרצה

צורת הרצה principal ההרצה גבולות ותסמינים לבדוק במיוחד
Task Scheduler החשבון שצוין ברישום גבולות 1, 2, 3 ו-5. בודקים לאן AppData ו-HKCU מצביעים, decryption של DPAPI, והכספת. עם S4U, הרשת ו-EFS אינם בשימוש
Windows service SYSTEM, LocalService, NetworkService, חשבון שירות גבולות 1 עד 5. SYSTEM משתמש ב-systemprofile וב-HKCU של משתמש ברירת המחדל; LocalService ו-NetworkService משתמשים בסביבות שלהם תחת ServiceProfiles. המפתחות, הכספת ומצב הדפדפן של המפתח לא עוברים
IIS application pool זהות ייחודית ל-pool כמו IIS AppPool\<name> גבולות 1, 2, 3 ו-5. בודקים שה-profile לא נטען כברירת מחדל, איפה מפתחות Data Protection מונחים, וגישה למפתחות הפרטיים של תעודות CurrentUser
RunAs / העלאת UAC המשתמש שצוין, או token אחר של אותו משתמש עם אותו חשבון, ה-SID, HKCU והכספת זהים, ו-mappings של כוננים מושפעים מהבדל הסשן. עם חשבון אחר, בודקים את כל גבולות 1 עד 5
סוכן CI/CD משתמש השירות של הסוכן. לעתים קרובות מעולם לא התחבר אינטראקטיבית גבולות 1 עד 5. בודקים תלויות ב-profile ובמצב sign-in של הדפדפן, credentials של Git, הגדרות HKCU של המפתח, ונתונים מוגנים ב-DPAPI
RDP / שרת משותף כמה סשנים של אותו משתמש, או כמה משתמשים כמה סשנים של אותו משתמש חולקים AppData ו-HKCU, לכן שומרים על כתיבות מתנגשות. משתמשים שונים מופרדים בגבולות 1 עד 5

השורה האחרונה היא זהירות בכיוון ההפוך. כמה סשני RDP של אותו משתמש לא נותנים לכל סשן AppData ו-HKCU עצמאיים משלו. כאן הבעיה אינה שמשהו בלתי נראה, אלא שאותו דבר משותף ונכתב אליו.

9. הנחיות לתכנון ולחקירה

9.1 מחליטים על מיקומי אחסון והגדרה מה-principal שמשתמש בהם

יעד יסודות תכנון
הגדרות ייחודיות למשתמש שמים אותן ב-AppData או ב-HKCU
נתונים והגדרות שמשותפים לכל המשתמשים או לשירותים שמים אותם ב-ProgramData או ב-HKLM, ומתכננים הרשאות כתיבה ו-ACLs
Secrets בוחרים את ה-scope של DPAPI ממי אמור להיות מסוגל לעשות decrypt. עם LocalMachine, מתכננים גם את ה-ACL על הקובץ
Credentials ותעודות לשירותים כוללים הכנסת credentials לכספת של חשבון ההרצה, הנחת התעודה ב-Certificate Store של LocalMachine, והגדרת ה-ACL של המפתח הפרטי בהליך ההגדרה

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

9.2 חוקרים מ-principal ההרצה למיקום המופנה בפועל

הליך חקירה לתקלות גבול משתמשמאשרים את משתמש ההרצה עם whoami, מסתכלים על ה-token ב-Process Explorer, מזהים את הנתיבים ואת מפתחות ה-Registry שנקראו בפועל עם Process Monitor, ואם צריך משחזרים מהעולם של הצד השני עם psexecמאשרים את משתמש ההרצה עם whoami /allבודקים את ה-token ב-Process Explorerמזהים את הנתיבים והמפתחות בפועל עם ProcMonמשחזרים מהעולם של הצד השני עם psexecנתיב profile לא צפוי הוא הרמז

איור 12: מאשרים את משתמש ההרצה, מסתכלים על הנתיבים ועל מפתחות ה-Registry בפועל, ואז משחזרים כ-principal ההרצה היעד.

שלב איך בודקים על מה מסתכלים
1. מאשרים את משתמש ההרצה כותבים whoami /all ללוג מיד אחרי ההפעלה האם principal ההרצה של המשימה או השירות שנכשלים זהה לזה שעל השולחן
2. בודקים את ה-token Process Explorer המשתמש והסשן של התהליך. זה משתמש אחר, או הקשר הרצה אחר של אותו משתמש?
3. מסתכלים על המיקום המופנה בפועל Process Monitor הנתיבים של הקבצים שנפתחו ומפתחות ה-Registry. האם profile אחר מהצפוי מופיע ב-PATH NOT FOUND?
4. משחזרים כ-principal ההרצה היעד ל-SYSTEM, psexec -s -i cmd מנסים את אותה פעולה מ-shell של SYSTEM ומאשרים את ההבדלים מהסביבה האינטראקטיבית שלכם

כשהגדרות חסרות, חוזרים ל-AppData ול-HKCU; כשאפשר לקרוא את הקובץ אבל לא לעשות decrypt, ל-DPAPI; כשרק האימות נכשל, לכספת, לתעודות ולסוג ה-logon. לא שופטים מהודעת השגיאה לבדה; הקיצור הוא ליישר “מי השתמש באיזה מיקום, עם איזה מפתח או credentials.”

10. סיכום

“עובד אצלי” פירושו “עובד כמשתמש הזה, עם ה-profile הזה, עם המפתחות האלה ועם הכספת הזו.” גם כשאותו .exe מופעל באותו מחשב, הפיכה למשימה, לשירות או ל-job ב-CI צריכה להיות מטופלת כמעבר של סביבת ההרצה.

AppData ומשתני סביבה של המשתמש מפנים ל-profile של משתמש ההרצה, ו-HKCU באותו אופן מצביע ל-hive של המשתמש הזה. ה-scope CurrentUser של DPAPI תלוי ב-master key של המשתמש, וגם מצב sign-in של דפדפן ו-Credential Manager מושפעים מהגבול הזה.

יתר על כן, גם עם אותו SID, ההבדלים בסוג logon, בטעינת profile ובסשן שיוצרת העלאת UAC נשארים. ולהפך, כשכמה סשנים של אותו משתמש חולקים AppData ו-HKCU, חושבים על כתיבות מתנגשות.

בסקירות תכנון, שואלים את השאלה הבאה.

האם הקוד הזה נכון לא משנה כאיזה משתמש הוא רץ?

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

מאמרים קשורים

תחומי ייעוץ קשורים

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

קישורים

  1. Microsoft Learn, About User Profiles. על כך ש-user profile נוצר ב-logon הראשון, ועל כך ש-profile מורכב מ-hive ה-Registry NTUSER.DAT (נטען ב-logon וממופה ל-HKEY_CURRENT_USER) ומקבוצת תיקיות ה-profile במערכת הקבצים.  2

  2. Microsoft Learn, Local accounts. על כך ש-SYSTEM (S-1-5-18), NETWORK SERVICE (S-1-5-20) ו-LOCAL SERVICE (S-1-5-19) הם חשבונות המערכת המקומיים כברירת מחדל שמשמשים להרצת מערכת ההפעלה והשירותים. 

  3. Microsoft Learn, LocalSystem Account. על כך שה-token של LocalSystem כולל NT AUTHORITY\SYSTEM, על כך שהוא לא משויך לשום חשבון משתמש מחובר, וכתוצאה על כך ש-HKEY_CURRENT_USER משויך למשתמש ברירת המחדל ועל הצורך לעשות impersonation למשתמש כדי לגשת ל-profile של המשתמש הזה.  2

  4. Microsoft Learn, LocalService Account. על כך שלחשבון LocalService יש subkey משלו תחת HKEY_USERS, ועל כך ש-HKEY_CURRENT_USER משויך לחשבון LocalService. אותו דבר חל על NetworkService (NetworkService Account).  2

  5. Microsoft Learn, Application Pool Identities. על כך ש-application pools רצים תחת זהות ייחודית ל-pool, על כך ש-IIS לא טוען את ה-user profile של Windows כברירת מחדל, ועל הגדרת מאפיין LoadUserProfile ל-true כדי לטעון את ה-profile. 

  6. Microsoft Learn, logonType Simple Type. על כך שסוגי logon של משימות כוללים S4U, Password ו-InteractiveToken, ועל כך ש-S4U logon לא שומר את הסיסמה ואין לו גישה לרשת או לקבצים מוצפנים.  2 3

  7. Microsoft Learn, Process Model Settings for an Application Pool. על המאפיינים loadUserProfile ו-setProfileEnvironment של processModel של application pool, ששולטים אם תהליך ה-worker טוען את ה-user profile.  2

  8. Microsoft Learn, How User Account Control works. על כך ש-logon של משתמש administrator יוצר שני tokens מקושרים, token משתמש סטנדרטי ו-access token administrator מלא, כש-UAC מופעל.  2

  9. Microsoft Learn, Mapped drives are not available from an elevated prompt. על כך שכונני רשת שממופים בסשן שהתחבר עם ה-token הסטנדרטי אינם זמינים לתהליכים elevated, ועל הסיבה הבסיסית ששני ה-logon sessions המקושרים מחזיקים את mappings הכוננים שלהם בנפרד.  2

  10. Microsoft Learn, KNOWNFOLDERID. על כך ש-FOLDERID_RoamingAppData (%APPDATA%), FOLDERID_LocalAppData (%LOCALAPPDATA%) ו-FOLDERID_LocalAppDataLow מוגדרים כ-known folders לפי משתמש. 

  11. Microsoft Learn, Error occurs during desktop setup and desktop location is unavailable. על כך של-user profile יש שני קבצי hive, NTUSER.DAT ו-UsrClass.dat, ועל כך ש-UsrClass.dat מונח תחת AppData\Local\Microsoft\Windows. 

  12. Microsoft Learn, Services and the Registry. על כך ששירותים לא ניגשים ל-HKEY_CURRENT_USER או ל-HKEY_CLASSES_ROOT, ועל שימוש בפונקציה RegOpenCurrentUser כשעושים impersonation למשתמש. 

  13. Microsoft Learn, CryptProtectData function. על כך ש-CryptProtectData בדרך כלל מגן על נתונים במפתח סשן שמשויך למשתמש המחובר ומניח decrypt על ידי אותו משתמש, ועל כך שהדגל CRYPTPROTECT_LOCAL_MACHINE עובר להגנה ברמת המכונה. 

  14. Microsoft Learn, Windows Data Protection. על כך ש-DPAPI מגן על master key אקראי בהצפנתו במפתח שנגזר מסיסמת המשתמש, על כך שה-master key נשמר תחת ה-user profile, ועל כך שה-master key מוגן מחדש כשהסיסמה משתנה.  2

  15. Microsoft Learn, ProtectedData Class. על כך ש-DataProtectionScope.CurrentUser מאפשר decrypt רק למשתמש שהגן על הנתונים, ועל כך ש-LocalMachine מאפשר decrypt לכל תהליך באותה מכונה. 

  16. Microsoft Learn, Data Protection key management and lifetime in ASP.NET Core. על כך שהמפתחות נשמרים ב-%LOCALAPPDATA%\ASP.NET\DataProtection-Keys ומוצפנים ב-DPAPI ב-Windows כש-user profile זמין, על הנפילה ל-Registry של HKLM עם ACL לחשבון תהליך ה-worker כשמתארחים ב-IIS בלי profile, על כך שהמפתחות אובדים כשהתהליך יוצא ו-payloads מוגנים הופכים לבלתי ניתנים ל-decrypt כשאף אחד מהתנאים לא חל, ועל המעורבות של מאפיין setProfileEnvironment. 

  17. פרויקט Chromium, User Data Directory. על כך שתיקיית User Data כברירת מחדל של Chrome ב-Windows היא %LOCALAPPDATA%\Google\Chrome\User Data, ועל כך ש-profiles (היסטוריה, סימניות, cookies וכן הלאה) מונחים תחתיה. 

  18. Google Security Blog, Improving the security of Chrome cookies on Windows. על כך ש-Chrome השתמש ב-DPAPI כדי להצפין cookies ודומים ב-Windows, ועל כך ש-App-Bound Encryption מגן על המפתח דרך שירות שרץ עם הרשאות SYSTEM ומאמת את זהות האפליקציה שמבקשת decrypt. 

  19. Microsoft Learn, cmdkey. על כך שפקודת cmdkey מציגה, יוצרת ומוחקת שמות משתמש וסיסמאות שמורים (credentials). 

  20. Microsoft Learn, Cached and Stored Credentials Technical Overview. על כך ש-credentials שנשמרו ב-Credential Manager נשמרים על הדיסק ומוגנים ב-DPAPI, ועל כך שתוכניות שרצות כמשתמש הזה יכולות לגשת ל-credentials ב-store הזה. 

  21. Microsoft Learn, Local Machine and Current User Certificate Stores. על כך שיש שני סוגים של Certificate Stores, ה-store של המכונה המקומית (ברמת המכונה) וה-store של המשתמש הנוכחי (לפי משתמש). 

  22. Microsoft Learn, System Store Locations. על כך שה-system store CERT_SYSTEM_STORE_CURRENT_USER מונח תחת HKEY_CURRENT_USER\Software\Microsoft\SystemCertificates ב-Registry. 

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

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

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

שאלות נפוצות

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

אפליקציה שעובדת כשמפעילים מ-File Explorer לא מוצאת את קובץ התצורה כשמפעילים מ-Task Scheduler. למה?
כי משתני סביבה כמו %APPDATA% נפתרים ל-profile של המשתמש שמריץ את התוכנית. כשהמשימה רצה כ-SYSTEM או כחשבון אחר, משתני הסביבה מצביעים ל-profile אחר (תחת systemprofile במקרה של SYSTEM), וקובץ התצורה ששמרתם לא קיים שם. בודקים את החשבון שהמשימה רצה כ, ושמים נתונים שצריכים להיות משותפים תחת ProgramData כתיקון הקבוע.
סיסמה שנשמרה עם ProtectedData.Protect כבר לא ניתנת ל-decrypt ברגע שהאפליקציה הפכה לשירות.
DPAPI ב-scope CurrentUser תלוי ב-master key של המשתמש שהצפין את הנתונים. אם השירות רץ כחשבון אחר, ה-master key הוא אחר, ולכן decryption נכשל עם CryptographicException. מתכננים מחדש secret שגם השירות וגם המשתמש האינטראקטיבי קוראים כך שישתמש ב-scope LocalMachine יחד עם ACL על הקובץ, או מריצים את השירות תחת החשבון של מי שהצפין.
מה קורה כשתהליך שרץ כ-SYSTEM קורא HKCU?
בתהליך LocalSystem, HKEY_CURRENT_USER משויך למשתמש ברירת המחדל (HKEY_USERS\.DEFAULT), כך שהערכים שהמשתמש האינטראקטיבי כתב ל-HKCU אינם נראים. שמים הגדרות ברמת המכונה ב-HKLM, ואם חייבים לקרוא הגדרות של משתמש, עושים impersonation למשתמש הזה ואז משתמשים ב-RegOpenCurrentUser.
אפשר להעתיק profile מחובר של Chrome/Edge למכונת CI ולהשתמש בו?
בדרך כלל לא. ה-profile של דפדפן מבוסס Chromium כמו Chrome או Edge חי תחת %LOCALAPPDATA% של המשתמש, ומפתח ההצפנה של cookies ושל סיסמאות שמורות מוגן ב-DPAPI של המשתמש הזה. העתקת התיקייה למשתמש אחר או למכונה אחרת נכשלת ב-decrypt כי המפתח לא תואם (דפדפנים כמו Firefox, שיש להם הגנת profile משלהם, הם סיפור אחר). לאוטומציה, מקודדים את שלבי ה-sign-in לחשבון בדיקה, או משתמשים במנגנון storage-state של כלי האוטומציה.
Credentials שנשמרו עם cmdkey לא בשימוש כשהמשימה רצה מ-Task Scheduler.
כי כספת Credential Manager נפרדת לכל משתמש, ומה ששמרתם נכנס לכספת של ה-logon האינטראקטיבי שלכם. בנוסף, משימה שמוגדרת עם "Do not store password" (S4U) רצה בלי network credentials, כך שהכנסת credentials לכספת לא עוזרת כל עוד היא נשארת על S4U. קודם שוקלים תצורה ששומרת את הסיסמה או מעבר לחשבון שירות, ורק אז, אם עדיין נחוץ, מכניסים את ה-credentials לכספת של חשבון ההרצה עצמו.

פרופיל הכותב

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

Go Komura

מנהל KomuraSoft LLC

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

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

חזרה לבלוג