ПРОГРАММИРОВАНИЕ    НА    V I S U A L   C + +
РАССЫЛКА САЙТА       
RSDN.RU  

    Выпуск No. 86 от 6 апреля 2003 г.
   
Подписчиков: 20547 

РАССЫЛКА ЯВЛЯЕТСЯ ЧАСТЬЮ ПРОЕКТА RSDN , НА САЙТЕ КОТОРОГО ВСЕГДА МОЖНО НАЙТИ ВСЮ НЕОБХОДИМУЮ РАЗРАБОТЧИКУ ИНФОРМАЦИЮ, СТАТЬИ, ФОРУМЫ, РЕСУРСЫ, ПОЛНЫЙ АРХИВ ПРЕДЫДУЩИХ ВЫПУСКОВ РАССЫЛКИ И МНОГОЕ ДРУГОЕ.

Добрый день!

Сегодня мы поговорим о привилегиях в операционных системах Windows NT/2000.


 CТАТЬЯ

Что такое привилегии?

Автор: Алексей Ширшов
Источник: RSDN Magazine #2
Предисловие

Хотя вопрос о том, что такое привилегии и как с ними работать, нечасто поднимается во всевозможных конференциях, каждый системный программист и/или администратор обязан знать, что это такое. В данной статье приводятся наиболее часто встречающиеся привилегии, их описание и назначение. Здесь же вы найдете несколько фрагментов кода, описывающих методы работы с привилегиями.

Данный материал относится к операционной системе Windows 2000, хотя большая часть описанного применима и ко всему семейству ОС, основанных на NT. Список литературы вы можете найти в конце статьи.

Маркер доступа (access tokens)

Статья базируется на определенных понятиях и терминах. Если вы в общих чертах знакомы с подсистемой безопасности, маркерами доступа и контекстом пользователя, можете смело пропускать этот раздел.

Любой процесс в системе исполняется в контексте какого-либо пользователя или системы. Этот контекст также называют контекстом защиты (security context). В нем хранится все необходимая информация для реализации контроля доступа к защищаемым объектам системы (securable objects). Все именованные объекты системы и некоторые не именованные (процессы, потоки) являются защищаемыми и имеют дескриптор безопасности (security descriptor). Дескриптор безопасности содержит информацию безопасности (security information) для данного объекта, что позволяет обеспечивать универсальный контроль доступа для любых типов объектов, в том числе и определенных пользователем.

Для реализации контроля доступа необходимо наличие информации о контексте защиты, в котором выполняется код, и дескриптора безопасности объекта, к которому этот код получает доступ. Дескриптор безопасности – довольно сложная тема, требующая отдельной статьи. Далее рассматривается контекст защиты и маркер доступа.

Каждый процесс в системе исполняется в определенном контексте защиты, который определяет маркер доступа. Маркер доступа содержит большое количество информации, которая требуется подсистеме безопасности для реализации контроля доступа к защищаемым объектам и ресурсам системы, а также для выполнения некоторых привилегированных операций.

ПРИМЕЧАНИЕ
 Под термином <подсистема безопасности> в данной статье понимается Security Reference Monitor (SRM). SRM - это один из компонентов исполнительной системы (executive), реализующий контроль доступа к защищаемым объектам. Не путайте его с локальным администратором безопасности (Local Security Authority - LSA). LSA - это доверенный процесс операционной системы, предназначенный для аутентификации входа пользователя интерактивно или удаленно. Администратор безопасности - страж на входе в операционную систему. Он создает сессию входа (logon session) и формирует маркер доступа.

Маркер доступа может быть связан не только с процессом, но и с отдельным потоком процесса. Способность потока исполнять код с другим контекстом защиты называется перевоплощением (impersonation). При этом маркер процесса называется первичным маркером (primary access token).

Право создавать маркер доступа имеет только локальный администратор безопасности.

ПРИМЕЧАНИЕ
 На самом деле, конечно, маркер доступа создается исполнительной системой по запросу LSA.

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

Что такое привилегии

Учетные записи пользователей и групп являются доверенными объектами системы. Доверенные объекты – это сущности, которым может быть предоставлен или отклонен доступ к защищаемым объектам или ресурсам системы. [3]

Привилегии – это права доверенного объекта на совершение каких-либо действий по отношению ко всей системе. Под системой понимается компьютер, на котором пользователь зарегистрировался (logon). Следовательно, привилегии пользователя на одном компьютере не распространяются на другой. При каждом интерактивном или неинтерактивном входе в систему формируется новый маркер доступа, и он может содержать другой набор привилегий.

При выполнении каких-либо привилегированных операций система проверяет, обладает ли пользователь соответствующей привилегией. О привилегированных операциях речь пойдет ниже, а сейчас важно отметить, что даже наличие определенной привилегии в маркере доступа не означает успешного результата проверки. Дело в том, что привилегия может находиться либо во включенном состоянии, либо в выключенном. Некоторые системные функции сами включают привилегии, если они выключены, некоторые – нет. Их непосредственное включение рассматривается в разделе «Состояние привилегии».

Привилегия может быть назначена любому доверенному объекту.

Обзор привилегий

В данном разделе приводится список наиболее часто используемых привилегий. Каждая привилегия имеет имя, но в системе идентифицируется по LUID – locally unique identifier. Это уникальный 64-битный идентификатор, который, как правило, представляется в структуры из двух чисел типа long. Для преобразования из имени в LUID и обратно используются специальные функции, рассмотренные в разделе «Преобразование имен».

Кроме того, в файле winnt.h определены специальные константы для каждого имени привилегии. Как правило, в документации используются именно они. Ниже приведена таблица, где перечисляются все рассматриваемые в данной статье привилегии с именами и коротким описанием.

Константа Имя привилегии Описание Дружественное имя По умолчанию имеют
SE_TCB_NAME SeTcbPrivilege Объект является частью доверенной вычислительной системы. Act as part of the operating system LocalSystem
SE_BACKUP_NAME SeBackupPrivilege Привилегия необходима для выполнения операций архивирования Back up files and directories LocalSystem, Backup Operators, Administrators
SE_TAKE_OWNERSHIP_NAME SeTakeOwnershipPrivilege Позволяет доверенному объекту стать владельцем защищенного объекта без явных прав в списке разграничительного доступа (DACL) Take ownership of files or other objects LocalSystem, Administrators
SE_DEBUG_NAME SeDebugPrivilege Необходима для отладки приложений Debug programs LocalSystem, Administrators
SE_ASSIGNPRIMARYTOKEN_NAME SeAssignPrimaryTokenPrivilege Необходима для назначения первичного маркера процессу Replace a process-level token LocalSystem
SE_INCREASE_QUOTA_NAME SeIncreaseQuotaPrivilege Необходима для увеличения квот процесса Increase quotas LocalSystem, Administrators
SE_INC_BASE_PRIORITY_NAME SeIncreaseBasePriorityPrivilege Необходима для увеличения базового приоритета процесса Increase scheduling priority LocalSystem, Administrators, Power Users
SE_LOCK_MEMORY_NAME SeLockMemoryPrivilege Необходима для удержания страниц в физической памяти. Lock pages in memory LocalSystem
SE_SECURITY_NAME SeSecurityPrivilege Необходима для выполнения операций относящихся к безопасности (security-related) Manage auditing and security log LocalSystem, Administrators
SE_AUDIT_NAME SeAuditPrivilege Необходима для создания записей аудита. Generate security audits LocalSystem, NetworkService, LocalService
SE_SHUTDOWN_NAME SeShutdownPrivilege Необходима для выключения компьютера Shut down the system Everyone
SE_TCB_NAME

Это могущественная привилегия, наиболее актуальная при создании logon-а. С помощью привилегии SE_TCB_NAME можно получать маркер доступа по имени пользователя и паролю. Точнее, эта функция передает локальной подсистеме безопасности (Local Security Authority – LSA) запрос на создание новой сессии. Тип сессии определяется параметром dwLogonType. Сессия может быть интерактивной или сетевой. Более подробную информацию об этом можно получить в MSDN или в [3]. При задании некоторых значений параметра dwLogonType нужны соответствующие права учетной записи (Account rights).

Эта привилегия необходима для вызова следующих функций:

  • LogonUser().
  • WTSQueryUserToken().
  • SetTokenInformation() с параметром TokenSessionId.
  • OpenProcessToken с параметром TOKEN_ADJUST_SESSIONID.
  • BroadcastSystemMessage[Ex] с параметром BSM_ALLDESKTOPS.

Наличие этой привилегии система всегда проверяет в первичном маркере процесса, игнорируя маркеры, используемые для имперсонации.

SE_BACKUP_NAME

Привилегия обычно назначается операторам, выполняющим работы по администрированию файл-сервера. Она позволяет даже при отсутствии явного разрешения в списке разграничительного доступа (DACL) просматривать информацию о владельце, группе и DACL файла или директории. Если у вас нет явного разрешения на просмотр такой информации для файла или директории, при включенной привилегии вы все-таки сможете открыть файл с доступом READ_CONTROL. Отмечу, что CreateFile() при этом должна быть вызвана с флагом FILE_FLAG_BACKUP_SEMANTICS. Более того, вы можете открыть его с правом доступа FILE_GENERIC_READ и, соответственно, прочитать все его содержимое. [1]

Эта привилегия часто используется в связке с SE_RESTORE_NAME. Здесь я ее не рассматриваю, однако ее действие аналогично SE_BACKUP_NAME с той лишь разницей, что вы можете писать в файл или директорию без разрешения явного доступа к ним. Это необходимо для работы программ резервного копирования.

ПРИМЕЧАНИЕ

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

Эта привилегия разрешает доступ к файлу или директории, даже если он явно запрещен.

Привилегия используется для:

  • Функции RegCreateKeyEx c флагом REG_OPTION_BACKUP_RESTORE.
  • Функции RegSaveKey[Ex].
  • Архивирования ActiveDirectory и репозитория CIM (Common Information Model).
SE_TAKE_OWNERSHIP_NAME

Данная привилегия предоставляет пользователю возможность становиться владельцем любого объекта. Владелец объекта всегда имеет стандартное право WRITE_DAC, которое позволяет изменять DACL объекта. Это очень мощная привилегия. Даже если пользователю явно запрещен доступ к какому-либо объекту, привилегия SE_TAKE_OWNERSHIP_NAME позволит ему стать владельцем объекта и изменить DACL таким образом, чтобы позволить себе любые операции над объектом (а другим запретить!). Последовательность операций при этом такова: открывается объект с правом доступа WRITE_OWNER, пользователь назначает себя владельцем, объект закрывается и открывается снова с правом доступа WRITE_DAC, изменяется DACL объекта. [1]

Интерфейс ISecurityInformation автоматически проверяет эту привилегию, делая доступной закладку OWNER в Advanced Control Settings.

Привилегия используется во всех функциях, изменяющих дескриптор безопасности с флагом OWNER_SECURITY_INFORMATION.

SE_DEBUG_NAME

Эта привилегией обычно пользуются отладчики для отладки системных процессов. Ее наличие позволяет получить хендл любого процесса с максимально широкими правами (PROCESS_ALL_ACCESS). Привилегия используется в связке с функцией OpenProcess().

SE_ASSIGNPRIMARYTOKEN_NAME

Данная привилегия, в основном, необходима для функции CreateProcessAsUser. Однако если передать этой функции ограниченный маркер, то привилегия не требуется.

SE_INCREASE_QUOTA_NAME

Используется совместно с предыдущей привилегией для вызова CreateProcessAsUser. Хочу сказать еще об одной особенности. Я всегда думал, что изменение рабочего набора процесса ограничено какими-либо квотами, тем более что для процессов имеется тип доступа PROCESS_SET_QUOTA, необходимый для вызова SetProcessWorkingSetSize. Каково же было мое удивление, когда я впервые обнаружил, что эта функция требует привилегии SE_INC_BASE_PRIORITY_NAME (о ней чуть позже). Я не поверил своим глазам и даже написал небольшую программку для проверки этого. Действительно, намного логичнее для увеличения рабочего набора (Working Set) процесса, запрашивать привилегию SE_INCREASE_QUOTA_NAME, однако в Microsoft рассудили иначе.

CreateProcessAsUser сама включает привилегии SE_INCREASE_QUOTA_NAME и SE_ASSIGNPRIMARYTOKEN_NAME, если они выключены.

SE_INC_BASE_PRIORITY_NAME

Привилегия используется для повышения базового приоритета процессов до уровня Realtime. Используется во всех функциях создания процесса и установки его базового приоритета, где приоритет устанавливается равным REALTIME_PRIORITY_CLASS. Если пользователь не имеет соответствующей привилегии, процесс всегда запускается с приоритетом не больше HIGH_PRIORITY_CLASS. [2]

Как уже говорилось выше, эта привилегия используется также для вызова SetProcessWorkingSetSize при увеличении минимального рабочего набора. Другими словами, если функция SetProcessWorkingSetSize будет использована для урезания рабочего набора, привилегия проверяться не будет.

SE_LOCK_MEMORY_NAME

Эта привилегия позволяет фиксировать страницы в физической памяти не только на время работы процесса (пока выполняется хотя бы один его поток), но и тогда, когда он реально не выполняется. Как видите, это очень мощная и, потенциально, одна из самых опасных привилегий. К счастью, интерфейс Win32 не представляет документированного способа для такой операции, следовательно, для большинства приложений пользовательского режима данная привилегия не нужна. Исключение составляет интерфейс AWE (Address Windowing Extensions). Пользователь должен обладать этой привилегией для вызова функции AllocateUserPhysicalPages. [2]

SE_SECURITY_NAME

Привилегия используется для проверки доступа к системному списку контроля доступа (SACL – System Access Control List) дескриптора безопасности (security descriptor). Любые функции изменяющие SACL, и, соответственно, вызванные с флагом SACL_SECURITY_INFORMATION, требуют наличия этой привилегии во включенном состоянии. В противном случае будет возвращена ошибка ERROR_PRIVILEGE_NOT_HELD.

Эта привилегия требуется также для открытия любого объекта с правом доступа ACCESS_SYSTEM_SECURITY.

SE_AUDIT_NAME

Данная привилегия используется для генерации событий аудита семейством функций AccessCheckXXXX за исключением самой AccessCheck().

Используется также с функциями:

  • ObjectOpenAuditAlarm
  • ObjectDeleteAuditAlarm
  • ObjectCloseAuditAlarm
  • ObjectPrivilegeAuditAlarm
  • PrivilegedServiceAuditAlarm

Привилегию необходимо включать явно.

SE_SHUTDOWN_NAME

Последняя из рассматриваемых привилегий наиболее популярна на различных форумах. Она необходима для выключения компьютера и обычно используется с функцией ExitWindows[Ex]. Суть привилегии состоит в том, чтобы не давать пользователю выключать компьютер, на котором функционируют (в виде системных служб) важные серверные компоненты. При простом завершении сеанса пользователя (флаг EWX_LOGOFF) привилегия не проверяется.

Она используется также с функциями:

  • AbortSystemShutdown
  • InitiateSystemShutdown[Ex]
  • SetSystemPowerState

Привилегию необходимо включать явно.

Преобразование имен привилегий

На самом деле имена привилегий – довольно тонкое место, как ни странно это звучит. Дело в том, что кроме программных имен (SeAuditPrivilege) и констант (SE_AUDIT_NAME), есть еще дружественное имя, которое повсеместно используется в пользовательском интерфейсе Windows (MMC – Microsoft Management Console). Иногда по одному имени можно определить другое, а иногда нет. Например, привилегия SeChangeNotifyPrivilege имеет дружественное имя «Bypass traverse checking». К счастью, сопоставление одних имен другим хорошо документировано в MSDN, и разобраться в этом не сложно. Кроме того, имеется функция LookupPrivilegeDisplayName(), которая преобразует программное имя в дружественное. Сейчас же мы поговорим о системном представлении привилегий и преобразовании программных имен во внутренние идентификаторы.

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

  • LookupPrivilegeValue – преобразование программного имени в LUID.
  • LookupPrivilegeName – преобразование LUID в программное имя.

Ничего сложного в их описании и использовании нет. Если при этом использовать специальные классы автоматического выделения памяти (подробнее об этом см. http://www.rsdn.ru/files/?Classes/autobuf.xml), работа по преобразованию имен привилегий в идентификаторы станет тривиальной. Пример использования функции LookupPrivilegeValue приведен в следующем разделе.

Состояние привилегий

В маркере доступа (access token) привилегии могут находиться в двух состояниях: включенном и выключенном. По большому счету, важен лишь сам факт наличия той или иной привилегии в маркере, так как многие системные функции сами, при необходимости, включают ее. Зачем нужна вообще вся эта возня с состоянием привилегий, если их включение – дело техники? Не знаю, скорее всего, это последняя возможность спросить программиста: «Ты действительно знаешь, что делаешь?». :)

Итак, c состоянием привилегий работают следующие функции:

  • PrivilegeCheck – проверка установленных привилегий в маркере доступа.
  • AdjustTokenPrivileges – изменение состояния одной или нескольких привилегий в маркере.

Для проверки наличия той или иной привилегии в маркере доступа используется функция GetTokenInformation с параметром TokenPrivileges.

PrivilegeCheck

Для вызова этой функции необходим маркер с правом доступа TOKEN_QUERY. Вот описание этой функции:


BOOL PrivilegeCheck(
  HANDLE ClientToken,                // хендл маркера безопасности клиента
  PPRIVILEGE_SET RequiredPrivileges, // привилегии
  LPBOOL pfResult                    // результат
);

На вход передается указатель на структуру PRIVILEGE_SET. Вот ее описание:


typedef struct _PRIVILEGE_SET { 
  DWORD PrivilegeCount; 
  DWORD Control; 
  LUID_AND_ATTRIBUTES Privilege[ANYSIZE_ARRAY]; 
} PRIVILEGE_SET, *PPRIVILEGE_SET;

Массив Privilege должен быть заполнен нужными привилегиями и атрибутами. Значения атрибутов на входе равны нулю. После успешного завершения функции, значения включенных привилегий равны SE_PRIVILEGE_USED_FOR_ACCESS. Вот так выглядит структура LUID_AND_ATTRIBUTES:


typedef struct _LUID_AND_ATTRIBUTES { 
  LUID   Luid; 
  DWORD  Attributes; 
} LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES;

Вот небольшой пример использования этой функции. На входе: маркер доступа, массив имен привилегий и размер этого массива. Функция CheckPrivs выводит на экран состояние указанных привилегий:


int CheckPrivs(HANDLE hTok, PCTSTR const* pname, DWORD dwNumPrivs)
{  
  size_t sz = sizeof(PRIVILEGE_SET)+sizeof(LUID_AND_ATTRIBUTES)*(dwNumPrivs-1);

  PPRIVILEGE_SET pPriv = (PPRIVILEGE_SET)_alloca(sz);    //Выделяем на стеке
  int i;
  pPriv->PrivilegeCount = NUM_PRIV;
  for(i = 0;i < pPriv->PrivilegeCount;i++)
  {
    pPriv->Privilege[i].Attributes = 0;
    if (!LookupPrivilegeValue(NULL, pname[i], &pPriv->Privilege[i].Luid)){
      int err = GetLastError();
      cout << "LookupPrivilegeValue error 0x" << hex << err << endl;
      return 1;
    }
  }
  BOOL res;
  if (!PrivilegeCheck(hTok,pPriv,&res)){
    int err = GetLastError();
    cout << "PrivilegeCheck error 0x" << hex << err << endl;
    return 1;
  }

  for(i = 0;i < pPriv->PrivilegeCount;i++)
  {
    if (pPriv->Privilege[i].Attributes == SE_PRIVILEGE_USED_FOR_ACCESS)
      cout << pname[i] << " enabled" << endl;
    else
      cout << pname[i] << " disabled" << endl;
  }
  return 0;
}

AdjustTokenPrivileges

Для вызова функции необходим маркер с правом доступа TOKEN_QUERY и TOKEN_ADJUST_PRIVILEGES. Вот описание этой функции:


BOOL AdjustTokenPrivileges(
  HANDLE TokenHandle, // хендл маркера безопасности
  BOOL DisableAllPrivileges, // отмена всех привилегий
  PTOKEN_PRIVILEGES NewState, // информация о привилегиях 
  DWORD BufferLength, // размер буфера
  PTOKEN_PRIVILEGES PreviousState, // список предыдущих состояний
  PDWORD ReturnLength // размер возвращенного массива PreviousState
);

Она может отключать все привилегии, если DisableAllPrivileges установлена в TRUE. При этом праметр NewState игнорируется. Если она установлена в FALSE, то переменная NewState содержит указатель на структуру TOKEN_PRIVILEGES. Ее описание идентично структуре PRIVILEGE_SET за исключением того, что в ней отсутствует поле Control. Параметр PreviousState должен указывать на структуру того же размера, что и NewState. В нем возвращаются предыдущие состояния измененных привилегий. Сама функция не выглядит сложной, сложно подготовить соответствующие данные для ее вызова. Существует множество версий функции EnablePrivilege [3], поэтому если мы приведем еще одну – хуже не будет. :)

Итак, ниже приводится моя собственная версия функции включения привилегий. Она хороша тем, что позволяет включить несколько привилегий за раз и использует существующий маркер доступа.


bool EnablePrivileges(PCTSTR const * ppPrivs, DWORD dwCnt,
  PTOKEN_PRIVILEGES* ppOldPrivs = NULL,HANDLE hTok = 0)
{
  bool retval = false;
  PTOKEN_PRIVILEGES pOldPrivs = NULL;
  try{
    size_t sz = sizeof(TOKEN_PRIVILEGES)+
      sizeof(LUID_AND_ATTRIBUTES)*(dwCnt-1);
    
    //Выделяем в стеке
    PTOKEN_PRIVILEGES pPriv = (PTOKEN_PRIVILEGES)_alloca(sz);
    DWORD RetLen,*pRetLen = NULL;
    
    //Выделяем память для старых значений привилегий
    if (ppOldPrivs != NULL){
      pOldPrivs = (PTOKEN_PRIVILEGES)malloc(sz);
      pRetLen = &RetLen;
    }

    //Заполняем буфер
    pPriv->PrivilegeCount = dwCnt;
    for(int i = 0; i < dwCnt; i++)
    {
      pPriv->Privileges[i].Attributes = SE_PRIVILEGE_ENABLED;
      if (!LookupPrivilegeValue(NULL, ppPrivs[i],
        &pPriv->Privileges[i].Luid))
        throw 1;
    }

    //Включаем привилегии
    bool bMyTok = false;
    if (!hTok){
      if (!OpenProcessToken(GetCurrentProcess(),
        TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES,&hTok))
        throw 1;
      bMyTok = true;
    }
    retval = (bool)AdjustTokenPrivileges(hTok, FALSE, pPriv, sz,
      pOldPrivs, pRetLen);
    
    if (bMyTok) CloseHandle(hTok);

  }
  catch(...){
  }

  if (ppOldPrivs != NULL)
    *ppOldPrivs = pOldPrivs;

  return retval;
};

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

Доверенные объекты и привилегии

Информация о доверенных объектах хранится либо в SAM (Security Account Manager), который расположен локально на машине клиента, либо в AD (Active Directory). Доменные настройки переопределяют локальные: если некоторые привилегии запрещены на доменном уровне, локальные настройки игнорируются. В этом разделе мы рассмотрим методы назначения привилегий учетным записям на локальном уровне с помощью функций LSA (Local Security Authority).

Все функции LSA начинаются с префикса Lsa. Функций, относящихся к привилегиям – четыре. Замечу, что LSA не делает различия между правами учетной записи (Account Rights) и привилегиями. Однако функции LookupPrivilegeValue и LookupPrivilegeName работают только с привилегиями.

  • LsaEnumerateAccountRights – перечисляет привилегии доверенного объекта.
  • LsaEnumerateAccountsWithUserRight – перечисляет доверенные объекты, обладающие данной привилегией.
  • LsaAddAccountRights – добавляет одну и более привилегий доверенному объекту.
  • LsaRemoveAccountRights – отзывает одну и более привилегий у доверенного объекта.

Все эти функции в качестве первого параметра принимают описатель локальной политики безопасности. Открыть его можно с помощью функции LsaOpenPolicy. Функции LSA работают со строками специального формата, который используется внутренними системными и недокументированными функциями. В DDK эти строки называются UNICODE_STRING, в документации по LSA они называются LSA_UNICODE_STRING. Но название сущности не меняет.

Функции перечисления сами выделяют буфер, который затем необходимо освобождать с помощью LsaFreeMemory().

Для лучшего понимания рассмотрим использование функции LsaEnumerateAccountRights.


PSID FromName(PTSTR pszName,PTSTR pszSystem)
{
  CAutoBufBase abuf1;
  CAutoBuf<TCHAR> abuf2;
  SID_NAME_USE peUse;
  int fOk;
  do {
    fOk = LookupAccountName(pszSystem, pszName,
      abuf1,abuf1.GetSizeAddr(),
      abuf2,abuf2.GetSizeAddr(),
      &peUse);
    if (!fOk)
      if (GetLastError() == ERROR_INSUFFICIENT_BUFFER){
        abuf1.Alloc();
        abuf2.Alloc();
      }
      else break;
  }
  while(!fOk);
  return (PSID)abuf1.Detach();
}
...
void PrintPrivileges(PTSTR pszName, PTSTR pszSystem)
{
  PSID pAccSid = FromName(pszName, pszSystem);
  if (pAccSid){
    LSA_UNICODE_STRING ustrComp = {0};
    LSA_OBJECT_ATTRIBUTES oa = {0};
    LSA_HANDLE hPol;
    NTSTATUS stat;
    stat = LsaOpenPolicy(&ustrComp,&oa,
      POLICY_VIEW_LOCAL_INFORMATION|POLICY_LOOKUP_NAMES,
      &hPol);
    ULONG err = LsaNtStatusToWinError(stat);
    
    if (err != ERROR_SUCCESS){
      cout << "error open policy" << endl;
      CAutoBufBase::Free(&pAccSid);
      return;
    }
    ULONG cPrivs = 0;
    PLSA_UNICODE_STRING pPrivs;
    stat = LsaEnumerateAccountRights(hPol, pAccSid, &pPrivs, &cPrivs);

    if (LsaNtStatusToWinError(stat) != ERROR_SUCCESS){
      cout << "error enumerate privileges" << endl;
      LsaClose(hPol);
      CAutoBufBase::Free(&pAccSid);
      return;
    }

    for(ULONG i = 0; i < cPrivs; i++){
      PWSTR pBuf = new WCHAR[pPrivs[i].MaximumLength];
      wcsncpy(pBuf,pPrivs[i].Buffer,pPrivs[i].Length);
      wcout << pBuf << endl;
      delete[] pBuf;
    }
    LsaFreeMemory(pPrivs);
    LsaClose(hPol);
  }
  else
    cout << "error getting sid" << endl;

  CAutoBufBase::Free(&pAccSid);
}

Если вам не понятны операции с памятью, использующие классы CAutoBufBase и CAutoBuf, смотрите статью "Автоматическое выделение памяти". Я их использую на том же основании, на котором Страуструп использует в своих примерах классы стандартной библиотеки (STL) – по соображениям улучшения наглядности кода и автоматизации рутинных операций.

Заключение

В заключение хочу сказать, что, в общем и целом, понимание работы привилегий базируется на знании архитектуры подсистемы безопасности Windows (я не имею в виду LSA). Вы должны представлять себе, что такое SID, SD, DACL, SACL и т.д. Вы должны понимать сущность маркера доступа (access token), кем, когда и как он формируется и для чего используется. Если вы чувствуете, что некоторые аспекты подсистемы безопасности вызывают у вас затруднение, обратитесь к источникам, перечисленным ниже. Большое количество достоверной информации можно получить также в MSDN и/или RSDN.

Литература
  1. Keith Brown, Security Briefs, MSJ, August 1999
  2. Давид Соломон, Марк Руссинович, Внутреннее устройство Microsoft Windows 2000, Издательства «Русская редакция», «Питер», 2001
  3. Джеффри Рихтер, Джейсон Кларк, Программирование серверных приложений для Windows 2000 , Издательства «Русская редакция», «Питер», 2001


Ведущий рассылки: Алекс Jenter   jenter@rsdn.ru
Публикуемые в рассылке материалы принадлежат сайту RSDN.

| Предыдущие выпуски     | Статистика рассылки