Vadims 的个人资料Vadims Podans's former b...照片日志列表更多 工具 帮助

日志


2008/7/4

Управление безопасностью общих папок (сетевых шар) в PowerShell (часть 1)

В предыдущей статье я сделал вводну часть по управлению сетевыми папками в PowerShell. В ней я не рассматривал вопрос управления Share Permissions, т.к. это дело весьма непростое. Итак, что нам для этого потребуется? А потребуются нам следующие классы WMI:

Основной класс, который позволит нам извлечь параметры безопасности - Win32_LogicalShareSecuritySettings. Как я говорил в предыдущей статье, упрощённый вариант создания новой сетевой папки - не самый лучшй вариант создания для последующего управления. Сейчас я продемонстрирую вам проблему:

[C:\] ([wmiClass] 'Win32_share').Create("C:\Test", "UserShare", "0", "100", "Network share for users")

__GENUS                 : 2
__CLASS                  : __PARAMETERS
__SUPERCLASS        :
__DYNASTY              : __PARAMETERS
__RELPATH               :
__PROPERTY_COUNT: 1
__DERIVATION         : {}
__SERVER                :
__NAMESPACE          :
__PATH                    :
ReturnValue            : 0

[C:\] Get-WmiObject Win32_Share

Name                                 Path                                                           Description
----                                    ----                                                             -----------
C$                                     C:\                                                              Default share
UserShare                        C:\Test                                                       Network share for users
CertEnroll                           C:\WINDOWS\system32\CertSrv\CertEnroll    Certificate Services share
IPC$                                                                                                     Remote IPC
ADMIN$                              C:\WINDOWS                                               Remote Admin
SYSVOL                              C:\WINDOWS\SYSVOL\sysvol                        Logon server share
NETLOGON                          C:\WINDOWS\SYSVOL\sysvol\contoso.com...  Logon server share

[C:\] Get-WmiObject Win32_LogicalShareSecuritySetting | Format-List [a-z]*

Caption        : Security settings of CertEnroll
ControlFlags : 32772
Description   : Security settings of CertEnroll
Name           : CertEnroll
SettingID      :

Caption        : Security settings of SYSVOL
ControlFlags : 32772
Description   : Security settings of SYSVOL
Name           : SYSVOL
SettingID      :

Caption        : Security settings of NETLOGON
ControlFlags : 32772
Description   : Security settings of NETLOGON
Name           : NETLOGON
SettingID      :

Итак, первой строчкой мы создали новую шару. В выводе команды я выделил последнюю строчку ReturnValue и его значение 0. Ноль нам говорит, что шара создалась успешно. Чтобы в этом убедиться в следующей строчке я ввёл команду, которая показывает список всех расшаренных папок на локальной машине. Я так же выделил жирным строчку, которая показывает нашу шару. Третей командой я вывел параметры безопасности всех доступных сетевых ресурсов на локальной машине. И что мы видим? Точнее, чего мы не видим - а не видим мы административных шар (которые заканчиваются на знак $) и, так же, не увидели созданной нами сетевой папки UserShare. И это означает, что мы в последующем через классы WMI не сможем извлекать параметры безопасности для последующей обработки. Поэтому более правильным будет создание новой сетевой папки с явным назначением Share Permissions для неё.

Если посмотреть в описание метода Create класса Win32_Share, то там видно, что в качестве последнего параметра выступает класс Win32_SecurityDescriptor. Посмотрим, что нужно для него. Я для него нужны классы Win32_Ace и Win32_Trustee. Ну что ж, приступим к разбору. Итак, у нас есть уже шара с дефолтными правами. Чтобы создать новый набор прав нам нужно знать следующее:

  • Имя сетевой папки для которой будем изменять права сетевого доступа;
  • имя пользователя, которому будут назначены права;
  • тип доступа, который нужно назначить (FullControl, Change, Read).

Теперь нужно объявляем переменные для них:

$share="Test"
$user = "Accounting"
$mask = "Сhange
"

Теперь объявляем вышеуказанные классы WMI:

$SD = ([WMIClass] "Win32_SecurityDescriptor").CreateInstance()
$ace = ([WMIClass] "Win32_Ace").CreateInstance()
$Trustee = ([WMIClass] "Win32_Trustee").CreateInstance()

Теперь преобразовываем имя пользователя/группы в его SID. Преобразование я описывал вот в этой статье:

$SID = (new-object security.principal.ntaccount $user).translate([security.principal.securityidentifier])

В переменной $SID теперь хранится SID данного пользователя. Правда, здесь он содержится в строковом формате. Но если мы посмотрим в свойства класса Win32_Trustee, то увидим, что тип данных для свойства SID должен быть: Data type: uint8 array. Получить такой массив можно следующей конструкцией:

[byte[]] $SIDArray = ,0 * $SID.BinaryLength
$SID.GetBinaryForm($SIDArray,0)

Здесь мы объявили переменную $SIDArray с длиной равной BinaryLength. Длину BinaryLength можно узнать выполнив преобразование имени пользователя в SID, как это рассказано в ссылке приведённой выше. Вот теперь у нас переменная $SID содержит уже байтовый массив из SID пользователя/группы, который получили из $SIDArray. С этим мы закончили. Теперь эти данные можно передать в класс Win32_Trustee, который является абстрактным классом для описания пользователя или группы. Он нам потребуется для дальнейшей передачи этих данных в класс Win32_Ace.

$Trustee.Name = $user
$Trustee.SID = $SIDArray

Сам класс Win32_Trustee уже объявлен, поэтому мы просто добавляем значения свойств для этого класса.

Примечание: На первый взгляд это может показаться очень сложным материалом (мне он тоже дался не очень легко). По сути здесь присутствует множественная инкапсуляция данных и преобразование этих данных из одного типа в другой. Т.е. из исходных данных у нас есть разве что имя пользователя/группы и тип устанавливаемых прав. Если внимательно изучить ссылки, которые приведены в самом начале, то можно увидеть следующую последовательность преобразований:

  • имя пользователя/группы в SID;
  • SID в байтовый массив SIDArray;
  • передача SIDArray и имени в Win32_Trustee (первый этап т.н. "инкапсуляции");
  • Передача преобразованного типа прав доступа в класс Win32_Ace (второй этап инкапсуляции);
  • Передача готового Trustee в класс Win32_Ace (третий этап "инкапсуляции");
  • Передача сформированного ACE в класс Win32_SecurityIdentifier (четвёртый этап "инкапсуляции");
  • Передача уже сформированного SecurityDescriptor в метод SetShareInfo;
  • И применение самого метода SetShareInfo для записи параметров безопасности (прав доступа) в ACL самой шары.

Вот так выглядит общая схема последовательности действий, которая является шаблоном для данной операции.

А теперь продолжим. Теперь нам нужно преобразовать права доступа в класс FileSystemRights. Преобразование типа прав так же было ранее мною описано в первой части Управление ACL в PowerShell. Пара слов о том, чем отличаются права Read, Change и Full Control в контексте разрешений сетевого ресурса от контекста разрешений NTFS. Этим правам в NTFS сопоставляются Read + Execute, Modify и Full Control соответственно и плюс право Synchonize. Поэтому давайте запишем маску доступа в переменную $ace:

$ace.AccessMask = [System.Security.AccessControl.FileSystemRights]"Modify, Synchronize"

Ещё в начале мы объявили переменную $mode и вписали текстовое значение типа доступа. Здесь я его использовать не буду, а лишь преобразую его сразу в аналог FileSystemRights. Теперь в этот класс нужно передать тип доступа (Allow/Deny) в класс Win32_Ace. первой части Управление ACL в PowerShell я показывал команду, которая позволяет перечислять доступные значения свойств класса. Применим его снова, но в контексте AceType

[system.enum]::getnames([System.Security.AccessControl.AceType])

И в этом списке нас будут интересовать только первые 2

  • AccessAllowed;
  • AccessDenied.

Их можно указывать по порядковым номерам. Например, 0 будет означать AccessAllowed, а 1 - AccessDenied. Теперь заканчиваем второй этап и делаем третий этап нашей "инкапсуляции":

$ace.AceType = 0 
$ace.Trustee = $Trustee

Далее, следуя нашему шаблонному алгоритму нужно сформированную переменную $ace передать в класс Win32_SecurityDescriptor. Здесь нам нужно будет заполнить только свойство DACL, которое будет содержать массив параметров безопасности из переменной $ace:

$SD.DACL = $ace

Ну что ж, вот теперь у нас полностью готов объект SecurityDescriptor для применения его в качестве параметра для метода SetShareInfo. Для начала нам нужно указать шару, для которой будет применяться данный метод:

$share = get-WmiObject win32_share -filter "name='$share'"

Теперь нужно получить шаблон набора свойств для метода SetShareInfo:

$inParams = $share.PsBase.GetMethodParameters("SetShareInfo")

Так мы получили форму с необходимыми свойствами и типами данных, которые мы должны заполнить. Метод SetShareInfo использует следующий синтаксис (приведено из ссылки MSDN):

uint32 SetShareInfo(
  [in]            uint32 MaximumAllowed,
  [in, optional]  string Description,
  [in]            Win32_SecurityDescriptor Access
);

Это нам говорит, что требуется именно параметр Access, который нужно заполнить данными из класса Win32_SeceurityDescriptor. У нас уже есть переменная $SD, в которой заполнен параметр DACL. Вот теперь эту всю переменную запишем в качестве параметра Access в форму набора свойств:

$inParams.Access = $SD

Ну вот и всё. Теперь нам только осталось записать всё это в нашу сетевую папку:

$share.PsBase.InvokeMethod("setshareinfo", $inParams, $null)

Здесь я указал шару и использовал PsBase.InvokeMethod, чтобы вызвать метод SetShareInfo и после указания метода перечислил передаваемые прараметры Ну и конечный результат всех наших терзаний в едином целом:

$share="UserShare"
([wmiClass] 'Win32_share').Create("C:\Test", $share, "0", "100", "Network share for users")
$user = "Domain Users"
$mask = "Change"
$SD = ([WMIClass] "Win32_SecurityDescriptor").CreateInstance()
$ace = ([WMIClass] "Win32_Ace").CreateInstance()
$Trustee = ([WMIClass] "Win32_Trustee").CreateInstance()
$SID = (new-object security.principal.ntaccount $user).translate([security.principal.securityidentifier])
[byte[]] $SIDArray = ,0 * $SID.BinaryLength
$SID.GetBinaryForm($SIDArray,0)
$Trustee.Name = $user
$Trustee.SID = $SIDArray
$ace.AccessMask = [System.Security.AccessControl.FileSystemRights]"Modify, Synchronize"
$ace.AceType = 0 
$ace.Trustee = $Trustee
$SD.DACL = $ace
$share = get-wmiObject win32_share -filter "name='$share'"
$inParams = $share.psbase.GetMethodParameters("SetShareInfo")
$inParams.Access = $SD
$share.psbase.invokemethod("setshareinfo", $inParams, $null)

Ну и, собственно, вторая цель, которой мы добивались:

[C:\] gwmi Win32_LogicalShareSecuritySetting | fl [a-z]*

Caption          : Security settings of UserShare
ControlFlags  : 32772
Description    : Security settings of UserShare
Name             : UserShare
SettingID       :

Caption        : Security settings of CertEnroll
ControlFlags : 32772
Description   : Security settings of CertEnroll
Name           : CertEnroll
SettingID      :

Caption        : Security settings of SYSVOL
ControlFlags : 32772
Description   : Security settings of SYSVOL
Name           : SYSVOL
SettingID      :

Caption        : Security settings of NETLOGON
ControlFlags : 32772
Description   : Security settings of NETLOGON
Name           : NETLOGON
SettingID      :

 

Вот теперь мы увидели нашу шару через Win32_LogicalShareSecuritySetting и благодаря чему в последующем можно будет работать с DACL списком этого сетевого ресурса. Как говорит /\/\o\/\/ - Enjoy! :)

Кстати, хотелось бы отметить, что по этому вопросу /\/\o\/\/ в своё время проделал очень большую работу, как часть вопроса управления безопасностью объектов в Windows и значительная часть материала была написана с использованием его наработок.

Давайте, теперь поговорим о том, как добавить несколько участников безопасности. Суть процесса не изменится. Для решения данной задачи я воспользуюсь следующими приёмами:

  • циклом foreach;
  • hashtables;
  • конструкцией switch.

Если посмотреть код выше, то для добавления других пользователей/групп в Share Permissions, то нетрудно предположить, что нам нужно записать в $SD.DACL столько ACE, сколько пользователей/групп нам требуется завести. Следовательно, тут нужен цикл. Имена пользователей/групп и назначемые им права будут выбираться из hashtables. Hashtables - по сути является массивом сопоставлений. Он представляется двумя столбцами - именем параметра в первом столбце и его значением во втором. В нашем случае в качестве параметра будет выступать пользователь/группа, а его значение будет - тип предоставляемого доступа. Hashtables, на мой вгляд, хорошо описаны в книге PowerShell in Action (сегодня постараюсь не забыть прикрепить к блогу список полезной литертуры по PowerShell). Т.к. набор прав у нас будет для каждого пользователя/группы разный, поэтому я применю конструкцию Switch, которая будет сопоставлять текстовому значению типа доступа его значению в классе FileSystemRights.

Итак, давайте проведём анализ, какую часть кода нам нужно поместить в цикл для многократной обработки и получения для пользователя собственного ACE. У нас индивидуальным для каждого пользователя будет SID, Trustee, ACE. Остальное же будет универсальным (общим) для всех пользователей. Вот его и поместим в цикл foreach. Но для foreach нужно передать параметры - имя пользователя/группы и маску доступа (тип предоставляемого доступа). Для этого, как я уже говорил, нам потребуется Hashtables. Общий формат хэш-таблиц выглидт следующим образом:

<hashLiteral> = '@{' <keyExpression> '=' <pipeline> [ <separator> <keyExpression> '=' <pipeline> ] * '}'
<separator> = ';' | <newline>

Поэтому изменив предыдущий код мы заменим переменные $user и $mode на одну переменную $user с указанием имён пользователей и маски предоставляемого доступа согласно формату хэш-таблиц:

$user = @{"Everyone" = "Read"; "Administrators" = "FullControl"; "Domain Users" = "Change"}

Если после этой строки в консоли набрать $user, то мы получим нашу хэш-таблицу:

Name                           Value
----                              -----
Everyone                      Read
Domain Users               Change
Administrators               FullControl

Теперь готовим цикл foreach:

$user.keys | foreach {

Мы передали содержимое хэш-таблицы в цикл. Теперь нужно изменить несколько строк кода. А именно: мы передадим в цикл сперва имя пользователя/группы. Для этого нужно изменить строчку преобразования аккаунта, чтобы туда при каждой итерации цикла попадало новое имя пользователя:

$SID = (new-object security.principal.ntaccount $_ ).translate([security.principal.securityidentifier])

В комбинацию "$_" будут попадать только значения столбца Name из нашей хэш-таблицы (если указать, как и раньше $user, то туда попадёт вся хэш-таблица и скрипт даст ошибку преобразования аккаунта). Далее, у нас будет передаваться индивидуальная маска доступа. В предыдущем примере мы использовали всего лишь одну строчку, которая начиналась с $ace.AccessMask. В том примере было всё просто - один пользователь = одна маска. Теперь пользователей/групп уже 3 и маски тоже 3. Здесь мы воспользуемся конструкцией Switch, которая будет принимать наше текстовое значение искать ему сопоставление внутри конструкции и выполнять только одно действие:

switch ($($user[$_])) {
  "FullControl"   {$ace.AccessMask = [System.Security.AccessControl.FileSystemRights]"FullControl, Synchronize"}
  "Change" {$ace.AccessMask = [System.Security.AccessControl.FileSystemRights]"Modify, Synchronize"}
  "Read"   {$ace.AccessMask = [System.Security.AccessControl.FileSystemRights]"ReadAndExecute, Synchronize"}
  }

Я выделил $($user[$_]), для того, чтобы показать, как выбирать только значения из столбца Value нашей хэш-таблицы. Т.к. эта часть кода находится в теле цикла foreach, то сюда будет подставляться новое значение с каждой итерацией. Ну и последний, завершающий штрих - запись нескольких ACE в массив DACL. Если в предыдущем примере мы делали просто

$SD.DACL = $ace

То теперь такой вариант не подойдёт. А почему? А потому, что операция присвоения каждый раз будет заменять значение DACL последним ACE. Т.к. DACL у нас является массивом данных Win32_Ace, то для добавления новых ACE воспользуемся оператором добавления - "+="

$SD.DACL += $ace

Вот теперь в SD.DACL каждый новый ACE будет добавляться как новый элемент массива. Кстати, говоря, именно этой строчкой мы завершаем цикл foreach, т.к. SecurityDescriptor у нас уже готов и его уже можно как обычно отправлять дальше в код.

Подытожив всю полученную информацию мы в конечном итоге получим вот такой код:

$share="UserShare"
([wmiClass] 'Win32_share').Create("C:\Test", $share, "0", "100", "Network share for users")
$user = @{"Everyone" = "Read"; "Administrators" = "FullControl"; "Domain Users" = "Change"}
$SD = ([WMIClass] "Win32_SecurityDescriptor").CreateInstance()
$ace = ([WMIClass] "Win32_Ace").CreateInstance()
$Trustee = ([WMIClass] "Win32_Trustee").CreateInstance()
$user.keys | foreach {
$SID = (new-object security.principal.ntaccount $_ ).translate([security.principal.securityidentifier])
[byte[]] $SIDArray = ,0 * $SID.BinaryLength
$SID.GetBinaryForm($SIDArray,0)
$Trustee.Name = $_
$Trustee.SID = $SIDArray
switch ($($user[$_])) {
  "FullControl"   {$ace.AccessMask = [System.Security.AccessControl.FileSystemRights]"FullControl, Synchronize"}
  "Change" {$ace.AccessMask = [System.Security.AccessControl.FileSystemRights]"Modify, Synchronize"}
  "Read"   {$ace.AccessMask = [System.Security.AccessControl.FileSystemRights]"ReadAndExecute, Synchronize"}
  }
$ace.AceType = 0
$ace.Trustee = $Trustee
$SD.DACL += $ace.psobject.baseobject}
$share = Get-WmiObject win32_share -filter "name='$share'"
$inparams = $share.psbase.GetMethodParameters("SetShareInfo")
$inParams.Access = $SD
$share.psbase.invokemethod("setshareinfo", $inParams, $null)

Вот так немного модифицировав код мы добились более широкого функционала, а именно - возможность добавления множества ACE в едином теле скрипта.

Здесь я переступил логику. По логике следовало сначала разобрать чтение Share Permissions, а потом уже запись, но ввиду одного момента, о котором я рассказал в начале пришлось сначала изучить возможность установки DACL, а потом уже чтение их из Share Permissions. Об этом я уже расскажу следующий раз. Ждите продолжения :)

评论

请稍候...
很抱歉,您输入的评论太长。请缩短您的评论。
您没有输入任何内容,请重试。
很抱歉,我们当前无法添加您的评论。请稍后重试。
若要添加评论,需要您的家长授予您相应权限。请求权限
您的家长禁用了评论功能。
很抱歉,我们当前无法删除您的评论。请稍后重试。
您已超过了一天之内允许提供的评论数上限。请在 24 小时后重试。
因为我们的系统表明您可能在向其他用户提供垃圾评论,您的帐户已禁用了评论功能。如果您认为我们错误地禁用了您的帐户,请联系 Windows Live 支持部门
完成下面的安全检查,您提供评论的过程才能完成。
您在安全检查中键入的字符必须与图片或音频中的字符一致。
PodānsVadi​ms 在此页禁用了评论功能。

引用通告

此日志的引用通告 URL 是:
http://vpodans.spaces.live.com/blog/cns!BB1419A2CFC1E008!170.trak
引用此项的网络日志