mirror of
https://github.com/TalAloni/SMBLibrary.git
synced 2025-08-15 19:53:46 +02:00
Improved NTLM authentication API and implementation
This commit is contained in:
parent
6e52e002a6
commit
345f4ae444
7 changed files with 125 additions and 177 deletions
|
@ -34,7 +34,8 @@ namespace SMBLibrary.Authentication
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// NegotiateLanManagerKey and NegotiateExtendedSecurity are mutually exclusive
|
/// NegotiateLanManagerKey and NegotiateExtendedSecurity are mutually exclusive
|
||||||
/// If both are set then NegotiateLanManagerKey must be ignored
|
/// If both are set then NegotiateLanManagerKey must be ignored.
|
||||||
|
/// NTLM v2 requires this flag to be set.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NegotiateExtendedSecurity = 0x80000, // NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
|
NegotiateExtendedSecurity = 0x80000, // NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
|
||||||
NegotiateIdentify = 0x100000, // NTLMSSP_NEGOTIATE_IDENTIFY
|
NegotiateIdentify = 0x100000, // NTLMSSP_NEGOTIATE_IDENTIFY
|
||||||
|
|
|
@ -13,13 +13,9 @@ namespace SMBLibrary.Server
|
||||||
{
|
{
|
||||||
public interface INTLMAuthenticationProvider
|
public interface INTLMAuthenticationProvider
|
||||||
{
|
{
|
||||||
// CIFS style NTLM
|
ChallengeMessage GetChallengeMessage(NegotiateMessage negotiateMessage);
|
||||||
byte[] GenerateServerChallenge();
|
|
||||||
User Authenticate(string accountNameToAuth, byte[] lmResponse, byte[] ntlmResponse);
|
|
||||||
|
|
||||||
// SSPI style NTLM
|
bool Authenticate(AuthenticateMessage authenticateMessage);
|
||||||
byte[] GetChallengeMessageBytes(byte[] negotiateMessageBytes);
|
|
||||||
User Authenticate(byte[] authenticateMessageBytes);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Permit access to this user via the guest user account if the normal authentication process fails.
|
/// Permit access to this user via the guest user account if the normal authentication process fails.
|
||||||
|
|
|
@ -28,7 +28,7 @@ namespace SMBLibrary.Server
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// LM v1 / NTLM v1
|
/// LM v1 / NTLM v1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public User AuthenticateV1(string accountNameToAuth, byte[] serverChallenge, byte[] lmResponse, byte[] ntlmResponse)
|
private User AuthenticateV1(string accountNameToAuth, byte[] serverChallenge, byte[] lmResponse, byte[] ntlmResponse)
|
||||||
{
|
{
|
||||||
for (int index = 0; index < this.Count; index++)
|
for (int index = 0; index < this.Count; index++)
|
||||||
{
|
{
|
||||||
|
@ -56,7 +56,7 @@ namespace SMBLibrary.Server
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// LM v1 / NTLM v1 Extended Security
|
/// LM v1 / NTLM v1 Extended Security
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public User AuthenticateV1Extended(string accountNameToAuth, byte[] serverChallenge, byte[] lmResponse, byte[] ntlmResponse)
|
private User AuthenticateV1Extended(string accountNameToAuth, byte[] serverChallenge, byte[] lmResponse, byte[] ntlmResponse)
|
||||||
{
|
{
|
||||||
for (int index = 0; index < this.Count; index++)
|
for (int index = 0; index < this.Count; index++)
|
||||||
{
|
{
|
||||||
|
@ -80,7 +80,7 @@ namespace SMBLibrary.Server
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// LM v2 / NTLM v2
|
/// LM v2 / NTLM v2
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public User AuthenticateV2(string domainNameToAuth, string accountNameToAuth, byte[] serverChallenge, byte[] lmResponse, byte[] ntlmResponse)
|
private User AuthenticateV2(string domainNameToAuth, string accountNameToAuth, byte[] serverChallenge, byte[] lmResponse, byte[] ntlmResponse)
|
||||||
{
|
{
|
||||||
for (int index = 0; index < this.Count; index++)
|
for (int index = 0; index < this.Count; index++)
|
||||||
{
|
{
|
||||||
|
@ -112,13 +112,13 @@ namespace SMBLibrary.Server
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] GenerateServerChallenge()
|
private byte[] GenerateServerChallenge()
|
||||||
{
|
{
|
||||||
new Random().NextBytes(m_serverChallenge);
|
new Random().NextBytes(m_serverChallenge);
|
||||||
return m_serverChallenge;
|
return m_serverChallenge;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChallengeMessage GetChallengeMessage(byte[] negotiateMessageBytes)
|
public ChallengeMessage GetChallengeMessage(NegotiateMessage negotiateMessage)
|
||||||
{
|
{
|
||||||
byte[] serverChallenge = GenerateServerChallenge();
|
byte[] serverChallenge = GenerateServerChallenge();
|
||||||
|
|
||||||
|
@ -138,42 +138,29 @@ namespace SMBLibrary.Server
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] GetChallengeMessageBytes(byte[] negotiateMessageBytes)
|
public bool Authenticate(AuthenticateMessage message)
|
||||||
{
|
{
|
||||||
ChallengeMessage message = GetChallengeMessage(negotiateMessageBytes);
|
if ((message.NegotiateFlags & NegotiateFlags.NegotiateAnonymous) > 0)
|
||||||
return message.GetBytes();
|
{
|
||||||
|
return this.EnableGuestLogin;
|
||||||
}
|
}
|
||||||
|
|
||||||
public User Authenticate(byte[] authenticateMessageBytes)
|
|
||||||
{
|
|
||||||
AuthenticateMessage message = new AuthenticateMessage(authenticateMessageBytes);
|
|
||||||
return Authenticate(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public User Authenticate(AuthenticateMessage message)
|
|
||||||
{
|
|
||||||
User user;
|
User user;
|
||||||
if ((message.NegotiateFlags & NegotiateFlags.NegotiateExtendedSecurity) > 0)
|
if ((message.NegotiateFlags & NegotiateFlags.NegotiateExtendedSecurity) > 0)
|
||||||
{
|
{
|
||||||
user = AuthenticateV1Extended(message.UserName, m_serverChallenge, message.LmChallengeResponse, message.NtChallengeResponse);
|
user = AuthenticateV1Extended(message.UserName, m_serverChallenge, message.LmChallengeResponse, message.NtChallengeResponse);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
// NTLM v2:
|
||||||
|
user = AuthenticateV2(message.DomainName, message.UserName, m_serverChallenge, message.LmChallengeResponse, message.NtChallengeResponse);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
user = AuthenticateV1(message.UserName, m_serverChallenge, message.LmChallengeResponse, message.NtChallengeResponse);
|
user = AuthenticateV1(message.UserName, m_serverChallenge, message.LmChallengeResponse, message.NtChallengeResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user == null)
|
return (user != null);
|
||||||
{
|
|
||||||
// NTLM v2
|
|
||||||
user = AuthenticateV2(message.DomainName, message.UserName, m_serverChallenge, message.LmChallengeResponse, message.NtChallengeResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public User Authenticate(string accountNameToAuth, byte[] lmResponse, byte[] ntlmResponse)
|
|
||||||
{
|
|
||||||
return AuthenticateV1(accountNameToAuth, m_serverChallenge, lmResponse, ntlmResponse);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool FallbackToGuest(string userName)
|
public bool FallbackToGuest(string userName)
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace SMBLibrary.Server.SMB1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class NegotiateHelper
|
public class NegotiateHelper
|
||||||
{
|
{
|
||||||
internal static NegotiateResponseNTLM GetNegotiateResponse(SMB1Header header, NegotiateRequest request, byte[] serverChallenge)
|
internal static NegotiateResponseNTLM GetNegotiateResponse(SMB1Header header, NegotiateRequest request, INTLMAuthenticationProvider users)
|
||||||
{
|
{
|
||||||
NegotiateResponseNTLM response = new NegotiateResponseNTLM();
|
NegotiateResponseNTLM response = new NegotiateResponseNTLM();
|
||||||
|
|
||||||
|
@ -37,7 +37,8 @@ namespace SMBLibrary.Server.SMB1
|
||||||
ServerCapabilities.LargeWrite;
|
ServerCapabilities.LargeWrite;
|
||||||
response.SystemTime = DateTime.UtcNow;
|
response.SystemTime = DateTime.UtcNow;
|
||||||
response.ServerTimeZone = (short)-TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).TotalMinutes;
|
response.ServerTimeZone = (short)-TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).TotalMinutes;
|
||||||
response.Challenge = serverChallenge;
|
ChallengeMessage challengeMessage = users.GetChallengeMessage(CreateNegotiateMessage());
|
||||||
|
response.Challenge = challengeMessage.ServerChallenge;
|
||||||
response.DomainName = String.Empty;
|
response.DomainName = String.Empty;
|
||||||
response.ServerName = String.Empty;
|
response.ServerName = String.Empty;
|
||||||
|
|
||||||
|
@ -67,5 +68,13 @@ namespace SMBLibrary.Server.SMB1
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static NegotiateMessage CreateNegotiateMessage()
|
||||||
|
{
|
||||||
|
NegotiateMessage negotiateMessage = new NegotiateMessage();
|
||||||
|
negotiateMessage.NegotiateFlags = NegotiateFlags.NegotiateUnicode | NegotiateFlags.NegotiateOEM | NegotiateFlags.RequestTarget | NegotiateFlags.NegotiateSign | NegotiateFlags.NegotiateSeal | NegotiateFlags.NegotiateLanManagerKey | NegotiateFlags.NegotiateNTLMKey | NegotiateFlags.NegotiateAlwaysSign | NegotiateFlags.NegotiateVersion | NegotiateFlags.Negotiate128 | NegotiateFlags.Negotiate56;
|
||||||
|
negotiateMessage.Version = Authentication.Version.Server2003;
|
||||||
|
return negotiateMessage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,10 +24,11 @@ namespace SMBLibrary.Server.SMB1
|
||||||
// The PrimaryDomain field in the request is used to determine with domain controller should authenticate the user credentials,
|
// The PrimaryDomain field in the request is used to determine with domain controller should authenticate the user credentials,
|
||||||
// However, the domain controller itself does not use this field.
|
// However, the domain controller itself does not use this field.
|
||||||
// See: http://msdn.microsoft.com/en-us/library/windows/desktop/aa378749%28v=vs.85%29.aspx
|
// See: http://msdn.microsoft.com/en-us/library/windows/desktop/aa378749%28v=vs.85%29.aspx
|
||||||
User user;
|
AuthenticateMessage message = CreateAuthenticateMessage(request.AccountName, request.OEMPassword, request.UnicodePassword);
|
||||||
|
bool loginSuccess;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
user = users.Authenticate(request.AccountName, request.OEMPassword, request.UnicodePassword);
|
loginSuccess = users.Authenticate(message);
|
||||||
}
|
}
|
||||||
catch (EmptyPasswordNotAllowedException)
|
catch (EmptyPasswordNotAllowedException)
|
||||||
{
|
{
|
||||||
|
@ -35,9 +36,9 @@ namespace SMBLibrary.Server.SMB1
|
||||||
return new ErrorResponse(CommandName.SMB_COM_SESSION_SETUP_ANDX);
|
return new ErrorResponse(CommandName.SMB_COM_SESSION_SETUP_ANDX);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user != null)
|
if (loginSuccess)
|
||||||
{
|
{
|
||||||
ushort? userID = state.AddConnectedUser(user.AccountName);
|
ushort? userID = state.AddConnectedUser(message.UserName);
|
||||||
if (!userID.HasValue)
|
if (!userID.HasValue)
|
||||||
{
|
{
|
||||||
header.Status = NTStatus.STATUS_TOO_MANY_SESSIONS;
|
header.Status = NTStatus.STATUS_TOO_MANY_SESSIONS;
|
||||||
|
@ -46,7 +47,7 @@ namespace SMBLibrary.Server.SMB1
|
||||||
header.UID = userID.Value;
|
header.UID = userID.Value;
|
||||||
response.PrimaryDomain = request.PrimaryDomain;
|
response.PrimaryDomain = request.PrimaryDomain;
|
||||||
}
|
}
|
||||||
else if (users.FallbackToGuest(user.AccountName))
|
else if (users.FallbackToGuest(message.UserName))
|
||||||
{
|
{
|
||||||
ushort? userID = state.AddConnectedUser("Guest");
|
ushort? userID = state.AddConnectedUser("Guest");
|
||||||
if (!userID.HasValue)
|
if (!userID.HasValue)
|
||||||
|
@ -98,23 +99,25 @@ namespace SMBLibrary.Server.SMB1
|
||||||
MessageTypeName messageType = AuthenticationMessageUtils.GetMessageType(messageBytes);
|
MessageTypeName messageType = AuthenticationMessageUtils.GetMessageType(messageBytes);
|
||||||
if (messageType == MessageTypeName.Negotiate)
|
if (messageType == MessageTypeName.Negotiate)
|
||||||
{
|
{
|
||||||
byte[] challengeMessageBytes = users.GetChallengeMessageBytes(messageBytes);
|
NegotiateMessage negotiateMessage = new NegotiateMessage(messageBytes);
|
||||||
|
ChallengeMessage challengeMessage = users.GetChallengeMessage(negotiateMessage);
|
||||||
if (isRawMessage)
|
if (isRawMessage)
|
||||||
{
|
{
|
||||||
response.SecurityBlob = challengeMessageBytes;
|
response.SecurityBlob = challengeMessage.GetBytes();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
response.SecurityBlob = GSSAPIHelper.GetGSSTokenResponseBytesFromNTLMSSPMessage(challengeMessageBytes);
|
response.SecurityBlob = GSSAPIHelper.GetGSSTokenResponseBytesFromNTLMSSPMessage(challengeMessage.GetBytes());
|
||||||
}
|
}
|
||||||
header.Status = NTStatus.STATUS_MORE_PROCESSING_REQUIRED;
|
header.Status = NTStatus.STATUS_MORE_PROCESSING_REQUIRED;
|
||||||
}
|
}
|
||||||
else // MessageTypeName.Authenticate
|
else // MessageTypeName.Authenticate
|
||||||
{
|
{
|
||||||
User user;
|
AuthenticateMessage authenticateMessage = new AuthenticateMessage(messageBytes);
|
||||||
|
bool loginSuccess;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
user = users.Authenticate(messageBytes);
|
loginSuccess = users.Authenticate(authenticateMessage);
|
||||||
}
|
}
|
||||||
catch (EmptyPasswordNotAllowedException)
|
catch (EmptyPasswordNotAllowedException)
|
||||||
{
|
{
|
||||||
|
@ -122,9 +125,9 @@ namespace SMBLibrary.Server.SMB1
|
||||||
return new ErrorResponse(CommandName.SMB_COM_SESSION_SETUP_ANDX);
|
return new ErrorResponse(CommandName.SMB_COM_SESSION_SETUP_ANDX);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user != null)
|
if (loginSuccess)
|
||||||
{
|
{
|
||||||
ushort? userID = state.AddConnectedUser(user.AccountName);
|
ushort? userID = state.AddConnectedUser(authenticateMessage.UserName);
|
||||||
if (!userID.HasValue)
|
if (!userID.HasValue)
|
||||||
{
|
{
|
||||||
header.Status = NTStatus.STATUS_TOO_MANY_SESSIONS;
|
header.Status = NTStatus.STATUS_TOO_MANY_SESSIONS;
|
||||||
|
@ -132,7 +135,7 @@ namespace SMBLibrary.Server.SMB1
|
||||||
}
|
}
|
||||||
header.UID = userID.Value;
|
header.UID = userID.Value;
|
||||||
}
|
}
|
||||||
else if (users.FallbackToGuest(user.AccountName))
|
else if (users.FallbackToGuest(authenticateMessage.UserName))
|
||||||
{
|
{
|
||||||
ushort? userID = state.AddConnectedUser("Guest");
|
ushort? userID = state.AddConnectedUser("Guest");
|
||||||
if (!userID.HasValue)
|
if (!userID.HasValue)
|
||||||
|
@ -154,5 +157,16 @@ namespace SMBLibrary.Server.SMB1
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static AuthenticateMessage CreateAuthenticateMessage(string accountNameToAuth, byte[] lmResponse, byte[] ntlmResponse)
|
||||||
|
{
|
||||||
|
AuthenticateMessage authenticateMessage = new AuthenticateMessage();
|
||||||
|
authenticateMessage.NegotiateFlags = NegotiateFlags.NegotiateUnicode | NegotiateFlags.NegotiateOEM | NegotiateFlags.RequestTarget | NegotiateFlags.NegotiateSign | NegotiateFlags.NegotiateSeal | NegotiateFlags.NegotiateLanManagerKey | NegotiateFlags.NegotiateNTLMKey | NegotiateFlags.NegotiateAlwaysSign | NegotiateFlags.NegotiateVersion | NegotiateFlags.Negotiate128 | NegotiateFlags.Negotiate56;
|
||||||
|
authenticateMessage.UserName = accountNameToAuth;
|
||||||
|
authenticateMessage.LmChallengeResponse = lmResponse;
|
||||||
|
authenticateMessage.NtChallengeResponse = ntlmResponse;
|
||||||
|
authenticateMessage.Version = Authentication.Version.Server2003;
|
||||||
|
return authenticateMessage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,8 +64,7 @@ namespace SMBLibrary.Server
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
byte[] serverChallenge = m_users.GenerateServerChallenge();
|
return NegotiateHelper.GetNegotiateResponse(header, request, m_users);
|
||||||
return NegotiateHelper.GetNegotiateResponse(header, request, serverChallenge);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -29,145 +29,60 @@ namespace SMBLibrary.Server.Win32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] GenerateServerChallenge()
|
public ChallengeMessage GetChallengeMessage(NegotiateMessage negotiateMessage)
|
||||||
{
|
{
|
||||||
NegotiateMessage negotiateMessage = new NegotiateMessage();
|
|
||||||
negotiateMessage.NegotiateFlags = NegotiateFlags.NegotiateUnicode | NegotiateFlags.NegotiateOEM | NegotiateFlags.RequestTarget | NegotiateFlags.NegotiateSign | NegotiateFlags.NegotiateSeal | NegotiateFlags.NegotiateLanManagerKey | NegotiateFlags.NegotiateNTLMKey | NegotiateFlags.NegotiateAlwaysSign | NegotiateFlags.NegotiateVersion | NegotiateFlags.Negotiate128 | NegotiateFlags.Negotiate56;
|
|
||||||
negotiateMessage.Version = Authentication.Version.Server2003;
|
|
||||||
|
|
||||||
byte[] negotiateMessageBytes = negotiateMessage.GetBytes();
|
byte[] negotiateMessageBytes = negotiateMessage.GetBytes();
|
||||||
byte[] challengeMessageBytes = SSPIHelper.GetType2Message(negotiateMessageBytes, out m_serverContext);
|
byte[] challengeMessageBytes = SSPIHelper.GetType2Message(negotiateMessageBytes, out m_serverContext);
|
||||||
ChallengeMessage challengeMessage = new ChallengeMessage(challengeMessageBytes);
|
ChallengeMessage challengeMessage = new ChallengeMessage(challengeMessageBytes);
|
||||||
|
|
||||||
m_serverChallenge = challengeMessage.ServerChallenge;
|
m_serverChallenge = challengeMessage.ServerChallenge;
|
||||||
return m_serverChallenge;
|
return challengeMessage;
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] GetChallengeMessageBytes(byte[] negotiateMessageBytes)
|
|
||||||
{
|
|
||||||
byte[] challengeMessageBytes = SSPIHelper.GetType2Message(negotiateMessageBytes, out m_serverContext);
|
|
||||||
ChallengeMessage message = new ChallengeMessage(challengeMessageBytes);
|
|
||||||
m_serverChallenge = message.ServerChallenge;
|
|
||||||
return challengeMessageBytes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Note: The 'limitblankpassworduse' (Under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa)
|
/// Authenticate will return false when the password is correct in these cases:
|
||||||
/// will cause AcceptSecurityContext to return SEC_E_LOGON_DENIED when the correct password is blank.
|
/// 1. The correct password is blank and 'limitblankpassworduse' is set to 1.
|
||||||
|
/// 2. The user is listed in the "Deny access to this computer from the network" list.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public User Authenticate(string accountNameToAuth, byte[] lmResponse, byte[] ntlmResponse)
|
public bool Authenticate(AuthenticateMessage message)
|
||||||
{
|
{
|
||||||
if (accountNameToAuth == String.Empty ||
|
if ((message.NegotiateFlags & NegotiateFlags.NegotiateAnonymous) > 0)
|
||||||
(String.Equals(accountNameToAuth, "Guest", StringComparison.InvariantCultureIgnoreCase) && IsPasswordEmpty(lmResponse, ntlmResponse) && this.EnableGuestLogin))
|
|
||||||
{
|
{
|
||||||
int guestIndex = IndexOf("Guest");
|
return this.EnableGuestLogin;
|
||||||
if (guestIndex >= 0)
|
|
||||||
{
|
|
||||||
return this[guestIndex];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int index = IndexOf(accountNameToAuth);
|
// AuthenticateType3Message is not reliable when 'limitblankpassworduse' is set to 1 and the user has an empty password set.
|
||||||
if (index >= 0)
|
// Note: Windows LogonUser API calls will be listed in the security event log.
|
||||||
{
|
|
||||||
// We should not spam the security event log, and should call the Windows LogonUser API
|
|
||||||
// just to verify the user has a blank password.
|
|
||||||
if (!AreEmptyPasswordsAllowed() &&
|
|
||||||
IsPasswordEmpty(lmResponse, ntlmResponse) &&
|
|
||||||
LoginAPI.HasEmptyPassword(accountNameToAuth))
|
|
||||||
{
|
|
||||||
throw new EmptyPasswordNotAllowedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthenticateMessage authenticateMessage = new AuthenticateMessage();
|
|
||||||
authenticateMessage.NegotiateFlags = NegotiateFlags.NegotiateUnicode | NegotiateFlags.NegotiateOEM | NegotiateFlags.RequestTarget | NegotiateFlags.NegotiateSign | NegotiateFlags.NegotiateSeal | NegotiateFlags.NegotiateLanManagerKey | NegotiateFlags.NegotiateNTLMKey | NegotiateFlags.NegotiateAlwaysSign | NegotiateFlags.NegotiateVersion | NegotiateFlags.Negotiate128 | NegotiateFlags.Negotiate56;
|
|
||||||
authenticateMessage.UserName = accountNameToAuth;
|
|
||||||
authenticateMessage.LmChallengeResponse = lmResponse;
|
|
||||||
authenticateMessage.NtChallengeResponse = ntlmResponse;
|
|
||||||
authenticateMessage.Version = Authentication.Version.Server2003;
|
|
||||||
byte[] authenticateMessageBytes = authenticateMessage.GetBytes();
|
|
||||||
|
|
||||||
bool success = SSPIHelper.AuthenticateType3Message(m_serverContext, authenticateMessageBytes);
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
return this[index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Note: The 'limitblankpassworduse' (Under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa)
|
|
||||||
/// will cause AcceptSecurityContext to return SEC_E_LOGON_DENIED when the correct password is blank.
|
|
||||||
/// </summary>
|
|
||||||
public User Authenticate(byte[] authenticateMessageBytes)
|
|
||||||
{
|
|
||||||
AuthenticateMessage message = new AuthenticateMessage(authenticateMessageBytes);
|
|
||||||
if ((message.NegotiateFlags & NegotiateFlags.NegotiateAnonymous) > 0 ||
|
|
||||||
(String.Equals(message.UserName, "Guest", StringComparison.InvariantCultureIgnoreCase) && IsPasswordEmpty(message) && this.EnableGuestLogin))
|
|
||||||
{
|
|
||||||
int guestIndex = IndexOf("Guest");
|
|
||||||
if (guestIndex >= 0)
|
|
||||||
{
|
|
||||||
return this[guestIndex];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
int index = IndexOf(message.UserName);
|
|
||||||
if (index >= 0)
|
|
||||||
{
|
|
||||||
// We should not spam the security event log, and should call the Windows LogonUser API
|
|
||||||
// just to verify the user has a blank password.
|
|
||||||
if (!AreEmptyPasswordsAllowed() &&
|
if (!AreEmptyPasswordsAllowed() &&
|
||||||
IsPasswordEmpty(message) &&
|
IsPasswordEmpty(message) &&
|
||||||
LoginAPI.HasEmptyPassword(message.UserName))
|
LoginAPI.HasEmptyPassword(message.UserName))
|
||||||
|
{
|
||||||
|
if (FallbackToGuest(message.UserName))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
throw new EmptyPasswordNotAllowedException();
|
throw new EmptyPasswordNotAllowedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = SSPIHelper.AuthenticateType3Message(m_serverContext, authenticateMessageBytes);
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
return this[index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsPasswordEmpty(byte[] lmResponse, byte[] ntlmResponse)
|
byte[] messageBytes = message.GetBytes();
|
||||||
{
|
bool success = SSPIHelper.AuthenticateType3Message(m_serverContext, messageBytes);
|
||||||
// Special case for anonymous authentication
|
return success;
|
||||||
// Windows NT4 SP6 will send 1 null byte OEMPassword and 0 bytes UnicodePassword for anonymous authentication
|
|
||||||
if (lmResponse.Length == 0 || ByteUtils.AreByteArraysEqual(lmResponse, new byte[] { 0x00 }) || ntlmResponse.Length == 0)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] emptyPasswordLMv1Response = NTAuthentication.ComputeLMv1Response(m_serverChallenge, String.Empty);
|
|
||||||
if (ByteUtils.AreByteArraysEqual(emptyPasswordLMv1Response, lmResponse))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] emptyPasswordNTLMv1Response = NTAuthentication.ComputeNTLMv1Response(m_serverChallenge, String.Empty);
|
|
||||||
if (ByteUtils.AreByteArraysEqual(emptyPasswordNTLMv1Response, ntlmResponse))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsPasswordEmpty(AuthenticateMessage message)
|
public bool IsPasswordEmpty(AuthenticateMessage message)
|
||||||
{
|
{
|
||||||
// Special case for anonymous authentication, see [MS-NLMP] 3.3.1 - NTLM v1 Authentication
|
// See [MS-NLMP] 3.3.1 - NTLM v1 Authentication
|
||||||
|
// Special case for anonymous authentication:
|
||||||
if (message.LmChallengeResponse.Length == 1 || message.NtChallengeResponse.Length == 0)
|
if (message.LmChallengeResponse.Length == 1 || message.NtChallengeResponse.Length == 0)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((message.NegotiateFlags & NegotiateFlags.NegotiateExtendedSecurity) > 0)
|
||||||
|
{
|
||||||
|
// NTLM v1 extended security:
|
||||||
byte[] clientChallenge = ByteReader.ReadBytes(message.LmChallengeResponse, 0, 8);
|
byte[] clientChallenge = ByteReader.ReadBytes(message.LmChallengeResponse, 0, 8);
|
||||||
byte[] emptyPasswordNTLMv1Response = NTAuthentication.ComputeNTLMv1ExtendedSecurityResponse(m_serverChallenge, clientChallenge, String.Empty);
|
byte[] emptyPasswordNTLMv1Response = NTAuthentication.ComputeNTLMv1ExtendedSecurityResponse(m_serverChallenge, clientChallenge, String.Empty);
|
||||||
if (ByteUtils.AreByteArraysEqual(emptyPasswordNTLMv1Response, message.NtChallengeResponse))
|
if (ByteUtils.AreByteArraysEqual(emptyPasswordNTLMv1Response, message.NtChallengeResponse))
|
||||||
|
@ -175,6 +90,14 @@ namespace SMBLibrary.Server.Win32
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NTLM v2:
|
||||||
|
byte[] _LMv2ClientChallenge = ByteReader.ReadBytes(message.LmChallengeResponse, 16, 8);
|
||||||
|
byte[] emptyPasswordLMv2Response = NTAuthentication.ComputeLMv2Response(m_serverChallenge, _LMv2ClientChallenge, String.Empty, message.UserName, message.DomainName);
|
||||||
|
if (ByteUtils.AreByteArraysEqual(emptyPasswordLMv2Response, message.LmChallengeResponse))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (message.NtChallengeResponse.Length > 24)
|
if (message.NtChallengeResponse.Length > 24)
|
||||||
{
|
{
|
||||||
NTLMv2ClientChallengeStructure clientChallengeStructure = new NTLMv2ClientChallengeStructure(message.NtChallengeResponse, 16);
|
NTLMv2ClientChallengeStructure clientChallengeStructure = new NTLMv2ClientChallengeStructure(message.NtChallengeResponse, 16);
|
||||||
|
@ -185,6 +108,22 @@ namespace SMBLibrary.Server.Win32
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// NTLM v1:
|
||||||
|
byte[] emptyPasswordLMv1Response = NTAuthentication.ComputeLMv1Response(m_serverChallenge, String.Empty);
|
||||||
|
if (ByteUtils.AreByteArraysEqual(emptyPasswordLMv1Response, message.LmChallengeResponse))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] emptyPasswordNTLMv1Response = NTAuthentication.ComputeNTLMv1Response(m_serverChallenge, String.Empty);
|
||||||
|
if (ByteUtils.AreByteArraysEqual(emptyPasswordNTLMv1Response, message.NtChallengeResponse))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -195,13 +134,16 @@ namespace SMBLibrary.Server.Win32
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// We immitate Windows, Guest logins are disabled when the guest account has password set
|
/// We immitate Windows, Guest logins are disabled in any of these cases:
|
||||||
|
/// 1. The Guest account is disabled.
|
||||||
|
/// 2. The Guest account has password set.
|
||||||
|
/// 3. The Guest account is listed in the "deny access to this computer from the network" list.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool EnableGuestLogin
|
private bool EnableGuestLogin
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return (IndexOf("Guest") >= 0) && LoginAPI.HasEmptyPassword("Guest");
|
return LoginAPI.ValidateUserPassword("Guest", String.Empty, LogonType.Network);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue