Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Введение в Security Identifiers (SID)


Информационный Канал Subscribe.Ru

Программирование для Windows NT на Delphi
Выпуск номер : 4 [ 30 июля 2002 года]


Здравствуйте, уважаемые подписчики.

По сравнению с прошлым выпуском, нас стало в несколько (десятков) раз больше :). Благодарю всех подписавшихся и напомню, что вы можете писать мне на flint@vtc.ru ваши вопросы, замечания и предложения (кроме спама :))

Сегодня в рассылке:
- Что такое SID
- Вопросы без ответов


Статья
Что такое SID


Сегодня мы разберем очень важное и часто используемое понятие - SID (Security Identifier) - идентификатор безопасности. Напомню, что в прошлом выпуске рассылки для добавления в локальную группу мы использовали SID, а не имя пользователя или группы.

Так что же такое SID? SID - это уникальное двоичное представление доверенного объекта (пользователя, группы, компьютера, домена и т.д.). Т.е. каждому объекту сопоставлено уникальное (шанс совпадения очень мал) число (пусть пока будет число, о структуре SID ниже), по которому (а вовсе не по имени) система и опознает объект.

Подготавливая этот выпуск, я решил посмотреть, что же интересного по SID есть в интернете. Яndex выдал кучу одинаковых ссылок, половина из которых вела на FAQ с вопросом "Как узнать SID юзера?", а вторая - "Словарь компьютерных терминов". В этом словаре написано следующее : "SID - содержит информацию о том, к каким группам принадлежит пользователь и какими привилегиями обладает.". Это в корне не верно. Опровергнуть это утверждение может хотя бы тот факт, что SID для определенного объекта постоянный (т.е. при перемещении в группу он не меняется :).

Теперь давайте проведем эксперимент - создадим пользователя (которого потом удалим) с именем test и любой файлик (если у вас не NTFS, то файл создавать бесполезно. Но можно заменить его созданием раздела в реестре, лучше в HKEY_CURRENT_USER\Software). Теперь смотрим свойства файла -> Безопасность (для реестра запустите regedt32.exe). Теперь добавьте нашего пользователя (какие вы ему права дадите - не важно). После этого закройте окно свойств и удалите этого пользователя. Появится сообщение с кратким рассказом о SID :)) После удаления откройте снова закладку безопасность. Теперь пользователя test там не должно уже быть, но вероятнее всего на его месте будет строка, например такая S-1-5-21-1409072233-1202550629-1957994488-1045 (я проверял это на Win2k Prof, а на NT4 Server пользователь просто "исчезал". Так что, если вы ничего не увидели - поверьте наслово :)) Это и есть тот самый SID (в текстовом виде).

Давайте разберем эти циферки. Текстовый SID имеет вид S-R-I-S-S.... Начинается он с префикса S. Далее идут - R - номер версии (пока это 1),
I (48бит) - уполномоченный орган, выдавший SID (в примере это 5 - создано WinNT),
S (32бита) - (один или несколько) уполномоченный орган нижнего уровня, выдавший SID, называемый RID (relative identifier).

Уполномоченный орган, выдавший SID (I) - 48-разрядное число, объявленное так
type SID_IDENTIFIER_AUTHORITY = record
 Value : array[0..5] of Byte;
end;

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

SECURITY_NULL_SID_AUTHORITY (0) - применяется для создания пустой группы
- SECURITY_NULL_RID (0) [Sid пустой группы S-1-0-0]
SECURITY_WORLD_SID_AUTHORITY (1) - применяется для создания учетной записи "Все"
- SECURITY_WORLD_RID (0) [Sid Все(EveryOne) S-1-1-0]
SECURITY_LOCAL_SID_AUTHORITY (2) - применяется для создания группы Local
 (в ней находятся все пользователи, регистрирующиеся локально)
- SECURITY_LOCAL_RID (0) [Sid группы Local S-1-2-0]
SECURITY_CREATOR_SID_AUTHORITY (3) - применяется для создания стандартных
                                    SID владельцев
- SECURITY_CREATOR_OWNER_RID (0) - [Создатель/владелец - S-1-3-0]
- SECURITY_CREATOR_GROUP_RID (1) - [Группа владельца - S-1-3-1]
Все вышеописанные SID являются одинаковыми для любой NT. Единственным органом, выдающим RID пользователям и группам, является SECURITY_NT_AUTHORITY (5). У него также есть несколько стандартных RID. Полный их список вы можете найти в MSDN. Я назову только один - SECURITY_LOCAL_SYSTEM_RID (18) и SID S-1-5-18 соответствуют учетной записи LocalSystem\Система.

В SID, который я дал для примера, то что подчеркнутым - это SID компьютера. А жирным выделен RID пользователя или группы. Пользовательские RID начинаются с 1000. Это значит, что SID в примере был создан 45ым по порядку.

Думаю, теперь стало понятнее, что такое SID. Ну теперь перейдем к практике.

Самое частое действие - получение SID по имени пользователя или группы или наоборот. Делается это функциями LookupAccountName и LookupAccountSid. Окончания в этих функциях (Name и Sid) показывают не то, что надо получить, а то, что у нас есть!!! Т.е. LookupAccountName по имени пользователя получает SID, а LookupAccountSid по SID имя пользователя (или группы..).

// Ищем SID пользователя lpName
procedure UserGetSid(lpName : PWideChar);
var
 lpDomain : PWideChar;
 Sid : PSID;
 cbDomain,cbSid : Cardinal;
 peUse : Cardinal;
begin
 cbSid:=128; // Размер буфера под SID (должно хватить)
 cbDomain:=64; // Кол-во символов в имени домена

 // Выделяем память
 GetMem(Sid,cbSid);
 GetMem(lpDomain,cbDomain*SizeOf(WideChar)); // Я в примерах использую Unicode-строки

 // Пытаемся получить SID и имя домена
 if not LookupAccountNameW(nil,lpName,Sid,cbSid,lpDomain,cbDomain,peUse)
    and (GetLastError=ERROR_INSUFFICIENT_BUFFER)
  then begin
   // Не хватило памяти под SID и/или имя домена.
   // в cbSid и cbDomain находятся необходимые размеры
   ReAllocMem(Sid,cbSid);
   // Повторюсь - функция требует и возвращает не размер буфера, а кол-во символов.
   ReAllocMem(lpDomain,cbDomain*SizeOf(WideChar));
   // Снова вызываем
   if not LookupAccountNameW(nil,lpName,Sid,cbSid,lpDomain,cbDomain,peUse)
    then MessageBoxW(0,PWideChar(SysErrorMessage(GetLastError)),'Error',MB_OK);
  end;

 // Пользуемся SID

 // Освобождаем память
 FreeMem(Sid);
 Freemem(lpDomain);
end;

// Получаем имя пользователя по SID
//(комментировать не буду - все аналогично предыдущему примеру)
procedure GetSidUser(Sid : PSID);
var
 Sid : PSID;
 lpName,lpDomain : PWideChar;
 cbName,cbDomain : Cardinal;
 peUse : Cardinal;
begin
 cbName:=64;
 cbDomain:=64;

 GetMem(lpName,cbName*SizeOf(WideChar));
 GetMem(lpDomain,cbDomain*SizeOf(WideChar));

 if not LookupAccountSidW(nil,Sid,lpName,cbName,lpDomain,cbDomain,peUse)
    and (GetLastError=122) then begin

   ReAllocMem(lpName,cbName*SizeOf(WideChar));
   ReAllocMem(lpDomain,cbDomain*SizeOf(WideChar));

   if not LookupAccountSidW(nil,Sid,lpName,cbName,lpDomain,cbDomain,peUse)
    then MessageBoxW(0,PWideChar(SysErrorMessage(GetLastError)),'Error',MB_OK);
  end;

 // Пользуемся lpName
 ShowMessage(lpName);

 FreeMem(lpName);
 Freemem(lpDomain);
end;
Наличие имени сервера (локального компьютера (nil) в данном случае) не означает, что поиск будет вестись только на этом компьютере. Функция просматривает объекты в таком порядке - стандартные SID, пользователи и группы на этом компьютере, домен и все компьютеры в нем, другие домены. При этом, если имя учетной записи совпадает с именем компьютера, то она вернет SID последнего.

Последним параметром у этих функций переменная типа SID_NAME_USE = DWORD. В нее после выполнения функции помещается тип SID.
const
  SidTypeUser = 1; // SID пользователя
  SidTypeGroup = 2; // группы
  SidTypeDomain = 3; // домена
  SidTypeAlias = 4; // встроенной группы
  SidTypeWellKnownGroup = 5; // стандартной группы
  SidTypeDeletedAccount = 6; // удаленной записи
  SidTypeInvalid = 7; // неверный SID
  SidTypeUnknown = 8; // неизвестный SID
  SidTypeComputer = 9; // SID компьютера (объявления в windows.pas нет.)

Еще одна операция - создание стандартных SID. Делается это функцией AllocateAndInitializeSid (в принципе этой функцией можно создать (почти) любой SID). Для примера создадим SID стандартной группы СЕТЬ (Network).
Текстовый вид этого SID S-1-5-2.

const
 SECURITY_NT_AUTHORITY : TSidIdentifierAuthority = (Value: (0, 0, 0, 0, 0, 5));

// создаем SID
AllocateAndInitializeSid(SECURITY_NT_AUTHORITY,1,2,0,0,0,0,0,0,0,Sid);

// используем SID
// Освобождаем память
FreeSid(Sid);

Итак, еще раз S-1-5-2. 1 - версия; орган, выдавший SID 5 = SECURITY_NT_AUTHORITY; 2 - RID группы СЕТЬ. Параметры функции : орган, выдавший SID (SECURITY_NT_AUTHORITY), кол-во RID (1), список до 8 RID (используется только 2). В итоге мы получаем в Sid SID группы СЕТЬ. Но мы не выделяли память под этот Sid! За нас это сделала система => она и должна ее освободить. Делается это функцией FreeSid().
ЗЫ. В реальности кол-во RID ограничено 15тью, но в AllocateAndInitializeSid их может быть не больше 8.

Другие функции для SID.
EqualSid(Sid1,Sid2) сравнивает 2 SID.
EqualPrefixSid(Sid1,Sid2); сравнивает 2 SID без последнего RID
GetLengthSid(Sid) возвращает размер Sid в байтах
IsValidSid(Sid); проверяет SID на "правильность"
CopySid(dwSidLength,ToSid,FromSid); копирует FromSid в ToSid,dwSidLength - размер буфера ToSid (GetLengthSid)

И еще две функции только для Win2000 и далее. Переводят SID в строку и наоборот
ConvertSidToStringSid(Sid,StringSid);
ConvertStringSidToSid(StringSid,Sid);

// Их описания нет в Windows.pas, но объявить их можно примерно так
function ConvertSidToStringSidW(Sid : PSID;var StringSid : PWideChar):Boolean;stdcall;external 'advapi32.dll';
function ConvertStringSidToSid(StringSid : PWideChar; var Sid : PSID):Boolean;stdcall;external 'advapi32.dll';

Обе функции требуют освободить память var-параметров функцией LocalFree.

В MSDN сама структура SID описывается как
SID = record
 Revision : Byte;
 SubAuthorityCount : Byte;
 IdentifierAuthority : SID_IDENTIFIER_AUTHORITY;
 SubAuthority : array [0..ANYSIZE_ARRAY-1] of DWORD;
end;

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

function SidToStr(Sid : PSID):WideString;
var
 SIA : PSidIdentifierAuthority;
 dwCount : Cardinal;
 I : Integer;
begin
 // S-R-I-S-S...
 Result:='';
 // Проверяем SID
 if not isValidSid(Sid) then Exit;

 Result:='S-'; // Префикс

 // Получаем номер версии SID
 // Хотя работать на прямую с SID, как я уже говорил, не рекомендуется
 Result:=Result+IntToStr(Byte(Sid^))+'-';

 // Получаем орган, выдавший SID
 // Пока все находится в последнем байте
 sia:=GetSidIdentifierAuthority(Sid);
 Result:=Result+IntToStr(sia.Value[5]); //S-R-I-

 // кол-во RID
 dwCount:= GetSidSubAuthorityCount(Sid)^;
 // и теперь перебираем их
 for i:=0 to dwCount-1 do
  Result:=Result+'-'+IntToStr(GetSidSubAuthority(Sid,i)^);

end;

Вопросы без ответов
Вопрос по структуре USER_INFO_2

Если можно помогите разобраться

в USER_INFO_2 (и аналогичных USER_INFO_3 и не помню далее цифры) есть два поля

last_logon:DWORD;
last_logoff:DWORD;
первое - время входа пользователя в систему прекрасно получается получить хоть и опрашивать все сервера для получения наибольшего правильного времени а вот второе last_logoff всегда 0 (время выхода не известно) и тоже опрос по всем серверам домена а очень хотелось бы получить это самое время выхода пользователя из системы что для этого сделать с серваками или может есть еще другой путь (анализировать журнал событий ну как то грустно хотелось бы напрямую из AD)

Если кто может помочь с ответом, напишите пожалуйста на flint@vtc.ru


Вот и все на сегодня.
С уважением, ведущий рассылки FliNT
delphi.xonix.ru



http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу

В избранное