Пытаюсь решить ее, но все усилия пока тщетны. Рабочий процесс следующим образом
Служба Windows работает как локальная система создает дочерний с помощью CreateProcessAsUser(...)
с токеном текущего зарегистрированного пользователя.
const auto session = WTSGetActiveConsoleSessionId();
auto result = WTSQueryUserToken(session, &token);
HANDLE primary;
result = DuplicateTokenEx(token,
TOKEN_QUERY_SOURCE | TOKEN_ALL_ACCESS | TOKEN_IMPERSONATE |
TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ADJUST_PRIVILEGES,
nullptr, SecurityImpersonation, TokenPrimary, &primary);
const auto args = std::to_string(reinterpret_cast<long>(access));
CreateProcessAsUser(primary,
const_cast<LPSTR>(command.c_str()), // module name
const_cast<LPSTR>(args.c_str()), // Command line
nullptr, // Process handle not inheritable
nullptr, // Thread handle not inheritable
TRUE, // Set handle inheritance to TRUE
0, // No creation flags
nullptr, // Use parent's environment block
nullptr, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi); // Pointer to PROCESS_INFORMATION structure
Дочерний процесс запускается на пользовательской рабочей станции \ рабочем столе, и основной поток записывает события ввода-вывода пользователя. Процесс подражания дочернего процесса выглядит следующим образом
void impersonate() {
const auto args = GetCommandLine();
const auto system_token = reinterpret_cast<HANDLE>(std::stol(args, nullptr));
if (SetThreadToken(nullptr, system_token) == TRUE) {
auto result = OpenThreadToken(GetCurrentThread(),
TOKEN_QUERY | TOKEN_QUERY_SOURCE, TRUE, &token);
if (result == TRUE)
{
DWORD dwSize = 0;
if (!GetTokenInformation(token, TokenStatistics, NULL, 0, &dwSize)) {
const auto dwResult = GetLastError();
if (dwResult != ERROR_INSUFFICIENT_BUFFER) {
cout << "GetTokenInformation Error: " << dwResult;
} else {
// Allocate the buffer.
PTOKEN_STATISTICS statistics =
(PTOKEN_STATISTICS)GlobalAlloc(GPTR, dwSize);
// Call GetTokenInformation again to get the group information.
if (!GetTokenInformation(token, TokenStatistics, statistics, dwSize,
&dwSize)) {
cout << "GetTokenInformation Error: " << error;
} else {
const auto level = statistics->ImpersonationLevel;
std::string str;
switch (level) {
case SecurityAnonymous:
str = R"(anonymous)";
break;
case SecurityIdentification:
str = R"(identification)";
break;
case SecurityImpersonation:
str = R"(impersonation)";
break;
case SecurityDelegation:
str = R"(delegation)";
break;
}
// This outputs identification.
cout << "impersonation level : " << str;
}
}
}
}
void thread_main()
{
impersonate();
// if impersonation is successful, file opening fails otherwise not.
const auto file = CreateFile(R"(C:\foo.txt)", // name of the write
GENERIC_WRITE, // open for writing
0, // do not share
NULL, // default security
CREATE_NEW, // create new file only
FILE_ATTRIBUTE_NORMAL, // normal file
NULL); // no attr. template
if (file == INVALID_HANDLE_VALUE) {
} else {
// Rest of code;
}
}
Хотя текущий пользователь является администратором и добавил «Олицетворять клиента после аутентификации», он по-прежнему сообщает «Идентификация безопасности».
Q: Есть ли что-нибудь еще, чтобы поднять это до безопасности подражать?
Спасибо,
насколько я понимаю, ты поступаешь дальше — ты дублируешь LocalSystem токен от службы к дочернему процессу (через наследующий дескриптор) и передать ему значение дескриптора в командной строке. тогда вы звоните SetThreadToken
.
но документация SetThreadToken
неправильно и неполно
вот только сказал, что токен должен иметь TOKEN_IMPERSONATE
права доступа.
ничего не сказано о правах доступа к дескриптору потока — он должен иметь THREAD_SET_THREAD_TOKEN
но главное:
При использовании SetThreadToken функция для подражания, вы должны
иметь выдавать себя за привилегии и убедитесь, что
SetThreadToken функция успешно
что значит под Вы должны иметь ? обычно это означает, что вызывающий поток (или процесс, которому принадлежит вызывающий поток в случае, если у потока нет токена) должен иметь права олицетворения в токене.
но это неправильно и не правда. какая привилегия у вас (вызывающего потока) — не имеет значения. процесс (даже если целевой поток имеет токен) к которой должен принадлежать целевой (не вызывающий!) поток SeImpersonatePrivilege
привилегия или иметь тот же идентификатор сеанса входа в систему, что и маркер олицетворения, в противном случае .. нет, функция не завершается ошибкой, и возврат завершается успешно, но он молча заменяет SECURITY_IMPERSONATION_LEVEL
участник в токене SecurityIdentification
(смотреть в WRK-v1.2 \ база \ НТО \ пс \ security.c PsImpersonateClient
функция — начать с SeTokenCanImpersonate
(реализовано в WRK-v1.2 \ база \ НТО \ се \ token.c — вот и проверил TOKEN_HAS_IMPERSONATE_PRIVILEGE
и LogonSessionId) и если не удается (STATUS_PRIVILEGE_NOT_HELD
) вернулся SeTokenCanImpersonate
— PsImpersonateClient
набор функций ImpersonationLevel = SecurityIdentification ;
так что даже если вы позвоните SetThreadToken
из службы (которая имеет привилегию олицетворения) для потока дочернего процесса — вызов «fail», если дочерний процесс не имеет привилегии олицетворения. и наоборот — если вы скажете передать (дублировать) собственный дескриптор потока (с THREAD_SET_THREAD_TOKEN
права доступа) к ограниченному процессу, который не имеет права олицетворения — он может успешно вызвать SetThreadToken
за ваш поток — уровень олицетворения не будет сброшен до SecurityIdentification
в вашем случае, потому что дочерний процесс не имеет SeImpersonatePrivilege
(обычно это существует только в повышенных процессах, но если пользователь входит в систему с LOGON32_LOGON_INTERACTIVE
— даже «администраторы» действительно ограничивают токен (поэтому они не являются настоящими администраторами) и имеют другой идентификатор сеанса (сравните идентификатор сеанса токена локальной системы) — после SetThreadToken
ваша нить имеет SecurityIdentification
уровень олицетворения. в результате любой системный вызов, в котором проверена безопасность (скажем, открытый файл или ключ реестра), завершится с ошибкой ERROR_BAD_IMPERSONATION_LEVEL
,
как насчет решения? если у пользователя есть права администратора — вам нужно создать приподнятый дочерний процесс в сеансе пользователя (например, «запуск от имени администратора»). для этого вам нужен тип возвышения запроса токена, возвращаемого WTSQueryUserToken
и если это TokenElevationTypeLimited
— нам нужно получить связанный токен от GetTokenInformation
позвонить с TokenLinkedToken
.
это полностью недокументировано, но какой токен возвращен в TOKEN_LINKED_TOKEN
структура зависит от призвание поток (или процесс) есть SE_TCB_PRIVILEGE
— Если да — TokenPrimary
возвращается иначе TokenImpersonation
возвращается с SECURITY_IMPERSONATION_LEVEL
установлен в SecurityIdentification
(поэтому этот токен можно использовать только для запроса). потому что служба работает под учетной записью локальной системы имеют SE_TCB_PRIVILEGE
— вы получили основной токен, который вам нужно использовать в CreateProcessAsUser
называть как есть. поэтому вам нужна следующая функция:
ULONG GetElevatedUserToken(PHANDLE phToken)
{
union {
ULONG SessionId;
TOKEN_ELEVATION_TYPE tet;
TOKEN_LINKED_TOKEN tlt;
TOKEN_TYPE tt;
};
SessionId = WTSGetActiveConsoleSessionId();
if (SessionId == MAXDWORD)
{
return ERROR_NO_SUCH_LOGON_SESSION;
}
HANDLE hToken;
if (!WTSQueryUserToken(SessionId, &hToken))
{
return GetLastError();
}
ULONG len;
ULONG dwError = NOERROR;
if (GetTokenInformation(hToken, TokenElevationType, &tet, sizeof(tet), &len))
{
if (tet == TokenElevationTypeLimited)
{
if (GetTokenInformation(hToken, TokenLinkedToken, &tlt, sizeof(tlt), &len))
{
CloseHandle(hToken);
hToken = tlt.LinkedToken;
}
else
{
dwError = GetLastError();
}
}
}
else
{
dwError = GetLastError();
}
if (dwError == NOERROR)
{
if (GetTokenInformation(hToken, TokenType, &tt, sizeof(tt), &len))
{
if (tt != TokenPrimary)
{
dwError = ERROR_INVALID_HANDLE;
}
}
else
{
dwError = GetLastError();
}
if (dwError == NOERROR)
{
*phToken = hToken;
return NOERROR;
}
CloseHandle(hToken);
}
return dwError;
}
и использовать следующий код для запуска ребенка
HANDLE hToken;
ULONG dwError = GetElevatedUserToken(&hToken);
if (dwError == NOERROR)
{
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
//***
if (CreateProcessAsUser(hToken, ***, &si, &pi))
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
CloseHandle(hToken);
}
в этом случае вам может не понадобиться выдавать себя за LocalSystem в дочернем процессе. Однако, если все еще нужно LocalSystem — вы можете продублировать такой токен в дочернем процессе и в этом случае SetThreadtoken
будет полностью нормально, потому что дочерний процесс будет иметь олицетворение привилегий
Простите за вопрос, что должно быть очевидным, но это нужно спросить:
Вы проверяете возвращаемые значения этих функций? Вызываете GetLastError, когда они терпят неудачу? Какие коды ошибок вы получаете обратно?
Если это C ++, вы устанавливаете обработчик необработанного исключения?