Сбой подмены LocalSystem в C ++ в дочернем процессе

Пытаюсь решить ее, но все усилия пока тщетны. Рабочий процесс следующим образом

Служба 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: Есть ли что-нибудь еще, чтобы поднять это до безопасности подражать?
Спасибо,

0

Решение

насколько я понимаю, ты поступаешь дальше — ты дублируешь 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) вернулся SeTokenCanImpersonatePsImpersonateClient набор функций 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 будет полностью нормально, потому что дочерний процесс будет иметь олицетворение привилегий

2

Другие решения

Простите за вопрос, что должно быть очевидным, но это нужно спросить:

Вы проверяете возвращаемые значения этих функций? Вызываете GetLastError, когда они терпят неудачу? Какие коды ошибок вы получаете обратно?

Если это C ++, вы устанавливаете обработчик необработанного исключения?

0

По вопросам рекламы [email protected]