Як перевірити облікові дані домену?


86

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

Username: STACKOVERFLOW\joel
Password: splotchy

Спосіб 1. Запит Active Directory з видаванням себе за іншу особу

Багато людей пропонують щось запитати в Active Directory. Якщо видається виняток, ви знаєте, що облікові дані не є дійсними - як пропонується у цьому запитанні про stackoverflow .

Однак у цього підходу є кілька серйозних недоліків :

  1. Ви не тільки аутентифікуєте обліковий запис домену, але й виконуєте неявну перевірку авторизації. Тобто ви зчитуєте властивості з AD, використовуючи маркер уособлення. Що робити, якщо дійсний інакше рахунок не має права читати з AD? За замовчуванням усі користувачі мають доступ для читання, але політики домену можуть бути встановлені для вимкнення дозволів на доступ для обмежених облікових записів (та або груп).

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

  3. Ви покладаєтесь на помилку винятку для не виняткових випадків і припускаєте, що це означає недійсні ім’я користувача та пароль. Інші проблеми (наприклад, збій мережі, збій підключення AD, помилка виділення пам'яті тощо) потім трактуються неправильно як збій автентифікації.

Спосіб 2. LogonUser Win32 API

Інші пропонують використовувати функцію LogonUser()API. Це звучить приємно, але, на жаль, користувачеві, що телефонує, іноді потрібен дозвіл, який зазвичай надається лише самій операційній системі:

Процес виклику 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 (у Менеджері користувачів це право « Діяти як частина операційної системи »). Привілей SE_TCB_NAME дуже потужний і не повинен надаватися будь-якому довільному користувачеві лише для того, щоб він міг запустити програму, яка повинна перевірити облікові дані.

Крім того, виклик до LogonUser()не вдасться, якщо вказано порожній пароль.


Який правильний спосіб автентифікації набору облікових даних домену?


Я випадково телефоную з керованого коду, але це загальне питання для Windows. Можна припустити, що у клієнтів встановлено .NET Framework 2.0.


1
Читачам слід зауважити, що з Windows XP LogonUser більше не вимагає SE_TCB_NAME (якщо ви не входите в обліковий запис Passport).
Harry Johnston

Відповіді:


130

C # у .NET 3.5 за допомогою System.DirectoryServices.AccountManagement .

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

Це підтвердить дію до поточного домену. Інші варіанти можна переглянути у параметризованому конструкторі PrincipalContext.


@tvanfosson: чи DirectoryServices не використовує AD?
Mitch Wheat

1
Так. Але документація вказує, що це швидкий спосіб перевірки облікових даних. Він також відрізняється від методу прив'язки, згаданого у питанні, оскільки ви не читаєте жодних властивостей об'єкта. Зверніть увагу, що метод знаходиться на контексті, а не на об'єкті каталогу.
tvanfosson

Виправлення: System.DirectoryServices.AccountManagement вимагає .NET 3.5. ( msdn.microsoft.com/en-us/library/… )
Ян Бойд,

19
Він також працює з місцевими користувачами, якщо ви використовували new PrincipalContext(ContextType.Machine)замість них.
VansFannel

Хтось знає, чи це працює з кешованими обліковими даними, чи вимагає підключення до DC? Мені потрібно це знати для певної реалізації, над якою я зараз працюю, і зараз я не перебуваю в жодному домені для тестування
Jcl

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

7
чи містить це якусь істотну різницю у відповіді @ tvanfosson 3 роки тому?
gbjbaanb

5
@gbjbaanb Так, оскільки він містить Domainпараметр під час створення того PrincipalContext, що мені було цікаво знати і знайшов у цій відповіді.
Руді Віссер

1
@RudiVisser tvanfosson запропонував вам "Перевірте параметризований конструктор PrincipalContext для інших варіантів" - завжди читайте документи, ніколи не беріть нічого просто для Інтернету! :)
gbjbaanb

4
@gbjbaanb Так, звичайно, але і забезпечує робочий приклад , а не посилання і пропозиція прочитати в іншому місці мантра StackOverflow, тому ми приймаємо кілька подань відповідей: D Просто сказати , що це дійсно забезпечує більш.
Руді Віссер

Хтось знає, як ми могли зробити щось подібне в програмі UWP? (із звичайним AD, а не з Azure AD). Я задав питання тут: stackoverflow.com/questions/42821447
slayernoah

7

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

Я шукав щось подібне протягом століть ... Тож, сподіваюся, це комусь допоможе!

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

це "метод 2", описаний у питанні ... так що ... насправді не відповідаючи на питання
Роберт Леві

1

Ось як визначити локального користувача:

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

Редагувати Ян Бойд

Вам більше не слід використовувати NTLM. Він настільки старий і настільки поганий, що програма перевірки програм Microsoft (яка використовується для виявлення типових помилок програмування) видасть попередження, якщо виявить, що ви використовуєте NTLM.

Ось розділ із документації Програми перевірки про те, чому вони проходять тест, якщо хтось помилково використовує NTLM:

Чому потрібен плагін NTLM

NTLM - це застарілий протокол автентифікації з вадами, які потенційно загрожують безпеці програм і операційної системи. Найважливішим недоліком є ​​відсутність автентифікації сервера, що може дозволити зловмисникові обдурити користувачів підключенням до підробленого сервера. Як наслідок відсутності автентифікації сервера, програми, що використовують NTLM, також можуть бути вразливими до типу атаки, відомого як атака "відображення". Це останнє дозволяє зловмисникові викрасти розмову автентифікації користувача на законний сервер і використовувати її для автентифікації зловмисника на комп'ютері користувача. Вразливості NTLM та способи їх використання є метою збільшення науково-дослідницької активності в спільноті безпеки.

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

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

Як працює плагін

Штекер верифікатора виявляє такі помилки:

  • Пакет 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”

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

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


Простір імен System.DirectoryServices.AccountManagement було новим у .NET 3.5
Джеремі Грей,

1
Я знаю, що цьому майже 4 роки, але якщо ви перевіряєте місцевого користувача, вам потрібно буде переконатися, що ви встановили ContextType на ContextType.Machine під час створення PrincipalContext. В іншому випадку він буде думати, що назва машини, вказана у змінній Domain, насправді є сервером домену.
SolidRegardless
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.