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

#c #winapi #kerberos #sspi #credential-manager

#c #winapi #kerberos #sspi #диспетчер учетных данных

Вопрос:

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

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

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

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

Клиентский код:

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

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

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

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

Серверный код:

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

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

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

  • Я попытался изменить первый аргумент этого вызова на "myprotocol/domainuser" , "domainuser" , "mydomaindomainuser" или даже "domainuser@mydomain.com" .
  • Я попытался добавить необходимые учетные данные в диспетчер учетных данных панели управления, используя hostname mycomputer и even domainuser .

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

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

 #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,
        amp;credentials,
        amp;lifetime);
    if(res==SEC_E_OK){
        std::printf("Successn");
        FreeCredentialsHandle(amp;credentials);
        return 0;}
    else{
        std::printf("Failuren");
        return res;}}
  

Комментарии:

1. Как вы настраиваете учетные данные? Это почти всегда будет причиной SEC_E_NO_CREDENTIALS . Также немного странно, что ACH возвращает это. Обычно его возвращает ASC или ISC, поскольку ACH мало что делает под прикрытием.

2. @Steve я не уверен, что понимаю ваш вопрос. Пользователь входит в Windows, используя свою локальную учетную запись, а затем запускает серверный код, который выполняет приведенный выше фрагмент кода. AcquireCredentialsHandle предполагается инициализировать credentials , но это не удается из-за SECPKG_CRED_INBOUND . На стороне клиента тот же вызов with SECPKG_CRED_OUTBOUND работает отлично.

3. Извините, делал это из памяти и неправильно запомнил параметры. Вы передаете null authdata (параметр 5) при исходящем вызове ACH, поэтому он должен полагаться на локальные учетные данные по умолчанию. Однако учетные данные локального пользователя не поддерживают единый вход таким образом, поскольку он не знает, в какой области вы находитесь.

4. Проверьте этот пример , чтобы получить учетные данные для конкретной учетной записи пользователя.

5. @DrakeWu-MSFT Это работает. Пожалуйста, напишите ответ со ссылкой. Я проголосую.

Ответ №1:

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

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

 #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( amp;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.
      amp;ClientAuthID,         // User information.
      NULL,                  // Not used with Digest SSP.
      NULL,                  // Not used with Digest SSP.
      amp;hCred,                // Receives the credential handle.
      amp;tsLifetime            // Receives the credential time limit.
    );
}