简体   繁体   中英

C++ windows LocalSystem impersonation in child process failing

Trying to resolve it but all efforts are in vain so far. The workflow as follows

Windows service running as LocalSystem creates child using CreateProcessAsUser(...) with token of current logged user.

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

The child process is launched in user workstation\\desktop and main thread captures user I/O events. The child process impersonation is as follows

 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;
  }
} 

Though current user is Administrator and added "Impersonate a Client After authentication" it still reporting "Security Identification".

Q: Is there anything else required to raise it to Security impersonate? Thanks,

how I understand you do next - you duplicate LocalSystem token, from service, to child process (via inherit handle) and pass it handle value in command line. then you call SetThreadToken .

but documentation of SetThreadToken is wrong and incomplete.

here only said that token must have TOKEN_IMPERSONATE access rights. nothing said about Thread handle access rights - it must have THREAD_SET_THREAD_TOKEN

but main:

When using the SetThreadToken function to impersonate, you must have the impersonate privileges and make sure that the SetThreadToken function succeeds

what is mean under you must have ? usually this mean that calling thread (or process to which calling thread belong in case thread have no token) must have impersonate privileges in token.

but this is wrong and not true. which privilege you ( calling thread ) have - does not matter. the process ( even if target thread have token ) to which target (not calling !) thread belong must have SeImpersonatePrivilege privilege or have the same logon session id as impersonation token, otherwise .. no, function not fail, and return succeeds, but it silently replace SECURITY_IMPERSONATION_LEVEL member in token to SecurityIdentification (look in WRK-v1.2\\base\\ntos\\ps\\security.c PsImpersonateClient function - begin from SeTokenCanImpersonate (implemented in WRK-v1.2\\base\\ntos\\se\\token.c - here and checked TOKEN_HAS_IMPERSONATE_PRIVILEGE and LogonSessionId) and if fail ( STATUS_PRIVILEGE_NOT_HELD ) returned by SeTokenCanImpersonate - the PsImpersonateClient function set ImpersonationLevel = SecurityIdentification ;

so even if you call SetThreadToken from service (which have impersonation privilege) for child process thread - call is "fail" if child process have not impersonation privilege. and visa versa - if you say pass(duplicate) own thread handle (with THREAD_SET_THREAD_TOKEN access rights) to restricted process, which have not impersonation privilege - he can success call SetThreadToken for your thread - impersonation level will be not reset to SecurityIdentification

in your case, because child process have no SeImpersonatePrivilege (usually it exist only in elevated processes, but if user enter to system with LOGON32_LOGON_INTERACTIVE - even "admins" have really restricted token (so they not really true admins)) and have different session id (compare local system token session id) - after SetThreadToken your thread have SecurityIdentification impersonation level. as result any system call, where security checked (say open file or registry key) will fail with error ERROR_BAD_IMPERSONATION_LEVEL .

how about solution ? if user have admin privileges - you need create elevated child process in user session (like "run as admin" ). for this you need query elevation type of token returned by WTSQueryUserToken and if it is TokenElevationTypeLimited - we need get linked token by GetTokenInformation call with TokenLinkedToken .

this is complete undocumented, but which token returned in TOKEN_LINKED_TOKEN structure depend from are calling thread (or process) have SE_TCB_PRIVILEGE - if yes - TokenPrimary is returned. otherwise TokenImpersonation is returned with SECURITY_IMPERSONATION_LEVEL set to SecurityIdentification (so this token can be used only for query). because service running under Local system account have SE_TCB_PRIVILEGE - you got the primary token, which you need use in CreateProcessAsUser call as is. so you need next function:

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;
}

and use next code for start child

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);
}

in this case you may be not need at all impersonate to LocalSystem in child process. however if still need LocalSystem - you can duplicate such token in child process and in this case SetThreadtoken will be full ok, because child process will be have impersonate privileges

Forgive me for asking what should be obvious but it needs to be asked:

Are you checking the return values of these functions? Calling GetLastError when they fail? What error codes are you getting back?

If this is C++ are you setting an unhandled exception handler?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM