Win32UserCollection.cs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. /* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  2. *
  3. * You can redistribute this program and/or modify it under the terms of
  4. * the GNU Lesser Public License as published by the Free Software Foundation,
  5. * either version 3 of the License, or (at your option) any later version.
  6. */
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Net;
  10. using System.Text;
  11. using Utilities;
  12. using SMBLibrary.Authentication;
  13. using SMBLibrary.Authentication.Win32;
  14. using Microsoft.Win32;
  15. namespace SMBLibrary.Server.Win32
  16. {
  17. public class Win32UserCollection : UserCollection, INTLMAuthenticationProvider
  18. {
  19. private SecHandle m_serverContext;
  20. private byte[] m_serverChallenge = new byte[8];
  21. public Win32UserCollection()
  22. {
  23. List<string> users = NetworkAPI.EnumerateNetworkUsers();
  24. foreach (string user in users)
  25. {
  26. this.Add(new User(user, String.Empty));
  27. }
  28. }
  29. public byte[] GenerateServerChallenge()
  30. {
  31. NegotiateMessage negotiateMessage = new NegotiateMessage();
  32. negotiateMessage.NegotiateFlags = NegotiateFlags.NegotiateUnicode | NegotiateFlags.NegotiateOEM | NegotiateFlags.RequestTarget | NegotiateFlags.NegotiateSign | NegotiateFlags.NegotiateSeal | NegotiateFlags.NegotiateLanManagerKey | NegotiateFlags.NegotiateNTLMKey | NegotiateFlags.NegotiateAlwaysSign | NegotiateFlags.NegotiateVersion | NegotiateFlags.Negotiate128 | NegotiateFlags.Negotiate56;
  33. negotiateMessage.Version = Authentication.Version.Server2003;
  34. byte[] negotiateMessageBytes = negotiateMessage.GetBytes();
  35. byte[] challengeMessageBytes = SSPIHelper.GetType2Message(negotiateMessageBytes, out m_serverContext);
  36. ChallengeMessage challengeMessage = new ChallengeMessage(challengeMessageBytes);
  37. m_serverChallenge = challengeMessage.ServerChallenge;
  38. return m_serverChallenge;
  39. }
  40. public byte[] GetChallengeMessageBytes(byte[] negotiateMessageBytes)
  41. {
  42. byte[] challengeMessageBytes = SSPIHelper.GetType2Message(negotiateMessageBytes, out m_serverContext);
  43. ChallengeMessage message = new ChallengeMessage(challengeMessageBytes);
  44. m_serverChallenge = message.ServerChallenge;
  45. return challengeMessageBytes;
  46. }
  47. /// <summary>
  48. /// Note: The 'limitblankpassworduse' (Under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa)
  49. /// will cause AcceptSecurityContext to return SEC_E_LOGON_DENIED when the correct password is blank.
  50. /// </summary>
  51. public User Authenticate(string accountNameToAuth, byte[] lmResponse, byte[] ntlmResponse)
  52. {
  53. if (accountNameToAuth == String.Empty ||
  54. (String.Equals(accountNameToAuth, "Guest", StringComparison.InvariantCultureIgnoreCase) && IsPasswordEmpty(lmResponse, ntlmResponse) && this.EnableGuestLogin))
  55. {
  56. int guestIndex = IndexOf("Guest");
  57. if (guestIndex >= 0)
  58. {
  59. return this[guestIndex];
  60. }
  61. return null;
  62. }
  63. int index = IndexOf(accountNameToAuth);
  64. if (index >= 0)
  65. {
  66. // We should not spam the security event log, and should call the Windows LogonUser API
  67. // just to verify the user has a blank password.
  68. if (!AreEmptyPasswordsAllowed() &&
  69. IsPasswordEmpty(lmResponse, ntlmResponse) &&
  70. LoginAPI.HasEmptyPassword(accountNameToAuth))
  71. {
  72. throw new EmptyPasswordNotAllowedException();
  73. }
  74. AuthenticateMessage authenticateMessage = new AuthenticateMessage();
  75. authenticateMessage.NegotiateFlags = NegotiateFlags.NegotiateUnicode | NegotiateFlags.NegotiateOEM | NegotiateFlags.RequestTarget | NegotiateFlags.NegotiateSign | NegotiateFlags.NegotiateSeal | NegotiateFlags.NegotiateLanManagerKey | NegotiateFlags.NegotiateNTLMKey | NegotiateFlags.NegotiateAlwaysSign | NegotiateFlags.NegotiateVersion | NegotiateFlags.Negotiate128 | NegotiateFlags.Negotiate56;
  76. authenticateMessage.UserName = accountNameToAuth;
  77. authenticateMessage.LmChallengeResponse = lmResponse;
  78. authenticateMessage.NtChallengeResponse = ntlmResponse;
  79. authenticateMessage.Version = Authentication.Version.Server2003;
  80. byte[] authenticateMessageBytes = authenticateMessage.GetBytes();
  81. bool success = SSPIHelper.AuthenticateType3Message(m_serverContext, authenticateMessageBytes);
  82. if (success)
  83. {
  84. return this[index];
  85. }
  86. }
  87. return null;
  88. }
  89. /// <summary>
  90. /// Note: The 'limitblankpassworduse' (Under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa)
  91. /// will cause AcceptSecurityContext to return SEC_E_LOGON_DENIED when the correct password is blank.
  92. /// </summary>
  93. public User Authenticate(byte[] authenticateMessageBytes)
  94. {
  95. AuthenticateMessage message = new AuthenticateMessage(authenticateMessageBytes);
  96. if ((message.NegotiateFlags & NegotiateFlags.NegotiateAnonymous) > 0 ||
  97. (String.Equals(message.UserName, "Guest", StringComparison.InvariantCultureIgnoreCase) && IsPasswordEmpty(message) && this.EnableGuestLogin))
  98. {
  99. int guestIndex = IndexOf("Guest");
  100. if (guestIndex >= 0)
  101. {
  102. return this[guestIndex];
  103. }
  104. return null;
  105. }
  106. int index = IndexOf(message.UserName);
  107. if (index >= 0)
  108. {
  109. // We should not spam the security event log, and should call the Windows LogonUser API
  110. // just to verify the user has a blank password.
  111. if (!AreEmptyPasswordsAllowed() &&
  112. IsPasswordEmpty(message) &&
  113. LoginAPI.HasEmptyPassword(message.UserName))
  114. {
  115. throw new EmptyPasswordNotAllowedException();
  116. }
  117. bool success = SSPIHelper.AuthenticateType3Message(m_serverContext, authenticateMessageBytes);
  118. if (success)
  119. {
  120. return this[index];
  121. }
  122. }
  123. return null;
  124. }
  125. public bool IsPasswordEmpty(byte[] lmResponse, byte[] ntlmResponse)
  126. {
  127. // Special case for anonymous authentication
  128. // Windows NT4 SP6 will send 1 null byte OEMPassword and 0 bytes UnicodePassword for anonymous authentication
  129. if (lmResponse.Length == 0 || ByteUtils.AreByteArraysEqual(lmResponse, new byte[] { 0x00 }) || ntlmResponse.Length == 0)
  130. {
  131. return true;
  132. }
  133. byte[] emptyPasswordLMv1Response = NTAuthentication.ComputeLMv1Response(m_serverChallenge, String.Empty);
  134. if (ByteUtils.AreByteArraysEqual(emptyPasswordLMv1Response, lmResponse))
  135. {
  136. return true;
  137. }
  138. byte[] emptyPasswordNTLMv1Response = NTAuthentication.ComputeNTLMv1Response(m_serverChallenge, String.Empty);
  139. if (ByteUtils.AreByteArraysEqual(emptyPasswordNTLMv1Response, ntlmResponse))
  140. {
  141. return true;
  142. }
  143. return false;
  144. }
  145. public bool IsPasswordEmpty(AuthenticateMessage message)
  146. {
  147. // Special case for anonymous authentication, see [MS-NLMP] 3.3.1 - NTLM v1 Authentication
  148. if (message.LmChallengeResponse.Length == 1 || message.NtChallengeResponse.Length == 0)
  149. {
  150. return true;
  151. }
  152. byte[] clientChallenge = ByteReader.ReadBytes(message.LmChallengeResponse, 0, 8);
  153. byte[] emptyPasswordNTLMv1Response = NTAuthentication.ComputeNTLMv1ExtendedSecurityResponse(m_serverChallenge, clientChallenge, String.Empty);
  154. if (ByteUtils.AreByteArraysEqual(emptyPasswordNTLMv1Response, message.NtChallengeResponse))
  155. {
  156. return true;
  157. }
  158. if (message.NtChallengeResponse.Length > 24)
  159. {
  160. NTLMv2ClientChallengeStructure clientChallengeStructure = new NTLMv2ClientChallengeStructure(message.NtChallengeResponse, 16);
  161. byte[] clientChallengeStructurePadded = clientChallengeStructure.GetBytesPadded();
  162. byte[] emptyPasswordNTLMv2Response = NTAuthentication.ComputeNTLMv2Response(m_serverChallenge, clientChallengeStructurePadded, String.Empty, message.UserName, message.DomainName);
  163. if (ByteUtils.AreByteArraysEqual(emptyPasswordNTLMv2Response, message.NtChallengeResponse))
  164. {
  165. return true;
  166. }
  167. }
  168. return false;
  169. }
  170. /// <summary>
  171. /// We immitate Windows, Guest logins are disabled when the guest account has password set
  172. /// </summary>
  173. public bool EnableGuestLogin
  174. {
  175. get
  176. {
  177. return (IndexOf("Guest") >= 0) && LoginAPI.HasEmptyPassword("Guest");
  178. }
  179. }
  180. public static bool AreEmptyPasswordsAllowed()
  181. {
  182. RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\Lsa");
  183. object value = key.GetValue("limitblankpassworduse", 1);
  184. if (value is int)
  185. {
  186. if ((int)value != 0)
  187. {
  188. return false;
  189. }
  190. }
  191. return true;
  192. }
  193. }
  194. }