Как я могу получить повышенные разрешения (UAC) посредством олицетворения под неинтерактивным входом?

У меня есть библиотека классов, в которой хранятся системные данные конфигурации в реестре (HKLM\Software\XXX). Эта библиотека используется в различных приложениях (сервисах, формах окон, веб-приложениях, консольных приложениях) в различных версиях Windows (XP, 2003, 7, 2008 R2). Из-за этого личность приложения несовместима и даже не может быть членом группы администраторов компьютеров. Таким образом, я создал администратора домена домена AD и выполняю олицетворение, чтобы получить доступ на запись в реестр. Это отлично работает в XP/2003, но не в системах с поддержкой UAC (7/2008R2). Насколько я понимаю, только интерактивные логины разделяли токены, что означало бы, что неинтерактивные логины (идентификаторы сервисов, идентификаторы пула приложений и т.д.) Этого не делают. Я не могу найти что-либо, чтобы подтвердить это, но работая с этим предположением, работа, которую я делаю, должна работать.

Я написал класс-оболочку для выполнения олицетворения с использованием собственного LogonUser (сетевой logontype, поставщика по умолчанию) и DuplicateTokenEx (олицетворение, первичный токен), а затем WindowsIdentity.Impersonate(). Я получаю ссылку на мой корневой ключ:

using (ECR.Impersonator imp = new ECR.Impersonator("XXX", "XXX", "XXX"))
{
    _root = Registry.LocalMachine.CreateSubKey("SOFTWARE\\XXX", RegistryKeyPermissionCheck.ReadWriteSubTree);
}

В соответствии с MSDN, используя ReadWriteSubTree, это должно быть ТОЛЬКО во время проверки безопасности. Я могу писать значения для этого ключа, создавать под-ключи (также используя ReadWriteSubTree) и записывать значения в эти под-ключи, не требуя повторной проверки безопасности. Поэтому я подумал, что мне нужно будет только сделать дорогостоящее олицетворение один раз - получить ссылку на мой корневой ключ.

Я могу написать значения для моего корневого ключа просто отлично:

_root.SetValue("cachedDate", value.ToBinary(), RegistryValueKind.QWord); }

но когда я создаю/открываю под-ключ с помощью ReadWriteSubTree:

RegistryKey key = _root.CreateSubKey("XXX", RegistryKeyPermissionCheck.ReadWriteSubTree);

он бомбит с помощью Access to the registry key 'HKEY_LOCAL_MACHINE\SOFTWARE\XXX\XXX' is denied.

В то время как мне любопытно, почему проверка безопасности выполняется, когда MSDN говорит, что этого не должно быть, мой вопрос в том, как я могу получить повышенные разрешения с помощью олицетворения для приложений, которые могут не выполняться под интерактивным входом?

Ответ 1

Было показано, что LogonUser() возвращает только ограниченный токен. При поиске подтверждения я получил сообщение о том, что LogonUser() вернул ограниченный токен только для сеансов. Я создал пару тестов, чтобы узнать.

Первое - это консольное приложение:

using (ECR.Impersonator imp = new ECR.Impersonator("XXX", "XXX", "XXX"))
{
    WindowsIdentity ident = WindowsIdentity.GetCurrent();
    WindowsPrincipal princ = new WindowsPrincipal(ident);
    Console.WriteLine("{0}, {1}", ident.Name, princ.IsInRole(WindowsBuiltInRole.Administrator));
    RegistryKey root = Registry.LocalMachine.CreateSubKey("SOFTWARE\\Connection Strings", RegistryKeyPermissionCheck.ReadWriteSubTree);
    RegistryKey key = root.CreateSubKey("AbacBill", RegistryKeyPermissionCheck.ReadWriteSubTree);
}

При запуске в повышенной консоли IsInRole() вернётся true и нет ошибки при открытии этого раздела. При запуске в не-поднятой консоли IsInRole() возвратил true и с ошибкой открыл подраздел:

Unhandled Exception: System.IO.IOException: Unknown error "1346".
   at Microsoft.Win32.RegistryKey.Win32Error(Int32 errorCode, String str)
   at Microsoft.Win32.RegistryKey.CreateSubKey(String subkey, RegistryKeyPermissionCheck permissionCheck, RegistrySecurity registrySecurity)
   at test.Program.test14()
   at test.Program.Main(String[] args)

Итак, похоже, что в невысокой интерактивной сессии LogonUser() действительно возвращает ограниченный токен. Интересно, что нормальный тест на выполнение IsInRole() неожиданно вернулся.

Второй тест - это веб-сайт. Я поместил тот же код в (заменил Console.Write на literal1.Text = string.Format): IsInRole() вернётся true, нет раздела для открытия ошибки, IIS7.5: анонимная аутентификация, пул приложений: классический конвейер, ApplicationPoolIdentity, 2.0 framework, web.config: режим аутентификации = нет, без олицетворения.

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

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

protected void Page_Load(object sender, EventArgs e)
{
    RegistryKey root = null;

    using (ECR.Impersonator imp = new ECR.Impersonator("XXX", "XXX", "XXX"))
    {
        WindowsIdentity ident = WindowsIdentity.GetCurrent();
        WindowsPrincipal princ = new WindowsPrincipal(ident);
        lit.Text = string.Format("{0}, {1}", ident.Name, princ.IsInRole(WindowsBuiltInRole.Administrator));
        root = Registry.LocalMachine.CreateSubKey("SOFTWARE\\XXX", RegistryKeyPermissionCheck.ReadWriteSubTree);
    }

    root.SetValue("test", "test");
    RegistryKey key = root.CreateSubKey("XXX", RegistryKeyPermissionCheck.ReadWriteSubTree);
}

и ошибки:

[UnauthorizedAccessException: Access to the registry key 'HKEY_LOCAL_MACHINE\SOFTWARE\XXX\XXX' is denied.]
   Microsoft.Win32.RegistryKey.Win32Error(Int32 errorCode, String str) +3803431
   Microsoft.Win32.RegistryKey.CreateSubKey(String subkey, RegistryKeyPermissionCheck permissionCheck, RegistrySecurity registrySecurity) +743
   webtest.impTest.Page_Load(Object sender, EventArgs e) in D:\VS 2008 Projects\test\webtest\impTest.aspx.cs:28
   System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e) +25
   System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e) +42
   System.Web.UI.Control.OnLoad(EventArgs e) +132
   System.Web.UI.Control.LoadRecursive() +66
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +2428

Опять же, никаких проблем с записью значений в мой корневой ключ, просто открытие дочерних элементов. Таким образом, похоже, что использование RegistryKeyPermissionCheck.ReadWriteSubTree действительно не выполняет никаких дополнительных проверок безопасности при написании этого ключа, но делает другую проверку безопасности при открытии подраздела даже с RegistryKeyPermissionCheck.ReadWriteSubTree(хотя документы говорят, что это не так).

Ответ на мой вопрос заключается в том, что он соответствующим образом предоставляет повышенные разрешения посредством олицетворения под неинтерактивным входом. Моя проблема в том, что я предположил, что RegistryKeyPermissionCheck.ReadWriteSubTree не будет делать никаких дальнейших проверок безопасности (например, документы) на этой ссылке даже после того, как завершается олицетворение.

Думаю, мне придется делать олицетворение каждый раз, когда мне нужно писать в реестр.: (

Ответ 2

Я не понимаю, как Microsoft может утверждать, что проверки безопасности не будут выполнены. Я быстро просмотрел код, и эти методы не намного больше, чем обертки вокруг собственных функций RegOpenKeyEx/RegCreateKeyEx, поэтому очевидно, что проверки безопасности будут сделаны. RegistryKeyPermissionCheck, похоже, контролирует, выполняет ли сам класс RegistryKey собственную проверку внутренней безопасности до P/Invoking собственных функций. Таким образом, вы можете передать все флаги, которые вы хотите использовать для этого класса RegistryKey, но если основной раздел реестра не предоставит вам доступ, вы получите исключение, исключенное для доступа.

Что касается LogonUser(), то из того, что я прочитал, кажется, что он всегда будет предоставлять вам ограниченный токен.