טיפול במשתמשים שכבר אינם Domain Admin

טיפול במשתמשים שכבר אינם Domain Admin

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

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

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

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

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

סקירת הבעיה:

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

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

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

את הסקריפטים כדאי להעתיק ל- PowerShell ISE Adminומשם לעבוד איתם.

 Domain Admin לשעבר – הבעיה:

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

  • איפוס סיסמה ל-Domain Admin
  • התחברות דרך ה-Domain Admin עם הסיסמה החדשה
  • אז ליצור לעצמו משתמש של דומיין אדמין
  • או להפוך גם את המשתמש הישן שלו ל-Domain Admin

בקיצור, ברגע שמשתמש הופך ל-Domain Admin, אף אחד לא יכול לערוך את המשתמש שלו – למעט Domain Admin אחר.

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

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

שורש הבעיה:

בפורומים שונים ברשת, מייעצים לאפס את ה-Attribute שנקרא adminCount. אצל משתמש רגיל הערך הזה לא רלוונטי ומופיע כ"not set". אצל Domain Admin מופיע שם הערך 1. הערך הזה מונע ממשתמשים בעלי מיקום נמוך יותר בהיררכיה, לערוך פרטים בפרופיל.

השוואה

מי שיגדיל את התמונה יראה מימין לשמאל את הנתונים הבאים:

משתמש רגיל, משתמש שכרגע Domain Admin, משתמש שהיה Domain Admin ועדיין מופיע לו Attribute.

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

למה זה קורה?

AdminSDHolder

Active-Directory מנהל את כל הרכיבים שלו (משתמשים, מחשבים, OU, קבוצות וכו') באמצעות כל מיני מאפיינים. אחד המאפיינים החשובים ביותר, הוא נושא ההרשאות והאבטחה. כחלק מן העניין, Active-Directory מכיל כמה קבוצות מובנות שחיוניות לתפעול שלו – כמו Domain Admins ועוד כמה. לצורך בקרה על כל העניין הזה של הרשאות ואבטחה, קיים בActive-Directory- רכיב בשם AdminSDHolder. SD – Security Descriptor.

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

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

הפתרון:

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

התשובה טמונה בהרשאות. בפשטות.

כמו שיש הרשאות לתיקיות וקבצים, כך ישנו מערך הרשאות למשתמשים, קבוצות ו-OU ב-Active-Directory. כדי לקבל גישה לעריכת הרשאות אלה, צריך כמובן להיות Domain Admin. בנוסף צריך להציג את הActive-Directory- בתצוגה מתקדמת. התצוגה המתקדמת מאפשרת לראות רכיבים רבים נוספים, ביניהם Attributes וגם הלשונית Security, שבה מנהלים את ההרשאות.

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

השוואה

שוב באותו הסדר, רואים מימין משתמש רגיל שיש ל-HelpDesk הרשאות עליו. באמצע יש Domain Admin שאין ל-HelpDesk הרשאות עליו. משמאל יושב אותו Domain Admin לשעבר, שמבחינת הרשאות שיש לאחרים עליו, מצבו לא השתנה.

הסבר:

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

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

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

ישום הפתרון:

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

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

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

מקרים שונים:

מי שרוצה לתת הרשאות על משתמש אחד או קבוצה אחת, יצטרך לבצע מעט עריכה. למשל – להחליף את הלולאה בערך יחיד. המטרה היא שורה 51, שבה משתמשים ב-Attribute שנקרא DistinguishedName של אותו משתנה $user. אפשר להחליף את המשתנה הזה שמתייחס למשתמש תורן או ספציפי, במשתנה שמתייחס לקבוצה או ל-OU.

מי שרק רוצה לתת הרשאות על כל המשתמשים לקבוצת HelpDesk שלו, רק שיוודא שהשם של קבוצת HelpDesk שלו כפי שמופיע ב-Active-Directory, ירשם נכון בשורה 47.

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

הפשטה וסיכון בצדה:

ישנה אפשרות נוספת ופשוטה יותר, שבמקרים מסוימים יהיה אפשר להריץ.

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

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

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

הערות:

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

  • במקומות שבהם ישנם הבדלי הרשאות בין המשתמשים הכלליים (בניגוד למשתמשים החברים בקבוצות ניהול מוגנות)
  • במקומות שבהם ישנן לקבוצות מסוימות הרשאות רק על חלק מהמשתמשים או רק למשתמשים ספציפיים

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

הסקריפט הכוללני והמקוצר, מופיע כנספח ב'.

משתמשים נשכחים בדומיינים צדדיים.

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

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

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

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

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

הפתרון:

הפתרון לעניין זה הוא יצירת משימה מתוזמנת שרצה ב-DC – השרת הראשי של אותו דומיין. המשימה מפעילה סקריפט שאותו אשתף עמכם כנספח 2. פעולתו של הסקריפט היא כדלקמן:

הסקריפט מתחבר מתוך ה-DC בדומיין הצדדי, אל השרת הראשי בדומיין הראשי.

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

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

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

אחרת, לפתוח את הסקריפט באמצעות PowerShell ISE Admin. שם צריך להזין בשורות 10-11 את המשתמש והסיסמה. להזין בשורה 15 את שם השרת הראשי בדומיין הראשי שאליו צריך להתחבר. אז להריץ את הסקריפט. לאחר מכן להחליף את הסיסמה בכוכביות או כל קשקוש אחר.

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

נספח א'

מתן הרשאות למשתמש או קבוצה, על אובייקט ב-Active-Directory.

Import-Module ActiveDirectory
# Bring up an Active Directory command prompt so we can use this later on in the script
cd ad:
# Get a reference to the RootDSE of the current domain
$rootdse = Get-ADRootDSE
# Get a reference to the current domain
$domain = Get-ADDomain

# Create a hashtable to store the GUID value of each schema class and attribute
$guidmap = @{}
Get-ADObject -SearchBase ($rootdse.SchemaNamingContext) -LDAPFilter `
"(schemaidguid=*)" -Properties lDAPDisplayName,schemaIDGUID | 
% {$guidmap[$_.lDAPDisplayName]=[System.GUID]$_.schemaIDGUID}

# Create a hashtable to store the GUID value of each extended right in the forest
$extendedrightsmap = @{}
Get-ADObject -SearchBase ($rootdse.ConfigurationNamingContext) -LDAPFilter `
"(&(objectclass=controlAccessRight)(rightsguid=*))" -Properties displayName,rightsGuid | 
% {$extendedrightsmap[$_.displayName]=[System.GUID]$_.rightsGuid}

# Contains all users in AD
$allusers = Get-ADUser -Filter * 

# Contains all Domain Admin members
$admin = Get-ADGroupMember -Identity "Domain Admins" -Recursive

# Make list of distinguishedName attribute of Domain Admins
$dsn = @()
foreach($x in $admin) {
    $dsn += $x.distinguishedName
}

# Follow procedure on all users
foreach($user in $allusers){

    # Get a reference to the user we want to delegate
    $user.DistinguishedName 

    # If user is not Domain Admin, follow procedure and grant full control permissions on him
    if($user.distinguishedName -ne $dsn){
    
        # If you want to get permission on users for specific user, get him as $role.
        #$role = Get-ADUser -Identity ("DistinguishedName")

        # Get the SID values of each group or user we wish to delegate access to.
        $p = New-Object System.Security.Principal.SecurityIdentifier (Get-ADGroup "HelpDesk").SID
        #$s = New-Object System.Security.Principal.SecurityIdentifier $role.SID

        # Get a copy of the current DACL on the target.
        $acl = Get-ACL -Path ($user.DistinguishedName)

        # Create an Access Control Entry for new permission we wish to add
        # Allow group or user to write all properties of descendent user objects
        $acl.AddAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule `
        $p,"GenericAll","Allow",$guidmap["user"],"All"))

        # Re-apply the modified DACL to the OU
        Set-ACL -ACLObject $acl -Path ("AD:\"+($user.DistinguishedName))
    }
}

# This function returns a list of users which have adminCount attribute, and therefore,
# permissions given to Help-Desk on those users, will not last.
function ProtectedUsers{
    
    # Get all users wiche have the attribute of adminCount
    $admin = Get-ADUser -Filter {admincount -eq 1} -Properties admincount, name, enabled | select name, adminCount, enabled
    
    $count = 0
    $prUsers = @()
    # Fore eache user in the list, check if it's enabled user.
    foreach($x in $admin){
        if($x.enabled -eq $true){
            # Make a list of enabled users with adminCount.
            $prUsers += $x
            # Count the amount of protected users.
            $count += 1
        }
    }
    # Print list of protected users
    $prUsers
    # Print number of protected users
    $count
}

# This function returns a list of groups which have adminCount attribute.
# All members of this groups are protected. 
function ProtectedGroups{
    $countGr = 0
    $groups = Get-ADGroup -LDAPFilter "(admincount=1)" | Select Name
    foreach($g in $groups){
        # Count the amount of protected users.
        $countGr += 1 
    } 
    # Print list of protected groups
    $groups
    # print number of protected groups
    $countGr
}

# Unmark to call this functions.

#ProtectedUsers
#ProtectedGroups

נספח ב'

ניקוי משתמשים ישנים מדומיינים צדדיים.

# This script intended to find all users which are disabled in the main domain, and disable them in the current domain as well.
# The script is running from DC of the other domain, remotely connecting to DC of the main domain and get a list of it's disabled users.
# The script is running over all active users on current DC, if a user from the list found, the script disables it.

# If test/service users have been deleled, there is a function on the bottom to retrieve them. just go to the last line and remark it.

### Yosi Cohen ###

# Taking Username and Password to connect the main domain.
$user = "Domain\User"
$pass = ConvertTo-SecureString "The password" -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential($user, $pass)

# Make an array of disabled usernames in the main domain - using the credentials taken before.
$sviva_users = Get-ADUser -Filter {enabled -eq $false} -Server dc01.domain.local:3268 -Credential $Cred -Properties sAMAccountName `
| SELECT sAMAccountName | %{$_.sAMAccountName} 

# Make an array of active users in current domain.
$localusers = Get-ADUser -Filter {enabled -eq $true} -Properties sAMAccountName


$count = 0
$disabled = @()

# Run over all active users found. for each user do the folloing procedure:
foreach ($u in $localusers){

    # If the user listed in disabled users from the main domain,
   if ($u.sAMAccountName -in $sviva_users) {
        # Disable the user here as well.
        Disable-ADAccount -Identity $u.SamAccountName
        # Add fresh disabled user to a list.
        $disabled += $u
        # Count how many had been disabled by the script.
        $count +=1
   }
}

# Show list of users which had been disabled now, and how many they were. 
$disabled | select name, SamAccountName
$count



# To retrieve accidently disabled users (mostly for test and service accounts):
function Re-EnableUsers {
    # Get disabled users on this DC.
    $localusers = Get-ADUser -Filter {enabled -eq $false} -Properties sAMAccountName

    # Make list of proccessed users.
    $count = 0
    $re_enabled = @()

    # Follow procedure on each disabled user:
    foreach ($u in $localusers){

        # If the username contain "test" (or whatsoever the user you want to retrieve),
        if ($u.sAMAccountName -like "*test*") {
            # Enable the user.
            Enable-ADAccount -Identity $u.SamAccountName
            # Add fresh enabled user to a list.
            $re_enabled += $u
            # Count how many had been enabled.
            $count +=1
        }
    }

    # Show list of users which had been enabled now, and how many they were. 
    $disabled | select name, SamAccountName
    $count   
}

# To re-enable the test users, remark the call to the function on the next line:
#Re-EnableUsers 

כתיבת תגובה