2015-07-16 2 views
12

Окно имеет пять параметров групповой политики, связанные с безопасностью пароля:Как запрограммировать проверку групповой политики «Пароль должен соответствовать требованиям сложности»?

  • Принудительно истории паролей
  • Максимальный срок действия пароля
  • Минимальный возраст пароля
  • Минимальная длина пароля
  • Пароль должен отвечать требованиям сложности
  • Хранить пароли с использованием обратимого шифрования

enter image description here

Я знаю, как использовать NetUserModalsGet читать most of these items. Но он не поддерживает проверку, если требование сложности пароля включено:

  • Принудительно историю паролей: usrmod0_password_hist_len
  • Максимальный срок действия пароля: usrmod0_max_passwd_age
  • Минимальный срок действия пароля: usrmod0_min_passwd_age
  • Минимальная длина пароля: usrmod0_min_passwd_len
  • Пароль должен отвечать требованиям сложности: ?
  • Хранить пароли, используя обратимое шифрование:

Я также знаю, что RSOP WMI (в "Результирующая политика") непригодна, as it only works on a domain. И я, конечно, не буду crawling through an undocumented binary blob (то есть я хочу, чтобы поддерживаемый способ).

Примечание: Я не забочусь о «Хранить пароли, используя обратимое шифрование» параметр политики группы.

Bonus

Вы можете также использовать NetUserModalsGet API для получения блокировки учетной записи Политика настройки:

  • Длительность блокировки счета: usrmod3_lockout_duration
  • порог блокировки учетной записи: usrmod3_lockout_threshold
  • Сброс счетчика блокировки учетной записи после: usrmod3_lockout_observation_window

Таким образом, округляя все связанные с паролем параметры групповой политики; за исключением «должны соответствовать требованиям сложности».

Для полноты предположим, что машина, не связанная с доменом (то есть сервер AD для запроса, RSOP для запроса и т. Д.).

+0

Почему другое решение задано в связанной ст. ackoverflow post, сброс в INI через secedit и разбор INI, не работает для вас? Он не требует домена и выглядит намного лучше, чем двоичный анализ blob. – ssnobody

+0

@ssnobody Даже при проблемах с необходимостью писать файл в каком-то месте и как [ждать, когда secedit будет запущен, или узнать, когда он не работает] (http://blogs.msdn.com/b/oldnewthing /archive/2005/01/19/356048.aspx), и как это требует административных привилегий, когда пользователь не будет иметь его или как его можно назвать много, он все равно остался бы ужасным взломом идеи о том, что i никогда бы не признался в создании или никогда не позволял ему видеть свет дня. Я ищу * правильный * способ сделать это. –

ответ

18

Это доступно с использованием API SAM (Security Account Manager).

Этот API (обслуживается Samlib.dll) не непосредственно не документирована (без заголовка, нет SDK), но «протокол», чтобы использовать документировано здесь: [MS-SAMR]: Security Account Manager (SAM) Remote Protocol (Client-to-Server), вы «просто» должны удалить r в описанной SamrXXXX методов.

Те, о которых идет речь здесь SamQueryInformationDomain (и связанные с ними SamSetInformationDomain), который поможет вам 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, 

PasswordProperties элемент может содержать DOMAIN_PASSWORD_COMPLEX флаг:

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

Я представил некоторые C#, чтобы проверить это.

Сначала один отвалы политики для всех доменов, обслуживаемого SAM сервер текущей машины:

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

Второго один читает и обновляет политику для домена текущей машины:

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

Это 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

Действительно ли этот код работает для вас?Это была хорошая отправная точка, но для меня она врезалась в строку 'Check (SamConnect (новый UNICODE_STRING (имя), out _handle, access, IntPtr.Zero)).'. По сравнению с, например, https://github.com/MichaelGrafnetter/DSInternals, я определил, что параметр name должен был передаваться по ссылке ref. –

+0

Кроме того, я обнаружил, что структура, возвращаемая 'SamEnumerateDomainsInSamServer', не выглядела правильной, и она врезалась в строку' Marshal.PtrToStructure'. Кроме того, параметр 'count' для' SamEnumerateDomainsInSamServer' игнорируется, и вы, вероятно, должны повторять перечисленные домены. Мое редактирование внесло эти изменения и сработало для меня. Что случилось с тобой? –

+1

Да, это работает при компиляции с x64 и не выполняется для x86, что является признаком проблемы в объявлении p/invoke. Я обновил свой ответ и протестировал его для обеих архитектур. count игнорируется, потому что мы просим его, поэтому мы не получаем больше одного, но в любом случае, когда вы получаете бесплатную информацию, он освобождает все это, включая UNICODE_STRING внутри. Действительно, некоторые утечки с буферами UNICODE_STRING также исправлены. Помните, что никто из них официально не документирован. PDF описывает протокол, а не API. На самом деле то, что возвращает SamEnumerateDomainsInSamServer, не соответствует 100% тому, что находится в PDF. –

Смежные вопросы