Получение дескрипторов учетных данных пользователя домена из процесса, запущенного локальным пользователем в Windows 10

Предыстория:

  • Компьютер mycomputer работает под управлением Windows 10 и присоединен к домену mydomain.com.
  • Пользователь зарегистрирован с локальной учетной записью mycomputer\localuser на mycomputer.
  • Пользователь также знает пароль учетной записи домена mydomain\domainuser.
  • Имя субъекта-службы myprotocol/domainuser зарегистрировано в Active Directory и сопоставляется с учетной записью домена mydomain\domainuser.
  • Локальному пользователю mycomputer\localuser не разрешено запускать процесс как mydomain\domainuser.

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

Я хочу написать код этого сервера.

Код клиента:

Код клиента прост и состоит из вызова AcquireCredentialsHandle, за которым следует вызов InitializeSecurityContext:

AcquireCredentialsHandle(
    nullptr,
    "Kerberos",
    SECPKG_CRED_OUTBOUND,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    &credentials,
    &lifetime);
InitializeSecurityContext(
    &credentials,
    nullptr,
    "myprotocol/myport",
    ISC_REQ_CONFIDENTIALITY,
    0,
    SECURITY_NATIVE_DREP,
    nullptr,
    0,
    &securityContext,
    &outBufferArray,
    &contextAttributes,
    &lifetime);

Обратите внимание на упрощенное использование строк во фрагментах кода. Реальность, в которой приходится иметь дело с wchar_t и const правильностью, несколько уродливее.

Также обратите внимание, что этот код работает при запуске локальным пользователем, если соответствующие учетные данные хранятся в диспетчере учетных данных панели управления, то есть с именем хоста domainuser (sic.)

Код сервера:

У меня уже есть код, который работает, когда процесс запущен mydomain\domainuser:

AcquireCredentialsHandle(
    nullptr,
    "Kerberos",
    SECPKG_CRED_INBOUND,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    &credentials,
    &lifetime);
AcceptSecurityContext(
    &credentials,
    nullptr,
    &inBufferArray,
    attribs,
    SECURITY_NATIVE_DREP,
    &securityContext,
    nullptr,
    &attribs,
    &lifetime);

Но когда сервер запускается mycomputer\localuser, вызов AcquireCredentialsHandle завершается с ошибкой с кодом SEC_E_NO_CREDENTIALS.

  • Я попытался изменить первый аргумент этого вызова на "myprotocol/domainuser", "domainuser", "mydomain\domainuser" или даже "[email protected]".
  • Я попытался добавить необходимые учетные данные в диспетчере учетных данных панели управления, используя имя хоста mycomputer и даже domainuser.

Что я могу сделать, чтобы получить дескриптор учетных данных mydomain\domainuser в процессе, запущенном mycomputer\localuser ?

Компиляция фрагмента кода:

#include <string>

#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define SECURITY_WIN32
#include <sspi.h>//Requires linking on Secur32.lib

int main(){
    CredHandle credentials;
    TimeStamp lifetime;
    std::string package="Kerberos";
    std::string principal="myprotocol/domainuser";
    auto res=AcquireCredentialsHandle(
        principal.data(),
        package.data(),
        SECPKG_CRED_INBOUND,
        nullptr,
        nullptr,
        nullptr,
        nullptr,
        &credentials,
        &lifetime);
    if(res==SEC_E_OK){
        std::printf("Success\n");
        FreeCredentialsHandle(&credentials);
        return 0;}
    else{
        std::printf("Failure\n");
        return res;}}

person Arnaud    schedule 29.09.2020    source источник
comment
Как вы устанавливаете учетные данные? Это почти всегда будет причиной SEC_E_NO_CREDENTIALS. Также немного странно, что ACH возвращает это. Обычно его возвращает ASC или ISC, так как ACH мало что делает под прикрытием.   -  person Steve    schedule 29.09.2020
comment
@ Стив, я не уверен, что понимаю твой вопрос. Пользователь входит в Windows, используя свою локальную учетную запись, а затем запускает серверный код, который выполняет приведенный выше фрагмент кода. AcquireCredentialsHandle должен инициализировать credentials, но это не удается из-за SECPKG_CRED_INBOUND. На стороне клиента тот же вызов с SECPKG_CRED_OUTBOUND работает отлично.   -  person Arnaud    schedule 29.09.2020
comment
Извините, делал это по памяти и неправильно запомнил параметры. Вы передаете нулевые данные аутентификации (параметр 5) при исходящем вызове ACH, поэтому он должен полагаться на локальные учетные данные по умолчанию. Однако учетные данные локального пользователя не поддерживают единый вход таким образом, потому что он не знает, в какой области вы находитесь.   -  person Steve    schedule 29.09.2020
comment
Проверьте этот пример, чтобы получить учетные данные. для конкретной учетной записи пользователя.   -  person Drake Wu    schedule 30.09.2020
comment
@DrakeWu-MSFT Это работает. Пожалуйста, напишите ответ со ссылкой. Я буду голосовать.   -  person Arnaud    schedule 30.09.2020


Ответы (1)


Чтобы получить учетные данные, отличные от тех, которые связаны с текущим сеансом входа, заполните SEC_WINNT_AUTH_IDENTITY с информацией об альтернативном субъекте безопасности. Передайте структуру функции AcquireCredentialsHandle, используя параметр pAuthData.

И этот пример microsoft демонстрирует вызов на стороне клиента для получения учетных данных Digest для определенной учетной записи пользователя:

#include <windows.h>

#ifdef UNICODE
  ClientAuthID.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
#else
  ClientAuthID.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
#endif

void main()
{
    SECURITY_STATUS SecStatus; 
    TimeStamp tsLifetime; 
    CredHandle hCred;
    SEC_WINNT_AUTH_IDENTITY ClientAuthID;
    LPTSTR UserName = TEXT("ASecurityPrinciple");
    LPTSTR DomainName = TEXT("AnAuthenticatingDomain");

    // Initialize the memory.
    ZeroMemory( &ClientAuthID, sizeof(ClientAuthID) );

    // Specify string format for the ClientAuthID structure.


    // Specify an alternate user, domain and password.
      ClientAuthID.User = (unsigned char *) UserName;
      ClientAuthID.UserLength = _tcslen(UserName);

      ClientAuthID.Domain = (unsigned char *) DomainName;
      ClientAuthID.DomainLength = _tcslen(DomainName);

    // Password is an application-defined LPTSTR variable
    // containing the user password.
      ClientAuthID.Password = Password;
      ClientAuthID.PasswordLength = _tcslen(Password);

    // Get the client side credential handle.
    SecStatus = AcquireCredentialsHandle (
      NULL,                  // Default principal.
      WDIGEST_SP_NAME,       // The Digest SSP. 
      SECPKG_CRED_OUTBOUND,  // Client will use the credentials.
      NULL,                  // Do not specify LOGON id.
      &ClientAuthID,         // User information.
      NULL,                  // Not used with Digest SSP.
      NULL,                  // Not used with Digest SSP.
      &hCred,                // Receives the credential handle.
      &tsLifetime            // Receives the credential time limit.
    );
}
person Drake Wu    schedule 30.09.2020