Làm cách nào để xác thực thông tin đăng nhập tên miền?


86

Tôi muốn xác thực một tập hợp thông tin xác thực chống lại bộ điều khiển miền. ví dụ:

Username: STACKOVERFLOW\joel
Password: splotchy

Phương pháp 1. Truy vấn Active Directory với Mạo danh

Rất nhiều người đề nghị truy vấn Active Directory cho một cái gì đó. Nếu một ngoại lệ được đưa ra, thì bạn biết rằng thông tin xác thực không hợp lệ - như được đề xuất trong câu hỏi stackoverflow này .

Tuy nhiên, có một số nhược điểm nghiêm trọng đối với phương pháp này :

  1. Bạn không chỉ xác thực tài khoản miền mà còn đang thực hiện kiểm tra ủy quyền ngầm. Có nghĩa là, bạn đang đọc các thuộc tính từ AD bằng cách sử dụng mã thông báo mạo danh. Điều gì sẽ xảy ra nếu tài khoản hợp lệ khác không có quyền đọc từ QUẢNG CÁO? Theo mặc định, tất cả người dùng đều có quyền truy cập đọc, nhưng các chính sách miền có thể được đặt để tắt quyền truy cập cho các tài khoản (và hoặc nhóm) bị hạn chế.

  2. Ràng buộc với AD có một chi phí nghiêm trọng, bộ đệm ẩn lược đồ AD phải được tải tại máy khách (bộ đệm ADSI trong bộ cung cấp ADSI được DirectoryServices sử dụng). Đây là cả mạng và máy chủ AD, tiêu tốn tài nguyên - và quá đắt cho một hoạt động đơn giản như xác thực tài khoản người dùng.

  3. Bạn đang dựa vào lỗi ngoại lệ cho một trường hợp không ngoại lệ và giả sử điều đó có nghĩa là tên người dùng và mật khẩu không hợp lệ. Các vấn đề khác (ví dụ: lỗi mạng, lỗi kết nối AD, lỗi cấp phát bộ nhớ, v.v.) sau đó được hiểu sai là lỗi xác thực.

Phương pháp 2. LogonUser Win32 API

Những người khác đã đề xuất sử dụng LogonUser()hàm API. Điều này nghe có vẻ hay, nhưng thật không may, người dùng đang gọi điện đôi khi cần một quyền thường chỉ được cấp cho chính hệ điều hành:

Quá trình gọi LogonUser yêu cầu đặc quyền SE_TCB_NAME. Nếu quá trình gọi không có đặc quyền này, LogonUser không thành công và GetLastError trả về ERROR_PRIVILEGE_NOT_HELD.

Trong một số trường hợp, quá trình gọi LogonUser cũng phải bật đặc quyền SE_CHANGE_NOTIFY_NAME; nếu không, LogonUser không thành công và GetLastError trả về ERROR_ACCESS_DENIED. Đặc quyền này không bắt buộc đối với tài khoản hệ thống cục bộ hoặc các tài khoản là thành viên của nhóm quản trị viên. Theo mặc định, SE_CHANGE_NOTIFY_NAME được bật cho tất cả người dùng, nhưng một số quản trị viên có thể tắt nó cho tất cả mọi người.

Cung cấp đặc quyền " Hoạt động như một phần của hệ điều hành " không phải là điều bạn muốn làm - như Microsoft đã chỉ ra trong một bài báo cơ sở kiến ​​thức :

... quá trình đang gọi LogonUser phải có đặc quyền SE_TCB_NAME (trong Trình quản lý người dùng, đây là quyền " Hoạt động như một phần của Hệ điều hành "). Đặc quyền SE_TCB_NAME rất mạnh mẽ và không nên được cấp cho bất kỳ người dùng tùy ý nào chỉ để họ có thể chạy ứng dụng cần xác thực thông tin đăng nhập.

Ngoài ra, một cuộc gọi đến LogonUser()sẽ không thành công nếu một mật khẩu trống được chỉ định.


Cách thích hợp để xác thực một tập hợp thông tin xác thực tên miền là gì?


Tôi tình cờ gọi từ mã được quản lý, nhưng đây là một câu hỏi chung về Windows. Có thể giả định rằng khách hàng đã cài đặt .NET Framework 2.0.


1
Người đọc cần lưu ý rằng kể từ Windows XP, LogonUser không còn yêu cầu SE_TCB_NAME nữa (trừ khi bạn đang đăng nhập vào tài khoản Passport).
Harry Johnston

Câu trả lời:


130

C # trong .NET 3.5 bằng System.DirectoryServices.AccountManagement .

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

Điều này sẽ xác thực với miền hiện tại. Kiểm tra phương thức khởi tạo MajorContext được tham số hóa để biết các tùy chọn khác.


@tvanfosson: DirectoryServices có sử dụng AD không?
Mitch Wheat

1
Đúng. Nhưng tài liệu chỉ ra rằng đây là một cách nhanh chóng để xác thực thông tin đăng nhập. Nó cũng khác với phương thức ràng buộc được đề cập trong câu hỏi vì bạn không đọc bất kỳ thuộc tính nào từ đối tượng. Lưu ý rằng phương thức nằm trên ngữ cảnh, không phải đối tượng thư mục.
tvanfosson

Sửa: System.DirectoryServices.AccountManagement yêu cầu .NET 3.5. ( msdn.microsoft.com/en-us/library/… )
Ian Boyd

19
Nó cũng hoạt động với người dùng địa phương nếu bạn đã sử dụng new PrincipalContext(ContextType.Machine)thay thế.
VansFannel

Có ai biết liệu điều này có hoạt động trên thông tin đăng nhập được lưu trong bộ nhớ đệm không, hay nó có yêu cầu kết nối với DC không? Tôi cần biết điều này đối với một số triển khai mà tôi đang thực hiện và hiện tôi không ở trên bất kỳ miền nào để kiểm tra
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
điều này có khác biệt đáng kể nào với câu trả lời của @ tvanfosson 3 năm trước không?
gbjbaanb

5
@gbjbaanb Có, vì nó chứa Domaintham số khi tạo PrincipalContext, điều gì đó mà tôi muốn biết và tìm thấy trong câu trả lời này.
Rudi Visser

1
@RudiVisser tvanfosson đã gợi ý bạn "Hãy xem phương thức khởi tạo ChiefContext được tham số hóa để biết các tùy chọn khác" - hãy luôn đọc tài liệu, đừng bao giờ chỉ lấy từ Internet cho bất cứ điều gì! :)
gbjbaanb

4
@gbjbaanb Có tất nhiên, nhưng cung cấp một ví dụ làm việc chứ không phải là một liên kết và gợi ý để đọc ở đâu đó là câu thần chú StackOverflow, mà lý do tại sao chúng tôi chấp nhận nhiều lần gửi câu trả lời: D Đơn giản chỉ cần nói rằng đây không cung cấp thêm.
Rudi Visser

Có ai biết cách chúng tôi có thể làm điều gì đó tương tự trong một ứng dụng UWP không? (với AD thông thường và không với Azure AD). Tôi đã hỏi một câu hỏi ở đây: stackoverflow.com/questions/42821447
slayernoah

7

Tôi đang sử dụng mã sau để xác thực thông tin đăng nhập. Phương pháp hiển thị bên dưới sẽ xác nhận xem thông tin đăng nhập có chính xác hay không và nếu không ướt thì mật khẩu đã hết hạn hoặc cần thay đổi.

Tôi đã tìm kiếm một cái gì đó như thế này trong nhiều năm ... Vì vậy, tôi hy vọng điều này sẽ giúp ai đó!

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

đây là "phương pháp 2" được mô tả trong câu hỏi ... như vậy ... không thực sự trả lời câu hỏi
Robert Levy

1

Đây là cách xác định người dùng cục bộ:

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

Chỉnh sửa bởi Ian Boyd

Bạn không nên sử dụng NTLM nữa. Nó quá cũ và tệ đến mức Trình xác minh ứng dụng của Microsoft (được sử dụng để bắt những lỗi lập trình phổ biến) sẽ đưa ra cảnh báo nếu phát hiện bạn sử dụng NTLM.

Dưới đây là một chương từ tài liệu Trình xác minh ứng dụng về lý do tại sao họ có một bài kiểm tra nếu ai đó đang sử dụng nhầm NTLM:

Tại sao Cần có Trình cắm NTLM

NTLM là một giao thức xác thực đã lỗi thời với các lỗ hổng có thể ảnh hưởng đến bảo mật của các ứng dụng và hệ điều hành. Thiếu sót quan trọng nhất là thiếu xác thực máy chủ, có thể cho phép kẻ tấn công lừa người dùng kết nối với một máy chủ giả mạo. Như một hệ quả của việc thiếu xác thực máy chủ, các ứng dụng sử dụng NTLM cũng có thể dễ bị tấn công bởi một kiểu tấn công được gọi là tấn công “phản chiếu”. Điều này cho phép kẻ tấn công chiếm quyền điều khiển cuộc hội thoại xác thực của người dùng đến một máy chủ hợp pháp và sử dụng nó để xác thực kẻ tấn công vào máy tính của người dùng. Các lỗ hổng và cách khai thác của NTLM là mục tiêu của hoạt động nghiên cứu ngày càng tăng trong cộng đồng bảo mật.

Mặc dù Kerberos đã có sẵn trong nhiều năm, nhiều ứng dụng vẫn được viết để chỉ sử dụng NTLM. Điều này không cần thiết làm giảm tính bảo mật của các ứng dụng. Tuy nhiên, Kerberos không thể thay thế NTLM trong tất cả các trường hợp - chủ yếu là những trường hợp mà khách hàng cần xác thực với các hệ thống không được tham gia vào miền (mạng gia đình có lẽ là phổ biến nhất trong số này). Gói bảo mật Đàm phán cho phép thỏa hiệp tương thích ngược sử dụng Kerberos bất cứ khi nào có thể và chỉ hoàn nguyên về NTLM khi không có tùy chọn nào khác. Việc chuyển mã sang sử dụng Negotiate thay vì NTLM sẽ tăng cường bảo mật đáng kể cho khách hàng của chúng tôi trong khi giới thiệu ít hoặc không có khả năng tương thích ứng dụng. Bản thân Negotiate không phải là một viên đạn bạc - có những trường hợp kẻ tấn công có thể buộc hạ cấp xuống NTLM nhưng chúng khó khai thác hơn đáng kể. Tuy nhiên, một cải tiến tức thì là các ứng dụng được viết để sử dụng Negotiate đúng cách sẽ tự động miễn nhiễm với các cuộc tấn công phản ánh NTLM.

Lời cuối cùng cảnh báo trước việc sử dụng NTLM: trong các phiên bản Windows trong tương lai, có thể vô hiệu hóa việc sử dụng NTLM trên hệ điều hành. Nếu các ứng dụng có sự phụ thuộc cứng vào NTLM, chúng sẽ đơn giản là không xác thực được khi NTLM bị vô hiệu hóa.

Cách thức hoạt động của Plug-in

Phích cắm Trình xác minh phát hiện các lỗi sau:

  • Gói NTLM được chỉ định trực tiếp trong lệnh gọi tới AcquireCredentialsHandle (hoặc API trình bao bọc cấp cao hơn).

  • Tên đích trong lệnh gọi tới InitializeSecurityContext là NULL.

  • Tên đích trong lệnh gọi tới InitializeSecurityContext không phải là tên miền kiểu SPN, UPN hoặc NetBIOS được định dạng đúng.

Hai trường hợp sau sẽ buộc Negotiate trở lại NTLM hoặc trực tiếp (trường hợp đầu tiên) hoặc gián tiếp (bộ điều khiển miền sẽ trả về lỗi "chính không tìm thấy" trong trường hợp thứ hai khiến Negotiate lùi lại).

Trình cắm cũng ghi lại các cảnh báo khi phát hiện hạ cấp xuống NTLM; ví dụ: khi Bộ điều khiển miền không tìm thấy SPN. Chúng chỉ được ghi lại dưới dạng cảnh báo vì chúng thường là các trường hợp hợp pháp - ví dụ: khi xác thực với hệ thống không được tham gia miền.

NTLM Dừng

5000 - Ứng dụng có gói NTLM được chọn rõ ràng

Mức độ nghiêm trọng - Lỗi

Ứng dụng hoặc hệ thống con chọn NTLM thay vì Thương lượng trong lệnh gọi tới AcquireCredentialsHandle. Mặc dù máy khách và máy chủ có thể xác thực bằng cách sử dụng Kerberos, điều này bị ngăn cản bởi lựa chọn rõ ràng của NTLM.

Cách sửa lỗi này

Cách khắc phục lỗi này là chọn gói Đàm phán thay cho NTLM. Điều này được thực hiện như thế nào sẽ phụ thuộc vào hệ thống con Mạng cụ thể đang được sử dụng bởi máy khách hoặc máy chủ. Một số ví dụ được đưa ra dưới đây. Bạn nên tham khảo tài liệu về thư viện hoặc bộ API cụ thể mà bạn đang sử dụng.

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

Kashif Mushtaq Ottawa, Canada


Không gian tên System.DirectoryServices.AccountManagement là mới trong .NET 3.5
Jeremy Grey

1
Tôi biết điều này đã gần 4 năm tuổi, nhưng nếu bạn đang xác thực một người dùng cục bộ, bạn sẽ cần đảm bảo rằng bạn đặt ContextType thành ContextType.Machine khi bạn tạo một ChiefContext. Nếu không, nó sẽ nghĩ rằng tên máy được cung cấp trong biến Miền thực sự là một máy chủ miền.
SolidRegardless
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.