Posts on this page:
Наткнулся на него когда готовил ответ для ньюсгрупп. Как известно, владелец объекта может спокойно изменять списки ACL объектов минуя все их ограничения пользуясь неоткланяемым правом владения объектом. Поэтому, если из ACL объекта удалить все ACE и сохранить, то мы из проводника можем в любой момент вызвать вкладку Security объекта и задать требуемые ACE. Однако, это возможно только из графического интерфейса проводника сделать. При использовании скрипта и командлета Set-Acl мы этого сделать не сможем. Продемонстрирую проблему:
[C:\] whoami
contoso\administrator
[C:\] Get-Acl C:\Test | fl
Path : Microsoft.PowerShell.Core\FileSystem::C:\Test
Owner : CONTOSO\administrator
Group : CONTOSO\Domain Users
Access :
Audit :
Sddl : O:LAG:DUD:PAI
[C:\] $acl = Get-Acl C:\Test
[C:\] $accessrule = New-Object System.Security.AccessControl.FileSystemAccessRule("Administrator","FullControl", "Allow"
)
[C:\] $acl.AddAccessRule($accessRule)
[C:\] $acl | Set-Acl C:\Test
Set-Acl : Attempted to perform an unauthorized operation.
At line:1 char:15
+ $acl | Set-Acl <<<< C:\Test
+ CategoryInfo : PermissionDenied: (C:\Test:String) [Set-Acl], UnauthorizedAccessException
+ FullyQualifiedErrorId : System.UnauthorizedAccessException,Microsoft.PowerShell.Commands.SetAclCommand
[C:\]
Как видите, текущий пользователь (администратор) является владельцем папки. Но все ACE в ACL пустые (секция Access). Пользуясь правами владельца, всё же, я не могу ничего сделать с этим списком. Как выяснилось в процессе исследования, пользователю-владельцу, который собирается менять ACL необходимо иметь явно назначенное или унаследованное разрешение TakeOwnership. Никаких Read/ChangePermissions и прочих не надо. Теперь я из проводника добавлю себе право TakeOwnership и попробую снова исполнить код:
[C:\] Get-Acl C:\Test | fl
Path : Microsoft.PowerShell.Core\FileSystem::C:\Test
Owner : CONTOSO\administrator
Group : CONTOSO\Domain Users
Access : CONTOSO\administrator Allow TakeOwnership, Synchronize
Audit :
Sddl : O:LAG:DUD:PAI(A;OICI;0x180000;;;LA)
[C:\] $acl = Get-Acl C:\Test
[C:\] $accessrule = New-Object System.Security.AccessControl.FileSystemAccessRule("Administrator","FullControl", "Allow"
)
[C:\] $acl.AddAccessRule($accessRule)
[C:\] $acl | Set-Acl C:\Test
[C:\] Get-Acl C:\Test | fl
Path : Microsoft.PowerShell.Core\FileSystem::C:\Test
Owner : CONTOSO\administrator
Group : CONTOSO\Domain Users
Access : CONTOSO\Administrator Allow TakeOwnership, Synchronize
CONTOSO\Administrator Allow FullControl
Audit :
Sddl : O:LAG:DUD:PAI(A;OICI;0x180000;;;LA)(A;;FA;;;LA)
[C:\]
Как видите, теперь всё получилось. Я не понимаю, зачем мне нужно иметь право TakeOwnership, чтобы изменить ACL, если я являюсь владельцем. В реальной среде это может вызвать определённые трудности, как неадекватное поведение скрипта, который будет сыпать ошибками (в связи с чем появился вопрос на ньюсгруппах).
Зато мною не очень любимый WMI справился с задачей на ура, израсходовав кода при этом во много раз больше, чем с использованием командлетов. И в итоге получилось вот:
$path = "C:\Test" $user = "Administrator" $path = $path.replace("\", "\\") $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]"FullControl" $ace.AceFlags = "0x3" $ace.AceType = 0 $ace.Trustee = $trustee # читаем текущий ACL с объекта $oldDACL = (gwmi Win32_LogicalFileSecuritySetting -filter "path='$path'").GetSecurityDescriptor().Descriptor.DACL # добавляем его к пустому объекту DACL $SD.DACL = $oldDACL # и добавляем новый ACE $SD.DACL += @($ace.psobject.baseobject) # устанавливаем флаг SE_DACL_PRESENT $SD.ControlFlags = "0x4" $folder = gwmi Win32_LogicalFileSecuritySetting -filter "path='$path'" $folder.setsecuritydescriptor($SD)
Я больше склонен доверять WMI и констатировать факт, что он работает честно – даёт мне делать с объектом что угодно и как угодно признавая мои права владения.
Я считаю, что Set-Acl был сильно не прав, отказав мне в изменении пустых ACE, поэтому отрепортил это дело на connect:
https://connect.microsoft.com/feedback/ViewFeedback.aspx?FeedbackID=418906&SiteID=99
Update 04.09.2009: относительно пункта 3 данного поста обязательно прочтите обновление информации по нему: Секреты Software Restriction Policies (часть 3) (так же прочитать пункт 3). Т.е. вы должны исключить LNK файлы из проверки политикой и, следовательно, исключения для них создавать не надо.
Так же прочтите обновление по этой же ссылки относительно использования переменных окружения в правилах SRP.
Наблюдая за сообщениями на форумах (а так же по комментариям в моём блоге) пришёл к выводу, что очень немногие понимают всю значимость Software Restriction Policies и далеко не каждый обладает навыками эффективного управления данной политикой. Я уже освещал некоторые вопросы по данной теме, но тем не менее пробелы до сих пор есть, а так же есть новые трюки для обхода политики.
Итак, для тех, кто ещё не в курсе, о чём идёт речь предлагается прочитать:
В данном посте я планирую раскрыть секреты расширения SCF, трюки с hh.exe, рассмотреть вопросы унификации правил и два важных момента в управлении политикой.
1) Расширение SCF – это командный файл, который обрабатывается стандартным шеллом (он же explorer.exe). Данные файлы достаточно хитры, поскольку проводник (explorer) даже в режиме показа известных расширений никогда не показывает его. Это ещё пол беды. Основная угроза, которая может от них исходить – встраиваемость в файлы. В двух словах это выглядит как прописывание своего кода в любой файл (будь то текстовый, графический, медиафайл и т.д.), подмена расширения и иконки. Синтаксис SCF файлов очень похож на синтаксис INI и INF файлов. Т.е. он состоит из секций, заключённых в квадратные скобки и элементы секций:
[section]
parameter=value
[section2]
parameter2=value2
parameter3=value3
Самый знакомый нам файл такого типа – Show Desktop в панели quick launch. Это самый настоящий SCF файл, который имеет следующее содержание:
[shell]
Command=2
IconFile=explorer.exe,3
[TaskBar]
Command=ToggleDesktop
как видно отсюда, мы можем изменять иконку файла на любую. Очень полезно для маскировки файла под другой тип файла. И в секции TaskBar мы видим вызов внутренней команды explorer.exe – ToggleDesktop. Напомню, что эти файлы обрабатываются только оболочкой explorer.exe. Если сохранить эту часть в текстовом файле и указать расширение SCF, то вы получите сворачивалку окон. Если, к примеру, в секции Command указать другую команду, например, Explorer, то при двойном клике на него запустится сам explorer.exe:
[shell]
Command=2
IconFile=explorer.exe,3
[TaskBar]
Command=Explorer
А теперь как встраивать код в другие файлы. Берём любой произвольный файл, например картинку в формате JPEG, открываем его в текстовом редакторе, но лушче будет в FAR и в самое начало или самый конец файла пишем:
[shell]
Command=2
IconFile=imageres.dll,66
[TaskBar]
Command=Explorer
сохраняем изменения и переименовываем файл в image.jpeg.scf.В Windows Vista/Windows Server 2008 мы получим файл размером с картинку, подходящее расширение и значок JPEG файла (хвост .scf будет скрыт от нас в любом случае). Если по нему нажать 2 раза, то запустится explorer. Причём никаких ошибок вы не получите, потому что остальное содержимое файла будет проигнорировано и будет выполнена только та часть кода, которую мы добавили. Искать иконки можно различными редакторами ресурсов, как Restorator или ResHacker. Просто в категории Icon или IconGroup выбираете нужный номер иконки и вставляете этот номер в параметр значка. При этом скорее всего потребуется уменьшить это число на единицу, поскольку Explorer будет считать значки с нуля, в то время как редакторы ресурсов считают с единицы. Но это уже детали.
К сожалению я в данный момент не обладаю сведениями о полном функционале файлов SCF, но доподлинно известно, что ими можно манипулировать как оболочкой explorer.exe, так и браузером Internet Explorer. Поэтому я пока не могу сказать на сколько это может быть опасно. Но неадекватное поведение файла при его запуске может доставить массу хлопот как самим пользователям, так и администраторам.
Следовательно имеет смысл при развёртывании политики SRP включить данное расширение в список блокируемых, а значок Show Desktop.scf разрешить по хэшу! Это важно, поскольку разрешение пути позволит пользователю модифицировать файл и исполнить его.
Примечание: в новых версиях Windows начиная от Windows Vista значок ShowDesktop.scf был сменён с SCF файла на Show Desktop.lnk, следовательно для этих версий ОС достаточно будет просто включить данное расширение в список и всё. Хотя SCF файл в Windows Vista будет работать :)
2)hh.exe – исполняемый файл, который обрабатывает фалы справки CHM. CHM – это скомпилированный HTML файл, следовательно может содержать потенциально уязвимый код. Но тут, казалось бы, бояться нечего, поскольку расширение CHM по умолчанию мониторится политикой SRP и его запуск из пользовательского окружения недоступен. Но это не совсем так. В действительности пользователь может обойти ограничение SRP и спокойно запускать любые CHM файлы. Обходится данное ограничение очень просто:
Start –> Run… –> hh.exe path\helpfile.chm
и всё. К сожалению, я не нашёл метода, как гибко бороться с этим, только явно запрещать политикой SRP запуск программы hh.exe. Ещё к бОльшему сожалению блокировать CHM файлы в системах до Windows Vista тяжело, поскольку почти вся справка Windows там написана в CHM. Однако, в Windows Vista и выше встроенная справка уже не базируется на CHM файлах, поэтому в них мы можем со значительно меньшими последствиями просто блокировать hh.exe. Но тут можно упереться в вопрос совместимости сторонних приложений, которые зачастую содержат справку в CHM файлах. Поэтому, я не призываю блокировать hh.exe, но просто исследовать данный вопрос в каждом индивидуальном случае отдельно.
Примечание: на данный момент мне неизвестно о других расширениях, которые подвержены данной проблемы. Я тестировал набор расширений, но добиться подобного результата не удалось. Хочется надеяться, что это единственное такое хитрое расширение :)
3) унификация и стандартизация создания исключений в политику SRP. Прочитав топик на форуме TechNet – сслыка, я в очередной раз пришёл к мнению, что человек первый раз столкнувшись с SRP не обладает навыками эффективного создания исключений. Это и не удивительно вобщем, поэтому я в темах про SRP ставлю перед собой задачу – рассказать про Best Practices для эффективной реализации этой политики.
За время практики я (и не только) смог выработать для себя общий концепт создания исключений, который звучит примерно так: следует предельно максимально использовать переменные окружения и ключи реестра вместо абсолютных путей.
Политика SRP даёт нам широкие возможности для этого, это и использование системных переменных окружений %variable%, но и чтение этих переменных из реестра. Повторюсь, что данная концепция решает несколько задач, как унификация правил для различных платформ и обход локализованных барьеров. В качестве примеров можно привести различия некоторых стандартных путей в ОС Windows XP и Windows Vista.
Чтобы администраторам дать хорошую отправную точку, я решил поделиться стандартным набором разрешений, который будет отвечать следующим требованиям:
И вот такой список исключений нам потребуется:
Unrestricted - %systemroot%
Unrestricted - %programfiles%
Unrestricted - %userprofile%\Application Data\Microsoft\Internet Explorer\Quick Launch\*.lnk – (Windows XP/2003 only)
Unrestricted - %HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\AppData%/Microsoft/Internet Explorer/Quick Launch/*.lnk – (Windows Vista/2008 или выше)
Unrestricted - %HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Desktop%*.lnk
Unrestricted - %HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Programs%*.lnk
Unrestricted - %HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Programs%*/*.lnk
Unrestricted - %HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Programs%*/*/*.lnk
Unrestricted - %HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Common Administrative Tools%*.lnk
Unrestricted - %HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Common Desktop%*.lnk
Unrestricted - %HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Common Programs%*.lnk
Unrestricted - %HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Common Programs%*/*.lnk
Unrestricted - %HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Common Programs%*/*/*.lnk
Dissalowed - %HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Printers%
Dissalowed - %systemroot%\Temp
Здесь хочу сделать несколько комментариев:
Примечание: для бизнес-приложений, которые как правило инсталлируются на отличный от системного том (обычно D:\) и для них рекомендуется делать исключения по хешу, нежели по пути. Это полезно по двум причинам:
4) обновление ярлыков временного отключения и включения SRP. Я считаю удобным держать на рабочем столе администратора ярлыки, которые запускают REG файлы, которые в свою очередь временно отключают и возвращают политику в исходную позицию. Если для одной рабочей станции – это менее критично, то в условиях домена отключать политику для установки/обновления ПО на уровне GPO – крайне нецелесообразно. Я уже публиковал в своём предыдущем блоге тексты REG файлов, но без учёта папки Temp.
Т.к. мы явно запретили запуск недозволенных расширений из папки C:\Windows\Temp, то даже временная деактивация политики ярлыками не позволит нам сделать некоторые действия, например, установка драйвера или приложения, если установщик сперва распаковывает INF файлы в эту папку, поскольку перевод глобального режима политики в Unrestricted не отменит этот явный запрет. Следовательно мы помимо перевода политики в Unrestricted должны вывести администраторов из под действия политики. Вот REG файлы для временного отключения и включения политики:
SRP_Disable.reg
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Safer\CodeIdentifiers]
"DefaultLevel"=dword:00040000
"PolicyScope"=dword:00000001
SRP_Enable.reg
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Safer\CodeIdentifiers]
"DefaultLevel"=dword:00000000
"PolicyScope"=dword:00000000
5) обновление политики. В процессе работы с ярлыками обнаружился один неприятный факт: если отключить политику реестром и не включить обратно, то после перезагрузки машины кроме включения политики реестром обратно потребуется выполнить ещё 2 перезагрузки. Как вы, наверное, поняли, эти ключи реестра не изменяют саму политику, а только её поведение. Следовательно пришлось решать вопрос возвращения политики в исходное состояние при перезагрузках при помощи групповых политик. По умолчанию, политики перезаписывают соответствующие ключи реестра только при изменение состояния политики (чего мы ярылками не делаем). Т.к. SRP у нас основывается на реестре (все её правила и настройки хранятся именно в реестре), то нужно задать принудительное переприменение политики при перезагрузках (или периодических обновлений политики в домене). Делается это в Group Policy:
Computer Configuration\Administrative Templates\System\Group Policy\Registry policy processing
и этот элемент необходимо выставить в Enabled внутри поставить галочку в Process even if the Group Policy objects have not changed. В таком случае даже если администратор забудет реестром обратно включить политику SRP, то политика процессинга сама вернёт её в исходное состояние – Disallowed.
-------------------
Сегодня мы рассмотрели очередную партию насущных вопросов по настройке и управлению политикой Software Restriction Policies. Я недеюсь, что этот материал будет полезен как начинающим (тех, кто только начинает работать с политикой SRP) и опытным администраторам, которые уже используют политики SRP в своих сетях.
В предыдущем посте я показал, как можно легко и удобно подписывать скрипты и как усилить безопасность их исполнения, а так же предупредить несанкционированное изменение кода. Но, как я уже отметил, подписывать каждый раз скрипт из консоли не особо удобно. Например, если вы занимаетесь финальной отладкой скрипта в редакторе, как PowerGUI или PowerShell ISE, то в конце отладки вы просто сохраняете скрипт и закрываете редактор. Понятно, что ещё отдельно запускать консоль PowerShell будет неудобно. Поэтому я написал небольшой скрипт с установщиком, который позволит из проводника по правому нажатию на PS1 файл вы сможете подписывать скрипты.
Чтобы поместить свой элемент в контекстное меню при нажатии на PS1 файлы (на других типах файлов его не будет) нам нужно добавить некоторые значения в реестр по пути:
HKLM\Software\Classes\Microsoft.PowerShellScript.1\Shell
подключ с названием нашего элемента – Sign It и в нём уже команду, которая будет отрабатываться при нажатии на него. Я решил не изобретать велосипед и воспользовался приёмами, которые использовал ещё в старом блоге: Полезная безделушка Hash SHA1 на PowerShell. Шапка по сути осталась та же, только тело (исполняемый модуль) изменился. И вот, что получилось для PowerShell 1.0 (для V2 опубликую ниже):
##################################################################### # Sign PS1 Script.ps1 # Version 1.5 # # Automatically sign PowerShell script from .PS1 file context menu # Fully compatible with PowerShell 1.0 # # Vadims Podans (c) 2009 # http://www.sysadmins.lv/ ##################################################################### $FilePath = Read-Host "Specify path to hold SignIt.PS1" if (Test-Path $FilePath) { $FilePath = $FilePath + "\" + "SignIt.ps1" $RegPath = "Registry::HKLM\Software\Classes\Microsoft.PowerShellScript.1\Shell\Sign It\command" $RegValue = "C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe -noninteractive -noprofile -command $FilePath '%1'" New-Item -Path $RegPath -Force -ErrorAction SilentlyContinue if (Test-Path $RegPath) { New-ItemProperty -Path $RegPath -Name "(Default)" -Value $RegValue New-Item -ItemType file -Path $FilePath -Force if (Test-Path $FilePath) { $exefile = 'param ($file) $cp = new-object Microsoft.CSharp.CSharpCodeProvider $cpar = New-Object System.CodeDom.Compiler.CompilerParameters $HideWindow = 0x0080 $ShowWindow = 0x0040 $Code = @" using System; using System.Runtime.InteropServices; namespace Win32API { public class Window { [DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags); } } "@ $cp.CompileAssemblyFromSource($cpar, $code) $PSHandle = (Get-Process –id $pid).MainWindowHandle [Win32API.Window]::SetWindowPos($PSHandle, 0, 0, 0, 0, 0, $HideWindow) function _msgbox_ ($title, $text, $type = "None") { [void][reflection.assembly]::LoadWithPartialName("System.Windows.Forms") $msg = [Windows.Forms.MessageBox]::Show($text, $title, [Windows.Forms.MessageBoxButtons]::ok, [Windows.Forms.MessageBoxIcon]::$type) } $cert = @(dir cert:\currentuser\my -codesigning)[0] if (!$cert) { _msgbox_ -title "Error" -text "Here is no valid signing certificate!" -type "Error" exit } $status = Set-AuthenticodeSignature "$file" $cert -TimestampServer "http://timestamp.verisign.com/scripts/timstamp.dll" if ($status.status -eq "Valid") { _msgbox_ -title "Success" -text "Script is now signed! Enjoy!" } else { _msgbox_ -title "Error" -text $status.StatusMessage -type "Error" } exit' Set-Content -Path $FilePath -Value $exefile &$filepath $filepath } else { Write-Warning "Unable to create file in: $FilePath. Path is incorrect or you haven't sufficient permissions" } } else { Write-Warning "Unable to create registry key. May be you haven't sufficient permissions to HKLM hive" } }
Данный скрипт является установщиком. Для реализации всех функций достаточно запустить этот скрипт и по требованию указать путь, где будет находиться рабочий скрипт (содержимое переменной $exefile).
Примечание: такой метод установки справедлив только для V2. При использовании PowerShell 1.0 установщик следует запускать непосредственно из консоли PowerShell.
Заметка: Здесь следует обратить внимание на блок кода, который идёт после PARAM в переменной $exefile. Данный блок кода скрывает саму консоль PowerShell и показывает только графическую часть, которая исполняется из скрипта. Функция скрытия консоли честно взята из блога Васи Гусева: Win32 API из PowerShell 1.0. Данный метод работает как в PowerShell V2 CTP3, так и в PowerShell 1.0.
И вариант скрипта для PowerShell V2:
##################################################################### # Sign PS1 Script.ps1 # Version 1.5 # # Automatically sign PowerShell script from .PS1 file context menu # # Require PowerShell V2, not compatible with PowerShell 1.0 # # Vadims Podans (c) 2009 # http://www.sysadmins.lv/ ##################################################################### #requires -Version 2.0 $FilePath = Read-Host "Specify path to hold SignIt.PS1" if (Test-Path $FilePath) { $FilePath = $FilePath + "\" + "SignIt.ps1" $RegPath = "Registry::HKLM\Software\Classes\Microsoft.PowerShellScript.1\Shell\Sign It\command" $RegValue = "C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe -WindowStyle Hidden -noprofile -command $FilePath '%1'" New-Item -Path $RegPath -Force -ErrorAction SilentlyContinue if (Test-Path $RegPath) { New-ItemProperty -Path $RegPath -Name "(Default)" -Value $RegValue New-Item -ItemType file -Path $FilePath -Force if (Test-Path $FilePath) { $exefile = 'param ($file) function _msgbox_ ($title, $text, $type = "None") { [void][reflection.assembly]::LoadWithPartialName("System.Windows.Forms") $msg = [Windows.Forms.MessageBox]::Show($text, $title, [Windows.Forms.MessageBoxButtons]::ok, [Windows.Forms.MessageBoxIcon]::$type) } $cert = @(dir cert:\currentuser\my -codesigning)[0] if (!$cert) { _msgbox_ -title "Error" -text "Here is no valid signing certificate!" -type "Error" exit } $status = Set-AuthenticodeSignature "$file" $cert -TimestampServer "http://timestamp.verisign.com/scripts/timstamp.dll" if ($status.status -eq "Valid") { _msgbox_ -title "Success" -text "Script is now signed! Enjoy!" } else { _msgbox_ -title "Error" -text $status.StatusMessage -type "Error" } exit' Set-Content -Path $FilePath -Value $exefile &$filepath $filepath } else { Write-Warning "Unable to create file in: $FilePath. Path is incorrect or you haven't sufficient permissions" } } else { Write-Warning "Unable to create registry key. May be you haven't sufficient permissions to HKLM hive" } }
Так же, после копирования файла установщик попытается сразу подписать рабочий скрипт, чтобы этого не пришлось делать потом вручную. После этого в контекстном меню PS1 файлов будет элемент Sign It:
И если у пользователя есть сертификат для подписи скриптов, то при нажатии на этот элемент получите сообщение:
В противном случае получите ошибку:
Вот так можно значимо упростить процесс подписывания скриптов не открывая консоль PowerShell. Но если файлов будет много, то ничего не мешает основную часть кода засунуть в цикл Foreach. Например:
$cert = @(dir cert:\currentuser\my -codesigning)[0] dir -Include *.ps1 -Recurse | %{Set-AuthenticodeSignature "$_" $cert}
На этом пока о подписывании скриптов всё, что я хотел рассказать.
Update 30.08.2009: немного переделал скрипты с учётом следующих поправок:
И, собственно, кнопки для скачивания скриптов:
Как известно практически всем пользователям PowerShell, в целях безопасности была введена политика запуска скриптов. Которая имеет 4 режима:
В версии V2 (пока ещё CTP3) скрипты PowerShell приравняли к исполняемым файлам и эти файлы стали неотключаемо мониториться политикой Software Restriction Policies. Я об этом уже писал ранее: PowerShell V2 и Software Restriction Policies. Поначалу меня это сильно напрягало, но после пришёл к мнению, что это правильно. Неправильно только то, что мы этого не видим (ps1 расширение нигде не фигурирует). С этим бороться можно двумя методами – явно указывать пути, откуда разрешён запуск .ps1 файлов или подписать их все.
Если компьютеров в сети больше одного, то самое идеальное для решения задачи будет наличие домена Active Directory и, по возможности, Enterprise Certification Authity (CA). Наличие домена решит массу задач, как распространение политики SRP в пределах домена, распространение сертификата в пределах домена, контроль версии сертификата, которым подписаны скрипты.
Итак, для начала нам нужно получить сертификат, которым будут подписываться скрипты. В целях безопасности следует создать ограниченную учётную запись пользователя из под которой администратор (в большинстве случаев) будет подписывать скрипты. В книге PowerShell In Action для генерации сертификата предлагается использовать makecert.exe, который входит в состав Visual Studio SDK, но как мне кажется более правильным будет использование CA. В Windows Server CA для этих целей есть уже готовый шаблон, который называется Code Signing. Но принципиальной разницы нету, каким инструментом вы будете генерировать сертификат и я опишу процесс получения сертификата с использованием доменного CA.
Если CA у нас уже установлен, то открываем оснастку Certification Authority и переходим в раздел Certificate Templates. Нажимаем правой кнопкой и выбираем Manage. Откроется редактор шаблонов. Если у вас Enterprise или Datacenter редакции Windows Server, то вы можете создать свой настроенный шаблон. Но я не вижу в этом необходимости. На данном этапе нам необходимо разрешить ограниченному пользователю запрашивать сертификаты этого шаблона. Для этого в закладке Security шаблона Code Signing нужно разрешить чтение и запрос сертификата ограниченному пользователю. Когда эта процедура проделана, редактор шаблонов можно закрыть. После чего в оснастке Certification Authority снова нажать правой кнопкой на разделе Certificate Templates –> New –> Certificate Template to Issue и в списке выбрать шаблон Code Signing.
После чего нужно залогиниться этим пользователем, запустить оснастку Certificate Manager (Start –> Run… –> certmgr.msc) и выполнить запрос сертификата. В списке шаблонов должен быть и добавленный нами Code Signing. Когда сертификат будет запрошен не надо закрывать оснастку сертификатов. Далее нам потребуется экспортировать открытую часть сертификата в x509 файл (с расширением .cer). Экспорт открытой части нам потребуется для проверки подписи и организации доверия подписи в пределах домена. Экспортированный сертификат необходимо теперь доставить администратору(-ам), который отвечает за групповую политику.
В групповых политиках (чаще всего в доменной политике) необходимо создать новую политику Software Restriction Policies и в Additiona Rules добавить правило сертификата (Certificate Rule) и указать экспортированный сертификат. Это необходимо затем, что PowerShell для проверки доверия сертификата ищет его в контейнере Trusted Publishers и только Software Restriction Policies позволяет централизовано распространять сертификаты в этот контейнер.
Теперь можно приступать к подписыванию скриптов. Для этих целей используется командлет Set-AuthenticodeSignature и синтаксис его такой:
Set-AuthenticodeSignature $file $cert
Где $file – путь к скрипту и $cert – объект сертификата, который получается следующим образом:
$cert = @(dir cert:\CurrentUser\My -codesigning)[0]
здесь мы явно указываем, что нам нужен сертификат, у которого в EKU (Enchanced Key Usage) указан Code Sgining. В нашем случае он будет всего 1. Но если их окажется несколько, то мы выберем самый первый. Вот как это будет выглядеть на практике:
[Desktop] Set-ExecutionPolicy allsigned
[Desktop] Get-ExecutionPolicy
AllSigned
[Desktop] .\uptime.ps1
File C:\Documents and Settings\Signer\Desktop\uptime.ps1 cannot be loaded. The file C:\Documents and Settings\Signer
\Desktop\uptime.ps1 is not digitally signed. The script will not execute on the system. Please see "get-help
about_signing" for more details..
At line:1 char:13
+ .\uptime.ps1 < +="" categoryinfo="" :="" notspecified:="" (:)="" [],="" pssecurityexception="" +="" fullyqualifiederrorid="" :="">
[Desktop] $cert = @(dir cert:\currentuser\my -codesigning)[0]
[Desktop] $cert
Directory: Microsoft.PowerShell.Security\Certificate::currentuser\my
Thumbprint Subject
---------- -------
42E5B32A19885C6ADCF9683BDA1C871E0FE5E0DB CN=Signer, CN=Users, DC=contoso, DC=com
[Desktop] Set-AuthenticodeSignature uptime.ps1 $cert
Directory: C:\Documents and Settings\Signer\Desktop
SignerCertificate Status Path
----------------- ------ ----
42E5B32A19885C6ADCF9683BDA1C871E0FE5E0DB Valid uptime.ps1
[Desktop] .\uptime.ps1
System Uptime for DC1 is: 1 days 4 hours 19 minutes 20 seconds
[Desktop]
Я наглядно показал, как это работает. Мы сначала перевели политику исполнения скриптов в AllSigned и убедились, что неподписанный скрипт не исполняется. После чего я подписал этот скрипт и попробовал снова. Как видите, скрипт теперь исполнился.
Если не будет выполнено условие распространения сертификата посредством политики SRP в контейнер Trusted Publishers, то вы получите вот такое сообщение:
[Desktop] .\uptime.ps1
Do you want to run software from this untrusted publisher?
File C:\Documents and Settings\Signer\Desktop\uptime.ps1 is
published by CN=Signer, CN=Users, DC=contoso, DC=com and is not trusted
on your system. Only run scripts from trusted publishers.
[V] Never run [D] Do not run [R] Run once [A] Always run [?] Help
(default is "D"):d
File C:\Documents and Settings\Signer\Desktop\uptime.ps1 cannot be loaded because you have elected to no
t run this software now.
At line:1 char:12
+ .\uptime.ps1
[Desktop]
Вот таким образом мы решаем задачу исполнения только проверенного набора скриптов. В этом смысле PowerShell 1.0 менее безопасный и удобный, поскольку мы не можем политикой SRP блокировать исполнение PS1 файлов как класс и имеем только один выход – принудительное подписывание скриптов. В версии V2 политика исполнения скриптов удобно интегрируется с SRP. Удобство интегрирования в том, что SRP помимо распространения сертификата в пределах домена так же на основе этого правила разрешает исполнять эти скрипты в обход общего ограничения на PS1 файлы.
В следующем посте я расскажу, как можно упростить процесс подписывания скриптов. Так что не отключаемся :-)
Этот скрипт написал скорее для себя, но весьма полезный. В Windows XP/Windows Server 2003 аптайм можно было легко посмотреть в свойствах сетевого подключения, которое как правило работает постоянно. Но в Windows Vista/Windows Server 2008 до него добираться далеко. Смотреть в Task Manager не удобно, поскольку он показывает аптайм в часах, а в уме высчитывать дни как-то неудобно. Вот и набросал функцию, которую положил себе в профиль: