Запуск процесса с повышенными правами UAC из неинтерактивной службы (win32/.net/powershell)
Я использую стороннюю службу Windows, которая обрабатывает некоторые задачи автоматизации, выполняя сценарии и исполняемые файлы с помощью CreateProcessAsUser(). Я сталкиваюсь с проблемами в Windows Server 2008 из-за UAC и способа, которым повышение уровня LUA обрабатывается через API.
Служба работает как LocalSystem и не имеет включенного "Взаимодействовать с рабочим столом". Процессы выполняются как пользователи в группе администраторов, но не в качестве учетной записи администратора (которая освобождена от многих ограничений UAC). Все настройки UAC по умолчанию на месте.
Я могу передать в службу произвольные команды или код powershell, но я не могу "вырваться" из не повышенного уровня, неинтерактивного процесса, который запускается службой.
Суть проблемы заключается в том, что единственной (общедоступной) опцией API для запуска процесса с повышенными правами является ShellExecute() с глаголом runas, но, насколько я могу судить, его нельзя вызвать из неинтерактивного интерфейса. или вы получаете ошибки типа "Для этой операции требуется интерактивная оконная станция".
Единственный найденный мной обходной путь упоминается здесь: http://www.eggheadcafe.com/software/aspnet/29620442/how-to-proper-use-sendinp.aspx
В Vista официальный документированный способ поднять процесс - это использовать только API оболочки ShellExecute(Ex)(не CreateProcess или CreateProcessAsUser). Таким образом, ваше приложение должно вызвать ShellExecute (Ex) для запуска помощника с повышенными привилегиями для вызова SendInput. Кроме того, из-за изоляции сеанса 0 служба может использовать только CreateProcessAsUser или CreateProcessWithLogonW(не может использовать ShellExecute(Ex)) для указания интерактивного рабочего стола.
... Я думаю, что нет прямого способа порождать повышенные процессы из службы Windows. Мы можем только сначала использовать CreateProcessAsUser или CreateProcessWithLogonW, чтобы запустить процесс без повышенных прав в пользовательский сеанс (интерактивный рабочий стол). Затем в процессе без повышенных прав он может использовать ShellExecute (Ex) для запуска процесса с повышенными правами для реальной задачи.
Чтобы сделать это из кода.net/powershell, похоже, что мне нужно было бы сделать некоторые сложные P/Invoke для вызова CreateProcessAsUser или CreateProcessWithLogonW, поскольку.Net System.Diagnostics.ProcessStartInfo не имеет эквивалента lpDesktop, который я мог бы сделать установите "winsta0\default". И мне не ясно, имеет ли LocalSystem даже права на вызов CreateProcessAsUser или CreateProcessWithLogonW.
Я также посмотрел http://blogs.msdn.com/alejacma/archive/2007/12/20/how-to-call-createprocesswithlogonw-createprocessasuser-in-net.aspx и Process.Start с различными учетными данными, когда UAC включен.
Исходя из всего этого, я прихожу к выводу, что не существует простого способа сделать это. Я что-то пропустил? Это действительно не похоже, что это должно быть так сложно. Такое ощущение, что UAC просто никогда не был разработан для обработки неинтерактивных сценариев использования.
И если кто-нибудь из Microsoft в конце концов прочитает это, я заметил, что ShellExecute внутренне обрабатывает повышение прав, вызывая Информационную службу приложений (AIS). Почему тот же вызов AIS не доступен через Win32 или.NET API? http://msdn.microsoft.com/en-us/library/bb756945.aspx
Извините, что побежал немного долго. Спасибо за любые идеи.
2 ответа
"Официальный" способ разорвать нулевую изоляцию сеанса состоит в использовании комбинации API служб терминалов и CreateProcessAsUser()
запустить процесс в сеансе пользователя. На моей старой работе мы сделали именно это, так как нам нужно было отобразить диалоговое окно для пользователя из службы до установки загруженного обновления. Итак, я знаю, что это работает, по крайней мере, на WinXP, Win2K3, Vista и Win7, но я не ожидайте, что Win 2K8 будет слишком другим. В основном, процесс идет следующим образом:
- Вызов
WTSGetActiveConsoleSessionId()
получить идентификатор сеанса активной консоли (ОЧЕНЬ важно, поскольку интерактивный сеанс не всегда является сеансом 1, даже в клиентских системах). Этот API также будет возвращать -1, если в интерактивном сеансе нет активного пользователя (то есть локально вошел в систему на физическом компьютере, в отличие от использования RDP). - Передать идентификатор сеанса из предыдущего вызова API
WTSQueryUserToken()
чтобы получить открытый токен, который представляет пользователя, вошедшего в консоль. - Вызов
DuplicateTokenEx()
преобразовать токен олицетворения (изWTSQueryUserToken
) в основной токен. - Вызов
CreateEnvironmentBlock()
создать новую среду для процесса (необязательно, но если вы этого не сделаете, у процесса ее не будет). - Передайте основной токен из шага № 3 в вызов
CreateProccessAsUser()
вместе с командной строкой для исполняемого файла. Если вы создали блок среды из шага № 4, вы должны передатьCREATE_UNICODE_ENVIRONMENT
флаг тоже (всегда). Это может показаться глупым, но API ужасно терпит неудачу, если вы неERROR_INVALID_PARAMTER
). - Если вы создали блок среды, то вам нужно позвонить
DestroyEnvironmentBlock
в противном случае вы будете генерировать утечку памяти. При запуске процессу дается отдельная копия блока среды, поэтому вы уничтожаете только локальные данные.
И вуаля! Windows делает некоторую внутреннюю магию, и вы видите запуск приложения. Однако, хотя это и запустит интерактивный процесс из службы, я не уверен, что это обойдет UAC (но не цитируйте меня по этому поводу). Другими словами, он может не запускаться как процесс с повышенными правами, если в реестре или во внутреннем манифесте не указано иное, и даже в этом случае вы все равно получите приглашение UAC. Если токен, полученный на шаге 3, является токеном с ограниченным доступом, вы можете использовать AdjustTokenPrivileges()
восстановить повышенный (полный) токен, но также не цитируйте меня. Однако, как указано в документации MSDN, обратите внимание, что невозможно "добавить" привилегии для токена, у которого их еще нет (например, вы не можете превратить токен пользователя с ограниченными правами в администратора с помощью AdjustTokenPrivileges
; основной пользователь должен был бы быть администратором для начала).
Технически возможно сделать все это из Win2K вперед. Тем не менее, это действительно возможно только начиная с WinXP, так как Win2K не хватает WTSGetActiveConsoleSessionId()
а также WTSQueryUserToken()
API (вместе с WTSEnumerateProcesses()
для Win2K Pro). Вы можете жестко закодировать 0 в качестве идентификатора сеанса (так как это всегда имеет место в Win2K), и я полагаю, что вы могли бы получить токен пользователя, перечислив запущенные процессы и продублировав один из их токенов (это должен быть тот, который имеет интерактивный SID присутствует). Независимо от CreateProcessAsUser()
будет вести себя так же, когда передается интерактивный токен пользователя, даже если вы не выберете "Взаимодействовать с рабочим столом" в настройках службы. Это также более безопасно, чем запуск непосредственно из службы, так как процесс не унаследует божественное LocalSystem
токен доступа.
Теперь я не знаю, будет ли ваше стороннее приложение делать это, когда он запускает скрипт / процесс, но если вы хотите сделать это из службы, то вот как (и с Vista или Win7, это единственный способ преодолеть сеанс 0 изоляции).
В зависимости от вашего варианта использования вы можете сделать то, что я делаю. Я выслеживаю процесс winlogon для активной сессии и краду его токен. Если нет активного сеанса (API вернул -1), используйте 1, если WINVER >= 6, иначе 0. Это приводит к SYSTEM в активном сеансе.