2015-07-16 12 views
12

finestra ha cinque impostazioni dei criteri di gruppo relative alla sicurezza delle password:Come verificare in modo programmatico l'impostazione dei criteri di gruppo "La password deve soddisfare i requisiti di complessità"?

  • Imponi cronologia delle password
  • Validità massima password
  • età minima password
  • Lunghezza minima password
  • password devono essere conformi ai requisiti di complessità
  • Archivia password utilizzando la crittografia reversibile

enter image description here

so come usare NetUserModalsGet a leggere most of these items. Ma non supporta controllando se requisito la complessità delle password è attivata:

  • Imponi cronologia delle password: usrmod0_password_hist_len
  • Validità massima password: usrmod0_max_passwd_age
  • età minima password: usrmod0_min_passwd_age
  • Lunghezza minima password: usrmod0_min_passwd_len
  • password devono essere conformi ai requisiti di complessità: ?
  • Archivia password mediante crittografia reversibile:

so anche che RSOP WMI ("Gruppo di criteri risultante") non è adatto, as it only works on a domain. E certamente non sto andando a crawling through an undocumented binary blob (cioè voglio il supportato modo).

Nota: Non mi interessa circa le "Archivia password mediante crittografia reversibile" impostazione dei criteri di gruppo.

Bonus

È anche possibile utilizzare il NetUserModalsGet API per recuperare i blocco degli account Policy impostazioni:

  • account durata del blocco: usrmod3_lockout_duration
  • account soglia di blocco: usrmod3_lockout_threshold
  • Azzerare il contatore blocco account dopo: usrmod3_lockout_observation_window

Così arrotondando tutte le opzioni di politica del gruppo relative alle password; ad eccezione di "deve soddisfare i requisiti di complessità".

Per completezza, presupporre una macchina non associata al dominio (ovvero nessun server AD da interrogare, nessun RSOP da interrogare, ecc.).

+0

Perché l'altra soluzione indicata nella st collegata post di ackoverflow, scaricando un INI via secedit e analizzando l'INI, non funziona per te? Non richiede un dominio e sembra molto più bello dell'analisi blob binario. – ssnobody

+0

@ssnobody Anche con i problemi di dover scrivere un file in qualche posto, e come [non è possibile aspettare il lancio di secedit, o sapere quando è stato risolto] (http://blogs.msdn.com/b/oldnewthing /archive/2005/01/19/356048.aspx), e come richiede i privilegi di amministratore quando l'utente non ce l'ha, o come potrebbe essere chiamato molto, rimarrebbe comunque un orribile hack di un'idea che non ammetterebbe mai di creare o lasciare mai vedere la luce del giorno. Sto cercando il modo * corretto * per farlo. –

risposta

18

Questo è accessibile utilizzando le API SAM (Security Account Manager).

Questa API (servito da Samlib.dll) non è documentato direttamente (nessuna intestazione, nessun SDK), ma il "protocollo" per usarlo è documentato qui: [MS-SAMR]: Security Account Manager (SAM) Remote Protocol (Client-to-Server), è "solo" è necessario rimuovere il r in descritto Metodi SamrXXXX.

Quelli in questione qui sono SamQueryInformationDomain (e SamSetInformationDomain associata) che vi porterà una struttura DOMAIN_PASSWORD_INFORMATION

typedef struct _DOMAIN_PASSWORD_INFORMATION { 
    unsigned short MinPasswordLength; 
    unsigned short PasswordHistoryLength; 
    unsigned long PasswordProperties; 
    OLD_LARGE_INTEGER MaxPasswordAge; 
    OLD_LARGE_INTEGER MinPasswordAge; 
} DOMAIN_PASSWORD_INFORMATION, 

Il membro PasswordProperties può contenere DOMAIN_PASSWORD_COMPLEX bandiera:

DOMAIN_PASSWORD_COMPLEX 
0x00000001 
The server enforces password complexity policy. See section 3.1.1.7.2 for details of the password policy. 

Ho fornito alcuni Campioni C# per verificarlo.

Primo uno discariche politica per tutti i domini serviti dal server di SAM della macchina corrente:

 using (SamServer server = new SamServer(null, SamServer.SERVER_ACCESS_MASK.SAM_SERVER_ENUMERATE_DOMAINS | SamServer.SERVER_ACCESS_MASK.SAM_SERVER_LOOKUP_DOMAIN)) 
     { 
      foreach (string domain in server.EnumerateDomains()) 
      { 
       Console.WriteLine("domain: " + domain); 

       var sid = server.GetDomainSid(domain); 
       Console.WriteLine(" sid: " + sid); 

       var pi = server.GetDomainPasswordInformation(sid); 
       Console.WriteLine(" MaxPasswordAge: " + pi.MaxPasswordAge); 
       Console.WriteLine(" MinPasswordAge: " + pi.MinPasswordAge); 
       Console.WriteLine(" MinPasswordLength: " + pi.MinPasswordLength); 
       Console.WriteLine(" PasswordHistoryLength: " + pi.PasswordHistoryLength); 
       Console.WriteLine(" PasswordProperties: " + pi.PasswordProperties); 
      } 
     } 

In secondo luogo si legge e aggiorna la politica per il dominio della macchina corrente:

 using (SamServer server = new SamServer(null, SamServer.SERVER_ACCESS_MASK.SAM_SERVER_ALL_ACCESS)) 
     { 
      var sid = server.GetDomainSid(Environment.MachineName); 
      var pi = server.GetDomainPasswordInformation(sid); 

      // remove password complexity 
      pi.PasswordProperties &= ~SamServer.PASSWORD_PROPERTIES.DOMAIN_PASSWORD_COMPLEX; 
      server.SetDomainPasswordInformation(sid, pi); 
     } 

Questa è la Utilità SamServer:

public sealed class SamServer : IDisposable 
{ 
    private IntPtr _handle; 

    public SamServer(string name, SERVER_ACCESS_MASK access) 
    { 
     Name = name; 
     Check(SamConnect(new UNICODE_STRING(name), out _handle, access, IntPtr.Zero)); 
    } 

    public string Name { get; } 

    public void Dispose() 
    { 
     if (_handle != IntPtr.Zero) 
     { 
      SamCloseHandle(_handle); 
      _handle = IntPtr.Zero; 
     } 
    } 

    public void SetDomainPasswordInformation(SecurityIdentifier domainSid, DOMAIN_PASSWORD_INFORMATION passwordInformation) 
    { 
     if (domainSid == null) 
      throw new ArgumentNullException(nameof(domainSid)); 

     var sid = new byte[domainSid.BinaryLength]; 
     domainSid.GetBinaryForm(sid, 0); 

     Check(SamOpenDomain(_handle, DOMAIN_ACCESS_MASK.DOMAIN_WRITE_PASSWORD_PARAMS, sid, out IntPtr domain)); 
     IntPtr info = Marshal.AllocHGlobal(Marshal.SizeOf(passwordInformation)); 
     Marshal.StructureToPtr(passwordInformation, info, false); 
     try 
     { 
      Check(SamSetInformationDomain(domain, DOMAIN_INFORMATION_CLASS.DomainPasswordInformation, info)); 
     } 
     finally 
     { 
      Marshal.FreeHGlobal(info); 
      SamCloseHandle(domain); 
     } 
    } 

    public DOMAIN_PASSWORD_INFORMATION GetDomainPasswordInformation(SecurityIdentifier domainSid) 
    { 
     if (domainSid == null) 
      throw new ArgumentNullException(nameof(domainSid)); 

     var sid = new byte[domainSid.BinaryLength]; 
     domainSid.GetBinaryForm(sid, 0); 

     Check(SamOpenDomain(_handle, DOMAIN_ACCESS_MASK.DOMAIN_READ_PASSWORD_PARAMETERS, sid, out IntPtr domain)); 
     var info = IntPtr.Zero; 
     try 
     { 
      Check(SamQueryInformationDomain(domain, DOMAIN_INFORMATION_CLASS.DomainPasswordInformation, out info)); 
      return (DOMAIN_PASSWORD_INFORMATION)Marshal.PtrToStructure(info, typeof(DOMAIN_PASSWORD_INFORMATION)); 
     } 
     finally 
     { 
      SamFreeMemory(info); 
      SamCloseHandle(domain); 
     } 
    } 

    public SecurityIdentifier GetDomainSid(string domain) 
    { 
     if (domain == null) 
      throw new ArgumentNullException(nameof(domain)); 

     Check(SamLookupDomainInSamServer(_handle, new UNICODE_STRING(domain), out IntPtr sid)); 
     return new SecurityIdentifier(sid); 
    } 

    public IEnumerable<string> EnumerateDomains() 
    { 
     int cookie = 0; 
     while (true) 
     { 
      var status = SamEnumerateDomainsInSamServer(_handle, ref cookie, out IntPtr info, 1, out int count); 
      if (status != NTSTATUS.STATUS_SUCCESS && status != NTSTATUS.STATUS_MORE_ENTRIES) 
       Check(status); 

      if (count == 0) 
       break; 

      var us = (UNICODE_STRING)Marshal.PtrToStructure(info + IntPtr.Size, typeof(UNICODE_STRING)); 
      SamFreeMemory(info); 
      yield return us.ToString(); 
      us.Buffer = IntPtr.Zero; // we don't own this one 
     } 
    } 

    private enum DOMAIN_INFORMATION_CLASS 
    { 
     DomainPasswordInformation = 1, 
    } 

    [Flags] 
    public enum PASSWORD_PROPERTIES 
    { 
     DOMAIN_PASSWORD_COMPLEX = 0x00000001, 
     DOMAIN_PASSWORD_NO_ANON_CHANGE = 0x00000002, 
     DOMAIN_PASSWORD_NO_CLEAR_CHANGE = 0x00000004, 
     DOMAIN_LOCKOUT_ADMINS = 0x00000008, 
     DOMAIN_PASSWORD_STORE_CLEARTEXT = 0x00000010, 
     DOMAIN_REFUSE_PASSWORD_CHANGE = 0x00000020, 
    } 

    [Flags] 
    private enum DOMAIN_ACCESS_MASK 
    { 
     DOMAIN_READ_PASSWORD_PARAMETERS = 0x00000001, 
     DOMAIN_WRITE_PASSWORD_PARAMS = 0x00000002, 
     DOMAIN_READ_OTHER_PARAMETERS = 0x00000004, 
     DOMAIN_WRITE_OTHER_PARAMETERS = 0x00000008, 
     DOMAIN_CREATE_USER = 0x00000010, 
     DOMAIN_CREATE_GROUP = 0x00000020, 
     DOMAIN_CREATE_ALIAS = 0x00000040, 
     DOMAIN_GET_ALIAS_MEMBERSHIP = 0x00000080, 
     DOMAIN_LIST_ACCOUNTS = 0x00000100, 
     DOMAIN_LOOKUP = 0x00000200, 
     DOMAIN_ADMINISTER_SERVER = 0x00000400, 
     DOMAIN_ALL_ACCESS = 0x000F07FF, 
     DOMAIN_READ = 0x00020084, 
     DOMAIN_WRITE = 0x0002047A, 
     DOMAIN_EXECUTE = 0x00020301 
    } 

    [Flags] 
    public enum SERVER_ACCESS_MASK 
    { 
     SAM_SERVER_CONNECT = 0x00000001, 
     SAM_SERVER_SHUTDOWN = 0x00000002, 
     SAM_SERVER_INITIALIZE = 0x00000004, 
     SAM_SERVER_CREATE_DOMAIN = 0x00000008, 
     SAM_SERVER_ENUMERATE_DOMAINS = 0x00000010, 
     SAM_SERVER_LOOKUP_DOMAIN = 0x00000020, 
     SAM_SERVER_ALL_ACCESS = 0x000F003F, 
     SAM_SERVER_READ = 0x00020010, 
     SAM_SERVER_WRITE = 0x0002000E, 
     SAM_SERVER_EXECUTE = 0x00020021 
    } 

    [StructLayout(LayoutKind.Sequential)] 
    public struct DOMAIN_PASSWORD_INFORMATION 
    { 
     public short MinPasswordLength; 
     public short PasswordHistoryLength; 
     public PASSWORD_PROPERTIES PasswordProperties; 
     private long _maxPasswordAge; 
     private long _minPasswordAge; 

     public TimeSpan MaxPasswordAge 
     { 
      get 
      { 
       return -new TimeSpan(_maxPasswordAge); 
      } 
      set 
      { 
       _maxPasswordAge = value.Ticks; 
      } 
     } 

     public TimeSpan MinPasswordAge 
     { 
      get 
      { 
       return -new TimeSpan(_minPasswordAge); 
      } 
      set 
      { 
       _minPasswordAge = value.Ticks; 
      } 
     } 
    } 

    [StructLayout(LayoutKind.Sequential)] 
    private class UNICODE_STRING : IDisposable 
    { 
     public ushort Length; 
     public ushort MaximumLength; 
     public IntPtr Buffer; 

     public UNICODE_STRING() 
      : this(null) 
     { 
     } 

     public UNICODE_STRING(string s) 
     { 
      if (s != null) 
      { 
       Length = (ushort)(s.Length * 2); 
       MaximumLength = (ushort)(Length + 2); 
       Buffer = Marshal.StringToHGlobalUni(s); 
      } 
     } 

     public override string ToString() => Buffer != IntPtr.Zero ? Marshal.PtrToStringUni(Buffer) : null; 

     protected virtual void Dispose(bool disposing) 
     { 
      if (Buffer != IntPtr.Zero) 
      { 
       Marshal.FreeHGlobal(Buffer); 
       Buffer = IntPtr.Zero; 
      } 
     } 

     ~UNICODE_STRING() => Dispose(false); 

     public void Dispose() 
     { 
      Dispose(true); 
      GC.SuppressFinalize(this); 
     } 
    } 

    private static void Check(NTSTATUS err) 
    { 
     if (err == NTSTATUS.STATUS_SUCCESS) 
      return; 

     throw new Win32Exception("Error " + err + " (0x" + ((int)err).ToString("X8") + ")"); 
    } 

    private enum NTSTATUS 
    { 
     STATUS_SUCCESS = 0x0, 
     STATUS_MORE_ENTRIES = 0x105, 
     STATUS_INVALID_HANDLE = unchecked((int)0xC0000008), 
     STATUS_INVALID_PARAMETER = unchecked((int)0xC000000D), 
     STATUS_ACCESS_DENIED = unchecked((int)0xC0000022), 
     STATUS_OBJECT_TYPE_MISMATCH = unchecked((int)0xC0000024), 
     STATUS_NO_SUCH_DOMAIN = unchecked((int)0xC00000DF), 
    } 

    [DllImport("samlib.dll", CharSet = CharSet.Unicode)] 
    private static extern NTSTATUS SamConnect(UNICODE_STRING ServerName, out IntPtr ServerHandle, SERVER_ACCESS_MASK DesiredAccess, IntPtr ObjectAttributes); 

    [DllImport("samlib.dll", CharSet = CharSet.Unicode)] 
    private static extern NTSTATUS SamCloseHandle(IntPtr ServerHandle); 

    [DllImport("samlib.dll", CharSet = CharSet.Unicode)] 
    private static extern NTSTATUS SamFreeMemory(IntPtr Handle); 

    [DllImport("samlib.dll", CharSet = CharSet.Unicode)] 
    private static extern NTSTATUS SamOpenDomain(IntPtr ServerHandle, DOMAIN_ACCESS_MASK DesiredAccess, byte[] DomainId, out IntPtr DomainHandle); 

    [DllImport("samlib.dll", CharSet = CharSet.Unicode)] 
    private static extern NTSTATUS SamLookupDomainInSamServer(IntPtr ServerHandle, UNICODE_STRING name, out IntPtr DomainId); 

    [DllImport("samlib.dll", CharSet = CharSet.Unicode)] 
    private static extern NTSTATUS SamQueryInformationDomain(IntPtr DomainHandle, DOMAIN_INFORMATION_CLASS DomainInformationClass, out IntPtr Buffer); 

    [DllImport("samlib.dll", CharSet = CharSet.Unicode)] 
    private static extern NTSTATUS SamSetInformationDomain(IntPtr DomainHandle, DOMAIN_INFORMATION_CLASS DomainInformationClass, IntPtr Buffer); 

    [DllImport("samlib.dll", CharSet = CharSet.Unicode)] 
    private static extern NTSTATUS SamEnumerateDomainsInSamServer(IntPtr ServerHandle, ref int EnumerationContext, out IntPtr EnumerationBuffer, int PreferedMaximumLength, out int CountReturned); 
} 
+0

Questo codice funziona davvero per te?È stato un buon punto di partenza, ma per me è andato in crash sulla riga 'Controlla (SamConnect (new UNICODE_STRING (nome), out _handle, access, IntPtr.Zero));'. Per fare un confronto con, ad esempio, https://github.com/MichaelGrafnetter/DSInternals, ho determinato che il parametro name doveva essere passato per ref. –

+0

Inoltre, ho trovato che la struttura restituita da 'SamEnumerateDomainsInSamServer' non sembrava essere corretta e si stava bloccando sulla linea' Marshal.PtrToStructure'. Inoltre, il parametro 'count' per' SamEnumerateDomainsInSamServer' viene ignorato, e probabilmente dovresti iterare i domini enumerati. La mia modifica ha apportato queste modifiche e ha funzionato per me. Cosa ti è successo? –

+1

Sì, funziona quando è compilato con x64 e non riesce per x86, che è un segno di un problema nella dichiarazione p/invoke. Ho aggiornato la mia risposta e l'ho testata per entrambe le architetture. il conteggio viene ignorato perché ne chiediamo uno, quindi non ne riceviamo più di uno, ma in ogni caso quando le informazioni sono libere, si libera tutto, incluso UNICODE_STRING all'interno. In effetti, alcune falle con i buffer UNICODE_STRING sono state riparate ora. Ricorda che nulla di tutto ciò è ufficialmente documentato. Il PDF descrive un protocollo, non un'API. Infatti, ciò che SamEnumerateDomainsInSamServer restituisce dos non corrisponde al 100% cosa c'è nel PDF. –

Problemi correlati