[c#] 도메인 자격 증명을 확인하는 방법은 무엇입니까?

도메인 컨트롤러에 대해 자격 증명 집합을 확인하고 싶습니다. 예 :

Username: STACKOVERFLOW\joel
Password: splotchy

방법 1. 가장을 사용하여 Active Directory 쿼리

많은 사람들이 Active Directory에 무언가를 쿼리 할 것을 제안합니다. 예외가 발생하면 이 stackoverflow 질문 에서 제안 된대로 자격 증명이 유효하지 않다는 것을 알 수 있습니다 .

그러나이 접근 방식 에는 몇 가지 심각한 단점이 있습니다 .

  1. 도메인 계정을 인증 할뿐만 아니라 암시 적 권한 검사도 수행하고 있습니다. 즉, 가장 토큰을 사용하여 AD에서 속성을 읽습니다. 다른 유효한 계정에 AD에서 읽을 권한이 없으면 어떻게됩니까? 기본적으로 모든 사용자는 읽기 액세스 권한이 있지만 제한된 계정 (및 / 또는 그룹)에 대한 액세스 권한을 비활성화하도록 도메인 정책을 설정할 수 있습니다.

  2. AD에 대한 바인딩에는 심각한 오버 헤드가 있으며 AD 스키마 캐시는 클라이언트에서로드되어야합니다 (DirectoryServices에서 사용하는 ADSI 공급자의 ADSI 캐시). 이것은 네트워크와 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()빈 암호를 지정하면에 대한 호출 이 실패합니다.


도메인 자격 증명 집합을 인증하는 올바른 방법은 무엇입니까?


나는 어떻게 관리 코드에서 호출 할 수 있지만,이 aa는 일반적인 윈도우 문제이다. 고객이 .NET Framework 2.0을 설치했다고 가정 할 수 있습니다.



답변

System.DirectoryServices.AccountManagement를 사용하는 .NET 3.5의 C # .

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

현재 도메인에 대해 유효성을 검사합니다. 다른 옵션은 매개 변수화 된 PrincipalContext 생성자를 확인하십시오.


답변

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


답변

자격 증명을 확인하기 위해 다음 코드를 사용하고 있습니다. 아래 표시된 방법은 자격 증명이 올바른지, 그렇지 않은 경우 암호가 만료되었거나 변경이 필요한지 확인합니다.

나는 오랫동안 이런 것을 찾고 있었다 … 그래서 나는 이것이 누군가를 돕기를 바랍니다!

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


답변

로컬 사용자를 확인하는 방법은 다음과 같습니다.

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

Ian Boyd 편집

더 이상 NTLM을 사용하지 않아야합니다. 너무 오래되고 너무 나빠서 Microsoft의 Application Verifier (일반적인 프로그래밍 실수를 포착하는 데 사용됨)는 NTLM을 사용하여 사용자를 감지하면 경고를 표시합니다.

다음은 누군가 NTLM을 실수로 사용하는 경우 테스트를받는 이유에 대한 Application Verifier 설명서의 한 장입니다.

NTLM 플러그인이 필요한 이유

NTLM은 응용 프로그램 및 운영 체제의 보안을 잠재적으로 손상시키는 결함이있는 오래된 인증 프로토콜입니다. 가장 중요한 단점은 서버 인증이 없다는 것입니다. 이로 인해 공격자가 사용자를 속여 스푸핑 된 서버에 연결하도록 할 수 있습니다. 누락 된 서버 인증의 결과로 NTLM을 사용하는 응용 프로그램은 “반사”공격이라고하는 공격 유형에 취약 할 수도 있습니다. 후자를 통해 공격자는 사용자의 인증 대화를 합법적 인 서버로 가로 채고이를 사용하여 공격자를 사용자 컴퓨터에 인증 할 수 있습니다. NTLM의 취약성과이를 악용하는 방법은 보안 커뮤니티에서 연구 활동을 증가시키는 대상입니다.

Kerberos는 수년 동안 사용할 수 있었지만 여전히 많은 응용 프로그램이 NTLM 만 사용하도록 작성되었습니다. 이것은 애플리케이션의 보안을 불필요하게 감소시킵니다. 그러나 Kerberos는 모든 시나리오에서 NTLM을 대체 할 수 없습니다. 주로 클라이언트가 도메인에 가입되지 않은 시스템 (가장 일반적인 경우는 홈 네트워크)에 대해 인증해야하는 경우입니다. 협상 보안 패키지는 가능하면 Kerberos를 사용하고 다른 옵션이 없을 때만 NTLM으로 되 돌리는 이전 버전과 호환되는 손상을 허용합니다. NTLM 대신 Negotiate를 사용하도록 코드를 전환하면 애플리케이션 호환성이 거의 또는 전혀 도입되지 않으면 서 고객의 보안이 크게 향상됩니다. 협상 자체는 은색 총알이 아닙니다. 공격자가 NTLM으로 강제로 다운 그레이드 할 수 있지만 악용하기가 훨씬 더 어려운 경우가 있습니다. 그러나 즉각적인 개선 사항 중 하나는 Negotiate를 올바르게 사용하도록 작성된 응용 프로그램이 자동으로 NTLM 반사 공격에 영향을받지 않는다는 것입니다.

NTLM 사용에 대한 마지막주의 사항 : 향후 Windows 버전에서는 운영 체제에서 NTLM 사용을 비활성화 할 수 있습니다. 응용 프로그램이 NTLM에 강한 종속성을 가지고있는 경우 NTLM이 비활성화되어 있으면 인증에 실패합니다.

플러그인 작동 방식

Verifier 플러그는 다음 오류를 감지합니다.

  • NTLM 패키지는 AcquireCredentialsHandle (또는 상위 수준 래퍼 API) 호출에 직접 지정됩니다.

  • InitializeSecurityContext에 대한 호출의 대상 이름이 NULL입니다.

  • InitializeSecurityContext 호출의 대상 이름이 올바른 형식의 SPN, UPN 또는 NetBIOS 스타일 도메인 이름이 아닙니다.

후자의 두 경우는 Negotiate가 직접 (첫 번째 경우) 또는 간접적으로 NTLM으로 폴백하도록 강제합니다 (도메인 컨트롤러는 두 번째 경우에 “주체를 찾을 수 없음”오류를 반환하여 협상이 폴 백됨).

플러그인은 또한 NTLM으로의 다운 그레이드를 감지 할 때 경고를 기록합니다. 예를 들어 도메인 컨트롤러에서 SPN을 찾을 수없는 경우입니다. 예를 들어 도메인에 가입되지 않은 시스템에 대해 인증 할 때와 같이 합법적 인 경우가 많기 때문에 경고로만 기록됩니다.

NTLM 중지

5000 – 응용 프로그램에 명시 적으로 NTLM 패키지가 선택됨

심각도 – 오류

응용 프로그램 또는 하위 시스템은 AcquireCredentialsHandle 호출에서 Negotiate 대신 NTLM을 명시 적으로 선택합니다. 클라이언트와 서버가 Kerberos를 사용하여 인증 할 수 있지만 이는 명시적인 NTLM 선택에 의해 방지됩니다.

이 오류를 수정하는 방법

이 오류에 대한 수정 사항은 NTLM 대신 협상 패키지를 선택하는 것입니다. 이 작업을 수행하는 방법은 클라이언트 또는 서버에서 사용하는 특정 네트워크 하위 시스템에 따라 다릅니다. 아래에 몇 가지 예가 나와 있습니다. 사용중인 특정 라이브러리 또는 API 세트에 대한 문서를 참조해야합니다.

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


답변

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 오타와, 캐나다


답변