Как проверить учетные данные домена? - PullRequest
78 голосов
/ 29 ноября 2008

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

Username: STACKOVERFLOW\joel
Password: splotchy

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

Многие люди предлагают что-то запросить в Active Directory. Если выдается исключение, то вы знаете, что учетные данные недействительны - как предложено в в этом вопросе о стеке .

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

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

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

  3. Вы полагаетесь на ошибку исключения для неисключительного случая, и предполагаете, что это означает неверное имя пользователя и пароль. Другие проблемы (например, сбой сети, сбой подключения 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 - включен для всех пользователей, но некоторые администраторы могут отключить его для все.

Выдача привилегии « Act как часть операционной системы » - это не то, что вы хотите делать волей-неволей - как Microsoft указывает в статье базы знаний

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

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


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


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

Ответы [ 5 ]

118 голосов
/ 29 ноября 2008

C # в .NET 3.5 с использованием System.DirectoryServices.AccountManagement .

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

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

18 голосов
/ 03 августа 2011
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);
        }
    }
}
5 голосов
/ 06 ноября 2014

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

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

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;
        }
    }
1 голос
/ 03 февраля 2015

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

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

Ред. Ян Бойд

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

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

Почему необходим плагин NTLM

NTLM - это устаревший протокол аутентификации с недостатками, которые потенциально ставит под угрозу безопасность приложений и операционной система. Наиболее важным недостатком является отсутствие сервера аутентификация, которая может позволить злоумышленнику обмануть пользователей подключение к поддельному серверу. Как следствие пропавшего сервера приложения, использующие NTLM, также могут быть уязвимы тип атаки, известный как атака «отражения». Этот последний позволяет злоумышленник, чтобы перехватить разговор аутентификации пользователя с законный сервер и использовать его для аутентификации злоумышленника на компьютер пользователя. Уязвимости NTLM и способы их использования являются целью увеличения исследовательской деятельности в области безопасности сообщества.

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

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

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

APIs(parameter) Used by Application    Incorrect Value  Correct Value  
=====================================  ===============  ========================
AcquireCredentialsHandle (pszPackage)  “NTLM”           NEGOSSP_NAME “Negotiate”
0 голосов
/ 02 марта 2009
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;
    }
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...