Bagaimana cara memvalidasi kredensial domain?

87

Saya ingin memvalidasi sekumpulan kredensial terhadap pengontrol domain. misalnya:

Username: STACKOVERFLOW\joel
Password: splotchy

Metode 1. Permintaan Direktori Aktif dengan Peniruan Identitas

Banyak orang menyarankan untuk menanyakan sesuatu di Active Directory. Jika pengecualian dilempar, Anda tahu bahwa kredensial tersebut tidak valid - seperti yang disarankan dalam pertanyaan stackoverflow ini .

Namun ada beberapa kelemahan serius dari pendekatan ini :

  1. Anda tidak hanya mengautentikasi akun domain, tetapi Anda juga melakukan pemeriksaan otorisasi implisit. Artinya, Anda membaca properti dari AD menggunakan token peniruan. Bagaimana jika akun yang valid tidak memiliki hak untuk membaca dari AD? Secara default, semua pengguna memiliki akses baca, tetapi kebijakan domain dapat disetel untuk menonaktifkan izin akses untuk akun (dan atau grup) yang dibatasi.

  2. Mengikat terhadap AD memiliki overhead yang serius, cache skema AD harus dimuat di klien (cache ADSI di penyedia ADSI yang digunakan oleh DirectoryServices). Ini adalah jaringan, dan server AD, memakan sumber daya - dan terlalu mahal untuk operasi sederhana seperti mengautentikasi akun pengguna.

  3. Anda mengandalkan kegagalan pengecualian untuk kasus yang tidak luar biasa, dan menganggap itu berarti nama pengguna dan kata sandi tidak valid. Masalah lain (misalnya kegagalan jaringan, kegagalan konektivitas AD, kesalahan alokasi memori, dll) kemudian disalahartikan sebagai kegagalan otentikasi.

Metode 2. LogonUser Win32 API

Orang lain menyarankan menggunakan LogonUser()fungsi API. Kedengarannya bagus, tapi sayangnya pengguna yang menelepon terkadang membutuhkan izin yang biasanya hanya diberikan ke sistem operasi itu sendiri:

Proses memanggil LogonUser membutuhkan hak istimewa SE_TCB_NAME. Jika proses panggilan tidak memiliki hak istimewa ini, LogonUser gagal dan GetLastError mengembalikan ERROR_PRIVILEGE_NOT_HELD.

Dalam beberapa kasus, proses yang memanggil LogonUser juga harus mengaktifkan hak istimewa SE_CHANGE_NOTIFY_NAME; jika tidak, LogonUser gagal dan GetLastError mengembalikan ERROR_ACCESS_DENIED. Hak istimewa ini tidak diperlukan untuk akun sistem lokal atau akun yang menjadi anggota grup administrator. Secara default, SE_CHANGE_NOTIFY_NAME diaktifkan untuk semua pengguna, tetapi beberapa administrator dapat menonaktifkannya untuk semua orang.

Membagikan hak istimewa " Bertindak sebagai bagian dari sistem operasi " bukanlah sesuatu yang ingin Anda lakukan mau tak mau - seperti yang ditunjukkan Microsoft dalam artikel basis pengetahuan :

... proses yang memanggil LogonUser harus memiliki hak istimewa SE_TCB_NAME (di Pengelola Pengguna, ini adalah hak " Bertindak sebagai bagian dari Sistem Operasi "). Hak istimewa SE_TCB_NAME sangat kuat dan tidak boleh diberikan kepada sembarang pengguna hanya agar mereka dapat menjalankan aplikasi yang perlu memvalidasi kredensial.

Selain itu, panggilan ke LogonUser()akan gagal jika kata sandi kosong ditentukan.


Apa cara yang tepat untuk mengautentikasi sekumpulan kredensial domain?


Saya kebetulan menelepon dari kode yang dikelola, tetapi ini adalah pertanyaan umum Windows. Dapat diasumsikan bahwa pelanggan telah menginstal .NETFramework 2.0.

Ian Boyd
sumber
1
Pembaca harus memperhatikan bahwa mulai Windows XP, LogonUser tidak lagi memerlukan SE_TCB_NAME (kecuali Anda masuk ke akun Passport).
Harry Johnston

Jawaban:

130

C # di .NET 3.5 menggunakan System.DirectoryServices.AccountManagement .

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

Ini akan memvalidasi terhadap domain saat ini. Lihat konstruktor PrincipalContext berparameter untuk opsi lain.

tvanfosson.dll
sumber
@tvanfosson: tidakkah DirectoryServices menggunakan AD?
Mitch Wheat
1
Iya. Tetapi dokumentasi menunjukkan bahwa ini adalah cara cepat untuk memvalidasi kredensial. Ini juga berbeda dari metode penjilidan yang disebutkan dalam pertanyaan karena Anda tidak membaca properti apa pun dari objek. Perhatikan bahwa metode ini ada pada konteks, bukan objek direktori.
tvanfosson
Koreksi: System.DirectoryServices.AccountManagement membutuhkan .NET 3.5. ( msdn.microsoft.com/en-us/library/… )
Ian Boyd
19
Ini juga berfungsi dengan pengguna lokal jika Anda menggunakannya new PrincipalContext(ContextType.Machine).
VansFannel
Apakah ada yang tahu jika ini bekerja pada kredensial yang di-cache, atau apakah itu memerlukan koneksi ke DC? Saya perlu mengetahui hal ini untuk beberapa implementasi yang sedang saya kerjakan dan saat ini saya tidak menggunakan domain apa pun untuk diuji
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);
        }
    }
}
kantanomo
sumber
7
apakah ini mengandung perbedaan yang signifikan dengan jawaban @ tvanfosson 3 tahun sebelumnya?
gbjbaanb
5
@gbjbaanb Ya, karena berisi Domainparameter saat membuat PrincipalContext, sesuatu yang saya ingin ketahui dan temukan dalam jawaban ini.
Rudi Visser
1
@RudiVisser tvanfosson menyarankan Anda "Periksa konstruktor PrincipalContext berparameter untuk opsi lain" - selalu baca dokumennya, jangan hanya mengambil kata-kata di internet! :)
gbjbaanb
4
@gbjbaanb Ya tentu saja, tapi memberikan contoh kerja daripada link dan saran untuk membaca di tempat lain adalah mantra StackOverflow, bahwa ini mengapa kita menerima beberapa kiriman dari jawaban: D Cukup mengatakan bahwa ini tidak memberikan lebih.
Rudi Visser
Adakah yang tahu bagaimana kita bisa melakukan sesuatu yang serupa dalam aplikasi UWP? (dengan AD biasa dan bukan dengan Azure AD). Saya telah mengajukan pertanyaan di sini: stackoverflow.com/questions/42821447
slayernoah
7

Saya menggunakan kode berikut untuk memvalidasi kredensial. Metode yang ditunjukkan di bawah ini akan mengkonfirmasi apakah kredensial benar dan jika tidak apakah kata sandi kedaluwarsa atau perlu diubah.

Saya sudah lama mencari sesuatu seperti ini ... Jadi saya harap ini membantu seseorang!

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;
        }
    }
Kevinrr3
sumber
ini adalah "metode 2" yang dijelaskan dalam pertanyaan ... jadi ... tidak benar-benar menjawab pertanyaan
Robert Levy
1

Berikut cara menentukan pengguna lokal:

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

Diedit oleh Ian Boyd

Anda seharusnya tidak menggunakan NTLM lagi. Ini sangat tua, dan sangat buruk, sehingga Pemverifikasi Aplikasi Microsoft (yang digunakan untuk menangkap kesalahan pemrograman umum) akan memberikan peringatan jika mendeteksi Anda menggunakan NTLM.

Berikut adalah bab dari dokumentasi Pemverifikasi Aplikasi tentang mengapa mereka melakukan pengujian jika seseorang salah menggunakan NTLM:

Mengapa Plug-in NTLM Dibutuhkan

NTLM adalah protokol otentikasi yang sudah ketinggalan zaman dengan kekurangan yang berpotensi membahayakan keamanan aplikasi dan sistem operasi. Kekurangan terpenting adalah kurangnya otentikasi server, yang memungkinkan penyerang mengelabui pengguna agar terhubung ke server palsu. Sebagai akibat dari otentikasi server yang hilang, aplikasi yang menggunakan NTLM juga rentan terhadap jenis serangan yang dikenal sebagai serangan "refleksi". Yang terakhir ini memungkinkan penyerang untuk membajak percakapan otentikasi pengguna ke server yang sah dan menggunakannya untuk mengotentikasi penyerang ke komputer pengguna. Kerentanan NTLM dan cara memanfaatkannya menjadi sasaran peningkatan aktivitas penelitian di komunitas keamanan.

Meskipun Kerberos telah tersedia selama bertahun-tahun, banyak aplikasi masih ditulis untuk menggunakan NTLM saja. Ini tidak perlu mengurangi keamanan aplikasi. Namun Kerberos tidak dapat menggantikan NTLM di semua skenario - terutama di mana klien perlu mengotentikasi ke sistem yang tidak bergabung dengan domain (jaringan rumah mungkin yang paling umum). Paket keamanan Negosiasikan memungkinkan kompromi yang kompatibel dengan mundur yang menggunakan Kerberos bila memungkinkan dan hanya kembali ke NTLM ketika tidak ada pilihan lain. Mengalihkan kode untuk menggunakan Negotiate daripada NTLM akan secara signifikan meningkatkan keamanan bagi pelanggan kami sambil memperkenalkan sedikit atau tidak ada kompatibilitas aplikasi. Bernegosiasi dengan sendirinya bukanlah peluru perak - ada kasus di mana penyerang dapat memaksa downgrade ke NTLM tetapi ini jauh lebih sulit untuk dieksploitasi. Namun, satu perbaikan langsung adalah bahwa aplikasi yang ditulis untuk menggunakan Negosiasi dengan benar secara otomatis kebal terhadap serangan refleksi NTLM.

Dengan kata terakhir peringatan terhadap penggunaan NTLM: di versi Windows yang akan datang dimungkinkan untuk menonaktifkan penggunaan NTLM pada sistem operasi. Jika aplikasi memiliki ketergantungan keras pada NTLM, mereka akan gagal untuk mengotentikasi ketika NTLM dinonaktifkan.

Cara Kerja Plug-in

Steker Verifier mendeteksi kesalahan berikut:

  • Paket NTLM secara langsung ditentukan dalam panggilan ke AcquireCredentialsHandle (atau API pembungkus tingkat yang lebih tinggi).

  • Nama target dalam panggilan ke InitializeSecurityContext adalah NULL.

  • Nama target dalam panggilan ke InitializeSecurityContext bukanlah nama domain gaya SPN, UPN atau NetBIOS yang dibentuk dengan benar.

Dua kasus terakhir akan memaksa Negotiate untuk kembali ke NTLM baik secara langsung (kasus pertama) atau tidak langsung (kontroler domain akan mengembalikan kesalahan "principal not found" dalam kasus kedua yang menyebabkan Negotiate mundur).

Plug-in juga mencatat peringatan ketika mendeteksi penurunan ke NTLM; misalnya, ketika SPN tidak ditemukan oleh Pengontrol Domain. Ini hanya dicatat sebagai peringatan karena sering kali merupakan kasus yang sah - misalnya, saat mengautentikasi ke sistem yang tidak bergabung dengan domain.

NTLM Berhenti

5000 - Aplikasi Memiliki Paket NTLM yang Dipilih Secara Eksplisit

Keparahan - Kesalahan

Aplikasi atau subsistem secara eksplisit memilih NTLM alih-alih Negosiasi dalam panggilan ke AcquireCredentialsHandle. Meskipun klien dan server dapat melakukan otentikasi menggunakan Kerberos, hal ini dicegah oleh pemilihan NTLM secara eksplisit.

Cara Memperbaiki Kesalahan ini

Perbaikan untuk kesalahan ini adalah dengan memilih paket Negosiasikan sebagai pengganti NTLM. Bagaimana ini dilakukan akan tergantung pada subsistem Jaringan tertentu yang digunakan oleh klien atau server. Beberapa contoh diberikan di bawah ini. Anda harus membaca dokumentasi tentang pustaka atau set API tertentu yang Anda gunakan.

APIs(parameter) Used by Application    Incorrect Value  Correct Value  
=====================================  ===============  ========================
AcquireCredentialsHandle (pszPackage)  “NTLM”           NEGOSSP_NAME “Negotiate”
Alan Nicholas
sumber
-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, Kanada

Markus Safar
sumber
Namespace System.DirectoryServices.AccountManagement baru di .NET 3.5
Jeremy Grey
1
Saya tahu ini hampir 4 tahun, tetapi jika Anda memvalidasi pengguna lokal, Anda perlu memastikan bahwa Anda menyetel ContextType ke ContextType.Machine saat Anda membuat PrincipalContext. Jika tidak, ia akan mengira nama mesin yang diberikan dalam variabel Domain sebenarnya adalah server domain.
SolidRegardless