Как проверить учетные данные домена?

Я хочу проверить набор учетных данных для контроллера домена. например:.

Username: STACKOVERFLOW\joel
Password: splotchy

Способ 1. Запрос Active Directory с олицетворением

Многие люди предлагают запросить Active Directory для чего-то. Если выбрано исключение, то вы знаете, что учетные данные недействительны - как это предлагается в qaru.site/info/15868/....

Есть некоторые серьезные недостатки этого подхода:

  • Вы не только аутентифицируете учетную запись домена, но также выполняете неявную проверку авторизации. То есть вы читаете свойства из AD, используя маркер олицетворения. Что делать, если в противном случае действительная учетная запись не имеет права читать из AD? По умолчанию все пользователи имеют доступ на чтение, но для политик домена можно отключить разрешения доступа для ограниченных учетных записей (или групп).

  • Привязка к AD имеет серьезные накладные расходы, кеш AD-схемы должен быть загружен на клиенте (кеш ADSI в поставщике ADSI, используемом DirectoryServices). Это как сетевой, так и серверы AD, потребляющие ресурсы - и слишком дороги для простой операции, такой как аутентификация учетной записи пользователя.

  • Вы полагаетесь на отказ исключения для случая, отличного от исключительных, и считаете, что это означает неправильное имя пользователя и пароль. Другие проблемы (например, сбой в сети, отказ подключения AD, ошибка выделения памяти и т.д.) Затем неправильно интерпретируются как сбой аутентификации.

Метод 2. LogonUser Win32 API

Другие предложили использовать функцию API LogonUser(). Это звучит неплохо, но, к сожалению, вызывающему пользователю иногда требуется разрешение, обычно предоставляемое самой операционной системе:

Для вызова процесса LogonUser требуется привилегия SE_TCB_NAME. Если вызывающий процесс не имеет этого привилегии, LogonUser выходит из строя и GetLastError возвращает ERROR_PRIVILEGE_NOT_HELD.

В некоторых случаях, процесс, который вызывает LogonUser также должен иметь SE_CHANGE_NOTIFY_NAME привилегия включен; в противном случае LogonUser не работает и GetLastError возвращается ERROR_ACCESS_DENIED. Эта привилегия не требуется для локальной системы учетные записи или учетные записи, которые являются членами группы администраторов. От по умолчанию, SE_CHANGE_NOTIFY_NAME для всех пользователей, но некоторые администраторы могут отключить его для каждый.

Предоставление привилегии "Акт как часть операционной системы" - это не то, что вы хотите делать волей-неволей - как указывает Microsoft в статья базы знаний:

... процесс, вызывающий LogonUser должен иметь SE_TCB_NAME привилегии (в User Manager это "Закон как часть действующего Система" справа). SE_TCB_NAME привилегия очень сильна и не должны предоставляться произвольным пользователям, чтобы они могли запустите приложение, которое должно проверять учетные данные.

Кроме того, вызов LogonUser() завершится с ошибкой, если указан пустой пароль.


Каков правильный способ аутентификации набора учетных данных домена?


Я звоню из управляемого кода, но это общий вопрос Windows. Можно предположить, что у клиентов установлен .NET Framework 2.0.

Ответ 1

С# в .NET 3.5 с помощью System.DirectoryServices.AccountManagement.

 bool valid = false;
 using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
 {
     valid = context.ValidateCredentials( username, password );
 }

Это будет проверяться на текущий домен. Ознакомьтесь с параметризованным конструктором PrincipalContext для других параметров.

Ответ 2

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security;
using System.DirectoryServices.AccountManagement;

public struct Credentials
{
    public string Username;
    public string Password;
}

public class Domain_Authentication
{
    public Credentials Credentials;
    public string Domain;

    public Domain_Authentication(string Username, string Password, string SDomain)
    {
        Credentials.Username = Username;
        Credentials.Password = Password;
        Domain = SDomain;
    }

    public bool IsValid()
    {
        using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, Domain))
        {
            // validate the credentials
            return pc.ValidateCredentials(Credentials.Username, Credentials.Password);
        }
    }
}

Ответ 3

Я использую следующий код для проверки учетных данных. Метод, показанный ниже, будет подтверждать правильность учетных данных и, если это не так, срок действия пароля или его изменение.

Я искал что-то вроде этого целую вечность... Поэтому я надеюсь, что это поможет кому-то!

using System;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
using System.Runtime.InteropServices;

namespace User
{
    public static class UserValidation
    {
        [DllImport("advapi32.dll", SetLastError = true)]
        static extern bool LogonUser(string principal, string authority, string password, LogonTypes logonType, LogonProviders logonProvider, out IntPtr token);
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool CloseHandle(IntPtr handle);
        enum LogonProviders : uint
        {
            Default = 0, // default for platform (use this!)
            WinNT35,     // sends smoke signals to authority
            WinNT40,     // uses NTLM
            WinNT50      // negotiates Kerb or NTLM
        }
        enum LogonTypes : uint
        {
            Interactive = 2,
            Network = 3,
            Batch = 4,
            Service = 5,
            Unlock = 7,
            NetworkCleartext = 8,
            NewCredentials = 9
        }
        public  const int ERROR_PASSWORD_MUST_CHANGE = 1907;
        public  const int ERROR_LOGON_FAILURE = 1326;
        public  const int ERROR_ACCOUNT_RESTRICTION = 1327;
        public  const int ERROR_ACCOUNT_DISABLED = 1331;
        public  const int ERROR_INVALID_LOGON_HOURS = 1328;
        public  const int ERROR_NO_LOGON_SERVERS = 1311;
        public  const int ERROR_INVALID_WORKSTATION = 1329;
        public  const int ERROR_ACCOUNT_LOCKED_OUT = 1909;      //It gives this error if the account is locked, REGARDLESS OF WHETHER VALID CREDENTIALS WERE PROVIDED!!!
        public  const int ERROR_ACCOUNT_EXPIRED = 1793;
        public  const int ERROR_PASSWORD_EXPIRED = 1330;

        public static int CheckUserLogon(string username, string password, string domain_fqdn)
        {
            int errorCode = 0;
            using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, domain_fqdn, "ADMIN_USER", "PASSWORD"))
            {
                if (!pc.ValidateCredentials(username, password))
                {
                    IntPtr token = new IntPtr();
                    try
                    {
                        if (!LogonUser(username, domain_fqdn, password, LogonTypes.Network, LogonProviders.Default, out token))
                        {
                            errorCode = Marshal.GetLastWin32Error();
                        }
                    }
                    catch (Exception)
                    {
                        throw;
                    }
                    finally
                    {
                        CloseHandle(token);
                    }
                }
            }
            return errorCode;
        }
    }

Ответ 4

Здесь, как определить локального пользователя:

    public bool IsLocalUser()
    {
        return windowsIdentity.AuthenticationType == "NTLM";
    }

Редактировать Ян Бойд

Вы больше не должны использовать NTLM. Он настолько стар и настолько плох, что Microsoft Application Verifier (который используется для обнаружения общих ошибок программирования) выдает предупреждение, если он обнаруживает, что вы используете NTLM.

В этой главе из документации Application Verifier о том, почему у них есть тест, если кто-то ошибочно использует NTLM:

Для чего нужен подключаемый модуль NTLM

NTLM - это устаревший протокол аутентификации с изъянами, которые потенциально могут поставить под угрозу безопасность приложений и система. Самым важным недостатком является отсутствие сервера аутентификация, которая может позволить злоумышленнику обмануть пользователей в подключение к поддельному серверу. В качестве следствия недостающего сервера аутентификации, приложения, использующие NTLM, также могут быть уязвимы для тип атаки, называемый атакой "отражения". Последнее позволяет атакующему, чтобы захватить сеанс аутентификации пользователей законного сервера и использовать его для аутентификации злоумышленника компьютер пользователей. Уязвимости NTLM и способы их использования являются целью увеличения исследовательской деятельности в сфере безопасности сообщества.

Хотя Kerberos уже много лет доступен для многих приложений все еще написаны для использования только NTLM. Это неизбежно уменьшает безопасность приложений. Однако Kerberos не может заменить NTLM во всех сценарии - в основном те, в которых клиент должен аутентифицироваться системы, которые не объединены с доменом (домашняя сеть, возможно, являющаяся наиболее распространенные из них). Пакет безопасности Negotiate позволяет обратный совместимый компромисс, который использует Kerberos по возможности и только возвращается в NTLM, когда нет другого варианта. Код переключения для использования Negotiate вместо NTLM значительно увеличит безопасность для наших клиентов при введении небольшого количества заявок или их отсутствие Совместимость. Переговоры сами по себе не являются серебряной пулей - там являются случаями, когда злоумышленник может принудительно понизить ставку до NTLM, но это значительно труднее эксплуатировать. Однако, сразу улучшение заключается в том, что приложения, написанные для правильного ведения переговоров автоматически невосприимчивы к атакам отражения NTLM.

В качестве последнего слова предостережения против использования NTLM: в будущем версиях Windows можно будет отключить использование NTLM при операционной системы. Если приложения сильно зависят от NTLM они просто не смогут аутентифицироваться, когда NTLM отключен.

Как работает подключаемый модуль

Проигрыватель Verifier обнаруживает следующие ошибки:

  • Пакет NTLM напрямую указан в вызове AcquireCredentialsHandle (или API-интерфейсе более высокого уровня).

  • Имя цели в вызове InitializeSecurityContext равно NULL.

  • Имя цели в вызове InitializeSecurityContext не является правильно сформированным доменным именем типа SPN, UPN или NetBIOS.

Последние два случая заставят Negotiate вернуться в NTLM либо напрямую (первый случай), либо косвенно (контроллер домена вернет ошибку "главный не найден" во втором случае, заставив Negotiate отступить).

Плагин также регистрирует предупреждения, когда обнаруживает понижение до NTLM; например, когда SPN не найден контроллером домена. Они регистрируются только как предупреждения, поскольку они часто являются законными случаями - например, при аутентификации в систему, не присоединенную к домену.

Остатки NTLM

5000 - приложение имеет явно выбранный пакет NTLM

Тяжесть - ошибка

Приложение или подсистема явно выбирает NTLM вместо Negotiate в вызове AcquireCredentialsHandle. Несмотря на то, что клиент и сервер могут аутентифицироваться с использованием Kerberos, это предотвращается явным выбором NTLM.

Как исправить эту ошибку

Исправление этой ошибки состоит в том, чтобы выбрать пакет Negotiate вместо NTLM. Это будет зависеть от конкретной подсистемы сети, используемой клиентом или сервером. Ниже приведены некоторые примеры. Вы должны проконсультироваться с документацией по конкретной библиотеке или набору API, которые вы используете.

APIs(parameter) Used by Application    Incorrect Value  Correct Value  
=====================================  ===============  ========================
AcquireCredentialsHandle (pszPackage)  "NTLM"           NEGOSSP_NAME "Negotiate"

Ответ 5

using System;
using System.Collections.Generic;
using System.Text;
using System.DirectoryServices.AccountManagement;

class WindowsCred
{
    private const string SPLIT_1 = "\\";

    public static bool ValidateW(string UserName, string Password)
    {
        bool valid = false;
        string Domain = "";

        if (UserName.IndexOf("\\") != -1)
        {
            string[] arrT = UserName.Split(SPLIT_1[0]);
            Domain = arrT[0];
            UserName = arrT[1];
        }

        if (Domain.Length == 0)
        {
            Domain = System.Environment.MachineName;
        }

        using (PrincipalContext context = new PrincipalContext(ContextType.Domain, Domain)) 
        {
            valid = context.ValidateCredentials(UserName, Password);
        }

        return valid;
    }
}

Кашиф Муштак Оттава, Канада