Improved NTLM authentication API

This commit is contained in:
Tal Aloni 2017-02-17 19:01:58 +02:00
parent 05f49c3128
commit 217451d18f
23 changed files with 602 additions and 526 deletions

View file

@ -0,0 +1,18 @@
using System;
namespace SMBLibrary.Authentication.GSSAPI
{
public enum GSSAttributeName
{
AccessToken,
IsAnonymous,
/// <summary>
/// Permit access to this user via the guest user account if the normal authentication process fails.
/// </summary>
IsGuest,
MachineName,
SessionKey,
UserName,
}
}

View file

@ -0,0 +1,236 @@
/* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
*
* You can redistribute this program and/or modify it under the terms of
* the GNU Lesser Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*/
using System;
using System.Collections.Generic;
using SMBLibrary.Authentication.GSSAPI;
using Utilities;
namespace SMBLibrary.Authentication.NTLM
{
public delegate string GetUserPassword(string userName);
public class IndependentNTLMAuthenticationProvider : NTLMAuthenticationProviderBase
{
public class AuthContext
{
public string WorkStation;
public byte[] ServerChallenge;
public string UserName;
public byte[] SessionKey;
public bool IsGuest;
public AuthContext(string workStation, byte[] serverChallenge)
{
WorkStation = workStation;
ServerChallenge = serverChallenge;
}
}
private GetUserPassword m_GetUserPassword;
/// <param name="getUserPassword">
/// The NTLM challenge response will be compared against the provided password.
/// </param>
public IndependentNTLMAuthenticationProvider(GetUserPassword getUserPassword)
{
m_GetUserPassword = getUserPassword;
}
public override Win32Error GetChallengeMessage(out object context, NegotiateMessage negotiateMessage, out ChallengeMessage challengeMessage)
{
byte[] serverChallenge = GenerateServerChallenge();
context = new AuthContext(negotiateMessage.Workstation, serverChallenge);
challengeMessage = new ChallengeMessage();
challengeMessage.NegotiateFlags = NegotiateFlags.UnicodeEncoding |
NegotiateFlags.TargetNameSupplied |
NegotiateFlags.NTLMKey |
NegotiateFlags.TargetTypeServer |
NegotiateFlags.ExtendedSecurity |
NegotiateFlags.TargetInfo |
NegotiateFlags.Version;
if ((negotiateMessage.NegotiateFlags & NegotiateFlags.Sign) > 0)
{
// [MS-NLMP] If the client sends NTLMSSP_NEGOTIATE_SIGN to the server in the NEGOTIATE_MESSAGE,
// the server MUST return NTLMSSP_NEGOTIATE_SIGN to the client in the CHALLENGE_MESSAGE.
challengeMessage.NegotiateFlags |= NegotiateFlags.Sign;
}
if ((negotiateMessage.NegotiateFlags & NegotiateFlags.Use56BitEncryption) > 0)
{
challengeMessage.NegotiateFlags |= NegotiateFlags.Use56BitEncryption;
}
if ((negotiateMessage.NegotiateFlags & NegotiateFlags.Use128BitEncryption) > 0)
{
challengeMessage.NegotiateFlags |= NegotiateFlags.Use128BitEncryption;
}
challengeMessage.TargetName = Environment.MachineName;
challengeMessage.ServerChallenge = serverChallenge;
challengeMessage.TargetInfo = AVPairUtils.GetAVPairSequence(Environment.MachineName, Environment.MachineName);
challengeMessage.Version = NTLMVersion.Server2003;
return Win32Error.ERROR_SUCCESS;
}
public override Win32Error Authenticate(object context, AuthenticateMessage message)
{
AuthContext authContext = context as AuthContext;
if (authContext == null)
{
return Win32Error.ERROR_NO_TOKEN;
}
authContext.UserName = message.UserName;
authContext.SessionKey = message.EncryptedRandomSessionKey;
if ((message.NegotiateFlags & NegotiateFlags.Anonymous) > 0)
{
if (this.EnableGuestLogin)
{
authContext.IsGuest = true;
return Win32Error.ERROR_SUCCESS;
}
else
{
return Win32Error.ERROR_LOGON_FAILURE;
}
}
string password = m_GetUserPassword(message.UserName);
if (password == null)
{
if (this.EnableGuestLogin)
{
authContext.IsGuest = true;
return Win32Error.ERROR_SUCCESS;
}
else
{
return Win32Error.ERROR_LOGON_FAILURE;
}
}
bool success;
byte[] serverChallenge = authContext.ServerChallenge;
if ((message.NegotiateFlags & NegotiateFlags.ExtendedSecurity) > 0)
{
if (AuthenticationMessageUtils.IsNTLMv1ExtendedSecurity(message.LmChallengeResponse))
{
// NTLM v1 Extended Security:
success = AuthenticateV1Extended(password, serverChallenge, message.LmChallengeResponse, message.NtChallengeResponse);
}
else
{
// NTLM v2:
success = AuthenticateV2(message.DomainName, message.UserName, password, serverChallenge, message.LmChallengeResponse, message.NtChallengeResponse);
}
}
else
{
success = AuthenticateV1(password, serverChallenge, message.LmChallengeResponse, message.NtChallengeResponse);
}
if (success)
{
return Win32Error.ERROR_SUCCESS;
}
else
{
return Win32Error.ERROR_LOGON_FAILURE;
}
}
public override void DeleteSecurityContext(ref object context)
{
}
public override object GetContextAttribute(object context, GSSAttributeName attributeName)
{
AuthContext authContext = context as AuthContext;
if (authContext != null)
{
switch (attributeName)
{
case GSSAttributeName.IsGuest:
return authContext.IsGuest;
case GSSAttributeName.MachineName:
return authContext.WorkStation;
case GSSAttributeName.SessionKey:
return authContext.SessionKey;
case GSSAttributeName.UserName:
return authContext.UserName;
}
}
return null;
}
private bool EnableGuestLogin
{
get
{
return (m_GetUserPassword("Guest") == String.Empty);
}
}
/// <summary>
/// LM v1 / NTLM v1
/// </summary>
private static bool AuthenticateV1(string password, byte[] serverChallenge, byte[] lmResponse, byte[] ntResponse)
{
byte[] expectedLMResponse = NTLMCryptography.ComputeLMv1Response(serverChallenge, password);
if (ByteUtils.AreByteArraysEqual(expectedLMResponse, lmResponse))
{
return true;
}
byte[] expectedNTResponse = NTLMCryptography.ComputeNTLMv1Response(serverChallenge, password);
return ByteUtils.AreByteArraysEqual(expectedNTResponse, ntResponse);
}
/// <summary>
/// LM v1 / NTLM v1 Extended Security
/// </summary>
private static bool AuthenticateV1Extended(string password, byte[] serverChallenge, byte[] lmResponse, byte[] ntResponse)
{
byte[] clientChallenge = ByteReader.ReadBytes(lmResponse, 0, 8);
byte[] expectedNTLMv1Response = NTLMCryptography.ComputeNTLMv1ExtendedSecurityResponse(serverChallenge, clientChallenge, password);
return ByteUtils.AreByteArraysEqual(expectedNTLMv1Response, ntResponse);
}
/// <summary>
/// LM v2 / NTLM v2
/// </summary>
private bool AuthenticateV2(string domainName, string accountName, string password, byte[] serverChallenge, byte[] lmResponse, byte[] ntResponse)
{
byte[] _LMv2ClientChallenge = ByteReader.ReadBytes(lmResponse, 16, 8);
byte[] expectedLMv2Response = NTLMCryptography.ComputeLMv2Response(serverChallenge, _LMv2ClientChallenge, password, accountName, domainName);
if (ByteUtils.AreByteArraysEqual(expectedLMv2Response, lmResponse))
{
return true;
}
if (AuthenticationMessageUtils.IsNTLMv2NTResponse(ntResponse))
{
byte[] clientNTProof = ByteReader.ReadBytes(ntResponse, 0, 16);
byte[] clientChallengeStructurePadded = ByteReader.ReadBytes(ntResponse, 16, ntResponse.Length - 16);
byte[] expectedNTProof = NTLMCryptography.ComputeNTLMv2Proof(serverChallenge, clientChallengeStructurePadded, password, accountName, domainName);
return ByteUtils.AreByteArraysEqual(clientNTProof, expectedNTProof);
}
return false;
}
/// <summary>
/// Generate 8-byte server challenge
/// </summary>
private static byte[] GenerateServerChallenge()
{
byte[] serverChallenge = new byte[8];
new Random().NextBytes(serverChallenge);
return serverChallenge;
}
}
}

View file

@ -0,0 +1,23 @@
/* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
*
* You can redistribute this program and/or modify it under the terms of
* the GNU Lesser Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*/
using System;
using System.Collections.Generic;
using SMBLibrary.Authentication.GSSAPI;
namespace SMBLibrary.Authentication.NTLM
{
public abstract class NTLMAuthenticationProviderBase
{
public abstract Win32Error GetChallengeMessage(out object context, NegotiateMessage negotiateMessage, out ChallengeMessage challengeMessage);
public abstract Win32Error Authenticate(object context, AuthenticateMessage authenticateMessage);
public abstract void DeleteSecurityContext(ref object context);
public abstract object GetContextAttribute(object context, GSSAttributeName attributeName);
}
}

View file

@ -25,6 +25,10 @@ namespace SMBLibrary
STATUS_FILE_LOCK_CONFLICT = 0xC0000054,
STATUS_LOGON_FAILURE = 0xC000006D, // Authentication failure.
STATUS_ACCOUNT_RESTRICTION = 0xC000006E, // The user has an empty password, which is not allowed
STATUS_INVALID_LOGON_HOURS = 0xC000006F,
STATUS_INVALID_WORKSTATION = 0xC0000070,
STATUS_PASSWORD_EXPIRED = 0xC0000071,
STATUS_ACCOUNT_DISABLED = 0xC0000072,
STATUS_DISK_FULL = 0xC000007F,
STATUS_INSUFFICIENT_RESOURCES = 0xC000009A,
STATUS_MEDIA_WRITE_PROTECTED = 0xC00000A2,
@ -36,9 +40,13 @@ namespace SMBLibrary
STATUS_TOO_MANY_OPENED_FILES = 0xC000011F,
STATUS_CANNOT_DELETE = 0xC0000121,
STATUS_FILE_CLOSED = 0xC0000128,
STATUS_LOGON_TYPE_NOT_GRANTED = 0xC000015B,
STATUS_ACCOUNT_EXPIRED = 0xC0000193,
STATUS_FS_DRIVER_REQUIRED = 0xC000019C,
STATUS_USER_SESSION_DELETED = 0xC0000203,
STATUS_INSUFF_SERVER_RESOURCES = 0xC0000205,
STATUS_ACCOUNT_LOCKED_OUT = 0xC0000234,
STATUS_PASSWORD_MUST_CHANGE = 0xC0000244,
STATUS_INVALID_SMB = 0x00010002, // SMB1/CIFS: A corrupt or invalid SMB request was received
STATUS_SMB_BAD_COMMAND = 0x00160002, // SMB1/CIFS: An unknown SMB command code was received by the server

View file

@ -10,10 +10,17 @@ namespace SMBLibrary
ERROR_DISK_FULL = 0x0070,
ERROR_DIR_NOT_EMPTY = 0x0091,
ERROR_ALREADY_EXISTS = 0x00B7,
ERROR_NO_TOKEN = 0x03F0,
ERROR_LOGON_FAILURE = 0x052E,
ERROR_ACCOUNT_RESTRICTION = 0x052F,
ERROR_INVALID_LOGON_HOURS = 0x0530,
ERROR_INVALID_WORKSTATION = 0x0531,
ERROR_PASSWORD_EXPIRED = 0x0532,
ERROR_ACCOUNT_DISABLED = 0x0533,
ERROR_LOGON_TYPE_NOT_GRANTED = 0x0569,
ERROR_ACCOUNT_EXPIRED = 0x0701,
ERROR_PASSWORD_MUST_CHANGE = 0x0773,
ERROR_ACCOUNT_LOCKED_OUT = 0x0775,
NERR_NetNameNotFound = 0x0906,
}
}

View file

@ -31,6 +31,7 @@
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="Authentication\GSSAPI\Enums\GSSAttributeName.cs" />
<Compile Include="Authentication\GSSAPI\GSSAPIHelper.cs" />
<Compile Include="Authentication\GSSAPI\SPNEGO\DerEncodingHelper.cs" />
<Compile Include="Authentication\GSSAPI\SPNEGO\SimpleProtectedNegotiationToken.cs" />
@ -40,6 +41,8 @@
<Compile Include="Authentication\NTLM\Helpers\AVPairUtils.cs" />
<Compile Include="Authentication\NTLM\Helpers\MD4.cs" />
<Compile Include="Authentication\NTLM\Helpers\NTLMCryptography.cs" />
<Compile Include="Authentication\NTLM\IndependentNTLMAuthenticationProvider.cs" />
<Compile Include="Authentication\NTLM\NTLMAuthenticationProviderBase.cs" />
<Compile Include="Authentication\NTLM\Structures\AuthenticateMessage.cs" />
<Compile Include="Authentication\NTLM\Structures\ChallengeMessage.cs" />
<Compile Include="Authentication\NTLM\Structures\Enums\AVPairKey.cs" />
@ -181,12 +184,10 @@
<Compile Include="Server\ConnectionState\SMB1Session.cs" />
<Compile Include="Server\ConnectionState\SMB2ConnectionState.cs" />
<Compile Include="Server\ConnectionState\SMB2Session.cs" />
<Compile Include="Server\Exceptions\EmptyPasswordNotAllowedException.cs" />
<Compile Include="Server\Exceptions\InvalidRequestException.cs" />
<Compile Include="Server\Exceptions\UnsupportedInformationLevelException.cs" />
<Compile Include="Server\Helpers\LogonHelper.cs" />
<Compile Include="Server\Helpers\ServerPathUtils.cs" />
<Compile Include="Server\IndependentUserCollection.cs" />
<Compile Include="Server\INTLMAuthenticationProvider.cs" />
<Compile Include="Server\NameServer.cs" />
<Compile Include="Server\Shares\FileSystemShare.cs" />
<Compile Include="Server\Shares\ISMBShare.cs" />
@ -222,8 +223,6 @@
<Compile Include="Server\SMBServer.cs" />
<Compile Include="Server\SMBServer.SMB1.cs" />
<Compile Include="Server\SMBServer.SMB2.cs" />
<Compile Include="Server\User.cs" />
<Compile Include="Server\UserCollection.cs" />
<Compile Include="Services\Enums\PlatformName.cs" />
<Compile Include="Services\RemoteService.cs" />
<Compile Include="Services\RemoteServiceHelper.cs" />
@ -532,12 +531,12 @@
<Compile Include="Utilities\LogEntry.cs" />
<Compile Include="Utilities\PrefetchedStream.cs" />
<Compile Include="Utilities\SocketUtils.cs" />
<Compile Include="Win32\IntegratedNTLMAuthenticationProvider.cs" />
<Compile Include="Win32\Security\LoginAPI.cs" />
<Compile Include="Win32\Security\NetworkAPI.cs" />
<Compile Include="Win32\Security\SSPIHelper.cs" />
<Compile Include="Win32\Security\Structures\SecBuffer.cs" />
<Compile Include="Win32\Security\Structures\SecBufferDesc.cs" />
<Compile Include="Win32\Win32UserCollection.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Readme.txt" />

View file

@ -30,6 +30,7 @@ namespace SMBLibrary.Server
public NBTConnectionReceiveBuffer ReceiveBuffer;
protected LogDelegate LogToServerHandler;
public SMBDialect ServerDialect;
public object AuthenticationContext;
public ConnectionState(LogDelegate logToServerHandler)
{

View file

@ -1,23 +0,0 @@
/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
*
* You can redistribute this program and/or modify it under the terms of
* the GNU Lesser Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace SMBLibrary.Server
{
public class EmptyPasswordNotAllowedException : Exception
{
public EmptyPasswordNotAllowedException() : base()
{
}
public EmptyPasswordNotAllowedException(string message) : base(message)
{
}
}
}

View file

@ -0,0 +1,42 @@
/* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
*
* You can redistribute this program and/or modify it under the terms of
* the GNU Lesser Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*/
using System;
using System.Collections.Generic;
using Utilities;
namespace SMBLibrary.Server
{
public class LogonHelper
{
public static NTStatus ToNTStatus(Win32Error errorCode)
{
switch (errorCode)
{
case Win32Error.ERROR_ACCOUNT_RESTRICTION:
return NTStatus.STATUS_ACCOUNT_RESTRICTION;
case Win32Error.ERROR_INVALID_LOGON_HOURS:
return NTStatus.STATUS_INVALID_LOGON_HOURS;
case Win32Error.ERROR_INVALID_WORKSTATION:
return NTStatus.STATUS_INVALID_WORKSTATION;
case Win32Error.ERROR_PASSWORD_EXPIRED:
return NTStatus.STATUS_PASSWORD_EXPIRED;
case Win32Error.ERROR_ACCOUNT_DISABLED:
return NTStatus.STATUS_ACCOUNT_DISABLED;
case Win32Error.ERROR_LOGON_TYPE_NOT_GRANTED:
return NTStatus.STATUS_LOGON_TYPE_NOT_GRANTED;
case Win32Error.ERROR_ACCOUNT_EXPIRED:
return NTStatus.STATUS_ACCOUNT_EXPIRED;
case Win32Error.ERROR_PASSWORD_MUST_CHANGE:
return NTStatus.STATUS_PASSWORD_MUST_CHANGE;
case Win32Error.ERROR_ACCOUNT_LOCKED_OUT:
return NTStatus.STATUS_ACCOUNT_LOCKED_OUT;
default:
return NTStatus.STATUS_LOGON_FAILURE;
}
}
}
}

View file

@ -1,33 +0,0 @@
/* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
*
* You can redistribute this program and/or modify it under the terms of
* the GNU Lesser Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*/
using System;
using System.Collections.Generic;
using System.Text;
using SMBLibrary.Authentication.NTLM;
namespace SMBLibrary.Server
{
public interface INTLMAuthenticationProvider
{
ChallengeMessage GetChallengeMessage(NegotiateMessage negotiateMessage);
bool Authenticate(AuthenticateMessage authenticateMessage);
/// <summary>
/// Permit access to this user via the guest user account if the normal authentication process fails.
/// </summary>
/// <remarks>
/// Windows will permit fallback when these conditions are met:
/// 1. The guest user account is enabled.
/// 2. The guest user account does not have a password set.
/// 3. The specified account does not exist.
/// OR:
/// The password is correct but 'limitblankpassworduse' is set to 1 (logon over a network is disabled for accounts without a password).
/// </remarks>
bool FallbackToGuest(string userName);
}
}

View file

@ -1,206 +0,0 @@
/* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
*
* You can redistribute this program and/or modify it under the terms of
* the GNU Lesser Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*/
using System;
using System.Collections.Generic;
using System.Text;
using Utilities;
using SMBLibrary.Authentication;
using SMBLibrary.Authentication.NTLM;
namespace SMBLibrary.Server
{
public class IndependentUserCollection : UserCollection, INTLMAuthenticationProvider
{
private byte[] m_serverChallenge = new byte[8];
public IndependentUserCollection()
{
}
public IndependentUserCollection(UserCollection users)
{
this.AddRange(users);
}
/// <summary>
/// LM v1 / NTLM v1
/// </summary>
private User AuthenticateV1(string accountNameToAuth, byte[] serverChallenge, byte[] lmResponse, byte[] ntResponse)
{
for (int index = 0; index < this.Count; index++)
{
string accountName = this[index].AccountName;
string password = this[index].Password;
if (String.Equals(accountName, accountNameToAuth, StringComparison.InvariantCultureIgnoreCase))
{
byte[] expectedLMResponse = NTLMCryptography.ComputeLMv1Response(serverChallenge, password);
if (ByteUtils.AreByteArraysEqual(expectedLMResponse, lmResponse))
{
return this[index];
}
byte[] expectedNTResponse = NTLMCryptography.ComputeNTLMv1Response(serverChallenge, password);
if (ByteUtils.AreByteArraysEqual(expectedNTResponse, ntResponse))
{
return this[index];
}
}
}
return null;
}
/// <summary>
/// LM v1 / NTLM v1 Extended Security
/// </summary>
private User AuthenticateV1Extended(string accountNameToAuth, byte[] serverChallenge, byte[] lmResponse, byte[] ntResponse)
{
for (int index = 0; index < this.Count; index++)
{
string accountName = this[index].AccountName;
string password = this[index].Password;
if (String.Equals(accountName, accountNameToAuth, StringComparison.InvariantCultureIgnoreCase))
{
byte[] clientChallenge = ByteReader.ReadBytes(lmResponse, 0, 8);
byte[] expectedNTLMv1Response = NTLMCryptography.ComputeNTLMv1ExtendedSecurityResponse(serverChallenge, clientChallenge, password);
if (ByteUtils.AreByteArraysEqual(expectedNTLMv1Response, ntResponse))
{
return this[index];
}
}
}
return null;
}
/// <summary>
/// LM v2 / NTLM v2
/// </summary>
private User AuthenticateV2(string domainNameToAuth, string accountNameToAuth, byte[] serverChallenge, byte[] lmResponse, byte[] ntResponse)
{
for (int index = 0; index < this.Count; index++)
{
string accountName = this[index].AccountName;
string password = this[index].Password;
if (String.Equals(accountName, accountNameToAuth, StringComparison.InvariantCultureIgnoreCase))
{
byte[] _LMv2ClientChallenge = ByteReader.ReadBytes(lmResponse, 16, 8);
byte[] expectedLMv2Response = NTLMCryptography.ComputeLMv2Response(serverChallenge, _LMv2ClientChallenge, password, accountName, domainNameToAuth);
if (ByteUtils.AreByteArraysEqual(expectedLMv2Response, lmResponse))
{
return this[index];
}
if (AuthenticationMessageUtils.IsNTLMv2NTResponse(ntResponse))
{
byte[] clientNTProof = ByteReader.ReadBytes(ntResponse, 0, 16);
byte[] clientChallengeStructurePadded = ByteReader.ReadBytes(ntResponse, 16, ntResponse.Length - 16);
byte[] expectedNTProof = NTLMCryptography.ComputeNTLMv2Proof(serverChallenge, clientChallengeStructurePadded, password, accountName, domainNameToAuth);
if (ByteUtils.AreByteArraysEqual(clientNTProof, expectedNTProof))
{
return this[index];
}
}
}
}
return null;
}
private byte[] GenerateServerChallenge()
{
new Random().NextBytes(m_serverChallenge);
return m_serverChallenge;
}
public ChallengeMessage GetChallengeMessage(NegotiateMessage negotiateMessage)
{
byte[] serverChallenge = GenerateServerChallenge();
ChallengeMessage message = new ChallengeMessage();
message.NegotiateFlags = NegotiateFlags.UnicodeEncoding |
NegotiateFlags.TargetNameSupplied |
NegotiateFlags.NTLMKey |
NegotiateFlags.TargetTypeServer |
NegotiateFlags.ExtendedSecurity |
NegotiateFlags.TargetInfo |
NegotiateFlags.Version;
if ((negotiateMessage.NegotiateFlags & NegotiateFlags.Sign) > 0)
{
// [MS-NLMP] If the client sends NTLMSSP_NEGOTIATE_SIGN to the server in the NEGOTIATE_MESSAGE,
// the server MUST return NTLMSSP_NEGOTIATE_SIGN to the client in the CHALLENGE_MESSAGE.
message.NegotiateFlags |= NegotiateFlags.Sign;
}
if ((negotiateMessage.NegotiateFlags & NegotiateFlags.Use56BitEncryption) > 0)
{
message.NegotiateFlags |= NegotiateFlags.Use56BitEncryption;
}
if ((negotiateMessage.NegotiateFlags & NegotiateFlags.Use128BitEncryption) > 0)
{
message.NegotiateFlags |= NegotiateFlags.Use128BitEncryption;
}
message.TargetName = Environment.MachineName;
message.ServerChallenge = serverChallenge;
message.TargetInfo = AVPairUtils.GetAVPairSequence(Environment.MachineName, Environment.MachineName);
message.Version = NTLMVersion.Server2003;
return message;
}
public bool Authenticate(AuthenticateMessage message)
{
if ((message.NegotiateFlags & NegotiateFlags.Anonymous) > 0)
{
return this.EnableGuestLogin;
}
User user;
if ((message.NegotiateFlags & NegotiateFlags.ExtendedSecurity) > 0)
{
if (AuthenticationMessageUtils.IsNTLMv1ExtendedSecurity(message.LmChallengeResponse))
{
// NTLM v1 Extended Security:
user = AuthenticateV1Extended(message.UserName, m_serverChallenge, message.LmChallengeResponse, message.NtChallengeResponse);
}
else
{
// NTLM v2:
user = AuthenticateV2(message.DomainName, message.UserName, m_serverChallenge, message.LmChallengeResponse, message.NtChallengeResponse);
}
}
else
{
user = AuthenticateV1(message.UserName, m_serverChallenge, message.LmChallengeResponse, message.NtChallengeResponse);
}
return (user != null);
}
public bool FallbackToGuest(string userName)
{
return (EnableGuestLogin && (IndexOf(userName) == -1));
}
private bool EnableGuestLogin
{
get
{
int index = IndexOf("Guest");
return (index >= 0 && this[index].Password == String.Empty);
}
}
public byte[] ServerChallenge
{
get
{
return m_serverChallenge;
}
}
}
}

View file

@ -19,7 +19,7 @@ namespace SMBLibrary.Server.SMB1
/// </summary>
public class NegotiateHelper
{
internal static NegotiateResponseNTLM GetNegotiateResponse(SMB1Header header, NegotiateRequest request, INTLMAuthenticationProvider users)
internal static NegotiateResponseNTLM GetNegotiateResponse(SMB1Header header, NegotiateRequest request, NTLMAuthenticationProviderBase securityProvider, ConnectionState state)
{
NegotiateResponseNTLM response = new NegotiateResponseNTLM();
@ -38,8 +38,13 @@ namespace SMBLibrary.Server.SMB1
ServerCapabilities.LargeWrite;
response.SystemTime = DateTime.UtcNow;
response.ServerTimeZone = (short)-TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).TotalMinutes;
ChallengeMessage challengeMessage = users.GetChallengeMessage(CreateNegotiateMessage());
NegotiateMessage negotiateMessage = CreateNegotiateMessage();
ChallengeMessage challengeMessage;
Win32Error status = securityProvider.GetChallengeMessage(out state.AuthenticationContext, negotiateMessage, out challengeMessage);
if (status == Win32Error.ERROR_SUCCESS)
{
response.Challenge = challengeMessage.ServerChallenge;
}
response.DomainName = String.Empty;
response.ServerName = String.Empty;

View file

@ -19,56 +19,43 @@ namespace SMBLibrary.Server.SMB1
/// </summary>
public class SessionSetupHelper
{
internal static SMB1Command GetSessionSetupResponse(SMB1Header header, SessionSetupAndXRequest request, INTLMAuthenticationProvider users, SMB1ConnectionState state)
internal static SMB1Command GetSessionSetupResponse(SMB1Header header, SessionSetupAndXRequest request, NTLMAuthenticationProviderBase securityProvider, SMB1ConnectionState state)
{
SessionSetupAndXResponse response = new SessionSetupAndXResponse();
// 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.
// See: http://msdn.microsoft.com/en-us/library/windows/desktop/aa378749%28v=vs.85%29.aspx
AuthenticateMessage message = CreateAuthenticateMessage(request.AccountName, request.OEMPassword, request.UnicodePassword);
bool loginSuccess;
try
Win32Error loginStatus = securityProvider.Authenticate(state.AuthenticationContext, message);
if (loginStatus != Win32Error.ERROR_SUCCESS)
{
loginSuccess = users.Authenticate(message);
}
catch (EmptyPasswordNotAllowedException)
{
state.LogToServer(Severity.Information, "User '{0}' authentication using an empty password was rejected", message.UserName);
header.Status = NTStatus.STATUS_ACCOUNT_RESTRICTION;
state.LogToServer(Severity.Information, "User '{0}' failed authentication. Win32 error: {1}", message.UserName, loginStatus);
header.Status = LogonHelper.ToNTStatus(loginStatus);
return new ErrorResponse(request.CommandName);
}
if (loginSuccess)
bool? isGuest = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.IsGuest) as bool?;
SMB1Session session;
if (!isGuest.HasValue || !isGuest.Value)
{
state.LogToServer(Severity.Information, "User '{0}' authenticated successfully", message.UserName);
SMB1Session session = state.CreateSession(message.UserName, message.WorkStation);
if (session == null)
{
header.Status = NTStatus.STATUS_TOO_MANY_SESSIONS;
return new ErrorResponse(request.CommandName);
}
header.UID = session.UserID;
response.PrimaryDomain = request.PrimaryDomain;
}
else if (users.FallbackToGuest(message.UserName))
{
state.LogToServer(Severity.Information, "User '{0}' failed authentication. logged in as guest", message.UserName);
SMB1Session session = state.CreateSession("Guest", message.WorkStation);
if (session == null)
{
header.Status = NTStatus.STATUS_TOO_MANY_SESSIONS;
return new ErrorResponse(request.CommandName);
}
header.UID = session.UserID;
response.Action = SessionSetupAction.SetupGuest;
response.PrimaryDomain = request.PrimaryDomain;
session = state.CreateSession(message.UserName, message.WorkStation);
}
else
{
state.LogToServer(Severity.Information, "User '{0}' failed authentication", message.UserName);
header.Status = NTStatus.STATUS_LOGON_FAILURE;
state.LogToServer(Severity.Information, "User '{0}' failed authentication. logged in as guest", message.UserName);
session = state.CreateSession("Guest", message.WorkStation);
response.Action = SessionSetupAction.SetupGuest;
}
if (session == null)
{
header.Status = NTStatus.STATUS_TOO_MANY_SESSIONS;
return new ErrorResponse(request.CommandName);
}
header.UID = session.UserID;
response.PrimaryDomain = request.PrimaryDomain;
if ((request.Capabilities & ServerCapabilities.LargeRead) > 0)
{
state.LargeRead = true;
@ -83,7 +70,7 @@ namespace SMBLibrary.Server.SMB1
return response;
}
internal static SMB1Command GetSessionSetupResponseExtended(SMB1Header header, SessionSetupAndXRequestExtended request, INTLMAuthenticationProvider users, SMB1ConnectionState state)
internal static SMB1Command GetSessionSetupResponseExtended(SMB1Header header, SessionSetupAndXRequestExtended request, NTLMAuthenticationProviderBase securityProvider, SMB1ConnectionState state)
{
SessionSetupAndXResponseExtended response = new SessionSetupAndXResponseExtended();
@ -117,7 +104,14 @@ namespace SMBLibrary.Server.SMB1
if (messageType == MessageTypeName.Negotiate)
{
NegotiateMessage negotiateMessage = new NegotiateMessage(messageBytes);
ChallengeMessage challengeMessage = users.GetChallengeMessage(negotiateMessage);
ChallengeMessage challengeMessage;
Win32Error status = securityProvider.GetChallengeMessage(out state.AuthenticationContext, negotiateMessage, out challengeMessage);
if (status != Win32Error.ERROR_SUCCESS)
{
header.Status = NTStatus.STATUS_LOGON_FAILURE;
return new ErrorResponse(request.CommandName);
}
if (isRawMessage)
{
response.SecurityBlob = challengeMessage.GetBytes();
@ -131,35 +125,26 @@ namespace SMBLibrary.Server.SMB1
else // MessageTypeName.Authenticate
{
AuthenticateMessage authenticateMessage = new AuthenticateMessage(messageBytes);
bool loginSuccess;
try
Win32Error loginStatus = securityProvider.Authenticate(state.AuthenticationContext, authenticateMessage);
if (loginStatus != Win32Error.ERROR_SUCCESS)
{
loginSuccess = users.Authenticate(authenticateMessage);
}
catch (EmptyPasswordNotAllowedException)
{
state.LogToServer(Severity.Information, "User '{0}' authentication using an empty password was rejected", authenticateMessage.UserName);
header.Status = NTStatus.STATUS_ACCOUNT_RESTRICTION;
state.LogToServer(Severity.Information, "User '{0}' failed authentication. Win32 error: {0}", authenticateMessage.UserName, loginStatus);
header.Status = LogonHelper.ToNTStatus(loginStatus);
return new ErrorResponse(request.CommandName);
}
if (loginSuccess)
bool? isGuest = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.IsGuest) as bool?;
if (!isGuest.HasValue || !isGuest.Value)
{
state.LogToServer(Severity.Information, "User '{0}' authenticated successfully", authenticateMessage.UserName);
state.CreateSession(header.UID, authenticateMessage.UserName, authenticateMessage.WorkStation);
}
else if (users.FallbackToGuest(authenticateMessage.UserName))
else
{
state.LogToServer(Severity.Information, "User '{0}' failed authentication. logged in as guest", authenticateMessage.UserName);
state.CreateSession(header.UID, "Guest", authenticateMessage.WorkStation);
response.Action = SessionSetupAction.SetupGuest;
}
else
{
state.LogToServer(Severity.Information, "User '{0}' failed authentication", authenticateMessage.UserName);
header.Status = NTStatus.STATUS_LOGON_FAILURE;
return new ErrorResponse(request.CommandName);
}
if (!isRawMessage)
{

View file

@ -18,7 +18,7 @@ namespace SMBLibrary.Server.SMB2
/// </summary>
public class SessionSetupHelper
{
internal static SMB2Command GetSessionSetupResponse(SessionSetupRequest request, INTLMAuthenticationProvider users, SMB2ConnectionState state)
internal static SMB2Command GetSessionSetupResponse(SessionSetupRequest request, NTLMAuthenticationProviderBase securityProvider, SMB2ConnectionState state)
{
// [MS-SMB2] Windows [..] will also accept raw Kerberos messages and implicit NTLM messages as part of GSS authentication.
SessionSetupResponse response = new SessionSetupResponse();
@ -49,7 +49,13 @@ namespace SMBLibrary.Server.SMB2
if (messageType == MessageTypeName.Negotiate)
{
NegotiateMessage negotiateMessage = new NegotiateMessage(messageBytes);
ChallengeMessage challengeMessage = users.GetChallengeMessage(negotiateMessage);
ChallengeMessage challengeMessage;
Win32Error status = securityProvider.GetChallengeMessage(out state.AuthenticationContext, negotiateMessage, out challengeMessage);
if (status != Win32Error.ERROR_SUCCESS)
{
return new ErrorResponse(request.CommandName, NTStatus.STATUS_LOGON_FAILURE);
}
if (isRawMessage)
{
response.SecurityBuffer = challengeMessage.GetBytes();
@ -63,33 +69,26 @@ namespace SMBLibrary.Server.SMB2
else // MessageTypeName.Authenticate
{
AuthenticateMessage authenticateMessage = new AuthenticateMessage(messageBytes);
bool loginSuccess;
try
Win32Error loginStatus = securityProvider.Authenticate(state.AuthenticationContext, authenticateMessage);
if (loginStatus != Win32Error.ERROR_SUCCESS)
{
loginSuccess = users.Authenticate(authenticateMessage);
}
catch (EmptyPasswordNotAllowedException)
{
state.LogToServer(Severity.Information, "User '{0}' authentication using an empty password was rejected", authenticateMessage.UserName);
return new ErrorResponse(request.CommandName, NTStatus.STATUS_ACCOUNT_RESTRICTION);
state.LogToServer(Severity.Information, "User '{0}' failed authentication. Win32 error: {1}", authenticateMessage.UserName, loginStatus);
NTStatus status = LogonHelper.ToNTStatus(loginStatus);
return new ErrorResponse(request.CommandName, status);
}
if (loginSuccess)
bool? isGuest = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.IsGuest) as bool?;
if (!isGuest.HasValue || !isGuest.Value)
{
state.LogToServer(Severity.Information, "User '{0}' authenticated successfully", authenticateMessage.UserName);
state.CreateSession(request.Header.SessionID, authenticateMessage.UserName, authenticateMessage.WorkStation);
}
else if (users.FallbackToGuest(authenticateMessage.UserName))
else
{
state.LogToServer(Severity.Information, "User '{0}' failed authentication. logged in as guest", authenticateMessage.UserName);
state.CreateSession(request.Header.SessionID, "Guest", authenticateMessage.WorkStation);
response.SessionFlags = SessionFlags.IsGuest;
}
else
{
state.LogToServer(Severity.Information, "User '{0}' failed authentication", authenticateMessage.UserName);
return new ErrorResponse(request.CommandName, NTStatus.STATUS_LOGON_FAILURE);
}
if (!isRawMessage)
{

View file

@ -68,7 +68,7 @@ namespace SMBLibrary.Server
}
else
{
return NegotiateHelper.GetNegotiateResponse(header, request, m_users);
return NegotiateHelper.GetNegotiateResponse(header, request, m_securityProvider, state);
}
}
else
@ -102,13 +102,13 @@ namespace SMBLibrary.Server
{
SessionSetupAndXRequest request = (SessionSetupAndXRequest)command;
state.MaxBufferSize = request.MaxBufferSize;
return SessionSetupHelper.GetSessionSetupResponse(header, request, m_users, state);
return SessionSetupHelper.GetSessionSetupResponse(header, request, m_securityProvider, state);
}
else if (command is SessionSetupAndXRequestExtended)
{
SessionSetupAndXRequestExtended request = (SessionSetupAndXRequestExtended)command;
state.MaxBufferSize = request.MaxBufferSize;
return SessionSetupHelper.GetSessionSetupResponseExtended(header, request, m_users, state);
return SessionSetupHelper.GetSessionSetupResponseExtended(header, request, m_securityProvider, state);
}
else if (command is EchoRequest)
{

View file

@ -109,7 +109,7 @@ namespace SMBLibrary.Server
{
if (command is SessionSetupRequest)
{
return SessionSetupHelper.GetSessionSetupResponse((SessionSetupRequest)command, m_users, state);
return SessionSetupHelper.GetSessionSetupResponse((SessionSetupRequest)command, m_securityProvider, state);
}
else if (command is EchoRequest)
{

View file

@ -8,6 +8,7 @@ using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using SMBLibrary.Authentication.NTLM;
using SMBLibrary.NetBios;
using SMBLibrary.Services;
using SMBLibrary.SMB1;
@ -24,7 +25,7 @@ namespace SMBLibrary.Server
public const bool EnableExtendedSecurity = true;
private ShareCollection m_shares; // e.g. Shared folders
private INTLMAuthenticationProvider m_users;
private NTLMAuthenticationProviderBase m_securityProvider;
private NamedPipeShare m_services; // Named pipes
private IPAddress m_serverAddress;
private SMBTransportType m_transport;
@ -37,14 +38,14 @@ namespace SMBLibrary.Server
public event EventHandler<LogEntry> OnLogEntry;
public SMBServer(ShareCollection shares, INTLMAuthenticationProvider users, IPAddress serverAddress, SMBTransportType transport) : this(shares, users, serverAddress, transport, true, true)
public SMBServer(ShareCollection shares, NTLMAuthenticationProviderBase securityProvider, IPAddress serverAddress, SMBTransportType transport) : this(shares, securityProvider, serverAddress, transport, true, true)
{
}
public SMBServer(ShareCollection shares, INTLMAuthenticationProvider users, IPAddress serverAddress, SMBTransportType transport, bool enableSMB1, bool enableSMB2)
public SMBServer(ShareCollection shares, NTLMAuthenticationProviderBase securityProvider, IPAddress serverAddress, SMBTransportType transport, bool enableSMB1, bool enableSMB2)
{
m_shares = shares;
m_users = users;
m_securityProvider = securityProvider;
m_serverAddress = serverAddress;
m_serverGuid = Guid.NewGuid();
m_transport = transport;

View file

@ -0,0 +1,176 @@
/* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
*
* You can redistribute this program and/or modify it under the terms of
* the GNU Lesser Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*/
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Runtime.InteropServices;
using Utilities;
using SMBLibrary.Authentication.GSSAPI;
using SMBLibrary.Authentication.NTLM;
using Microsoft.Win32;
namespace SMBLibrary.Win32.Security
{
public class IntegratedNTLMAuthenticationProvider : NTLMAuthenticationProviderBase
{
public class AuthContext
{
public SecHandle ServerContext;
public string WorkStation;
public string UserName;
public byte[] SessionKey;
public bool IsGuest;
public AuthContext(SecHandle serverContext, string workStation)
{
ServerContext = serverContext;
WorkStation = workStation;
}
}
public override Win32Error GetChallengeMessage(out object context, NegotiateMessage negotiateMessage, out ChallengeMessage challengeMessage)
{
byte[] negotiateMessageBytes = negotiateMessage.GetBytes();
SecHandle serverContext;
byte[] challengeMessageBytes;
try
{
challengeMessageBytes = SSPIHelper.GetType2Message(negotiateMessageBytes, out serverContext);
}
catch (Exception)
{
context = null;
challengeMessage = null;
return (Win32Error)Marshal.GetLastWin32Error();
}
context = new AuthContext(serverContext, negotiateMessage.Workstation);
challengeMessage = new ChallengeMessage(challengeMessageBytes);
return Win32Error.ERROR_SUCCESS;
}
/// <summary>
/// Authenticate will return false when the password is correct in these cases:
/// 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>
public override Win32Error Authenticate(object context, AuthenticateMessage message)
{
AuthContext authContext = context as AuthContext;
if (authContext == null)
{
return Win32Error.ERROR_NO_TOKEN;
}
authContext.UserName = message.UserName;
authContext.SessionKey = message.EncryptedRandomSessionKey;
if ((message.NegotiateFlags & NegotiateFlags.Anonymous) > 0)
{
if (this.EnableGuestLogin)
{
authContext.IsGuest = true;
return Win32Error.ERROR_SUCCESS;
}
else
{
return Win32Error.ERROR_LOGON_FAILURE;
}
}
byte[] messageBytes = message.GetBytes();
bool success;
try
{
success = SSPIHelper.AuthenticateType3Message(authContext.ServerContext, messageBytes);
}
catch (Exception)
{
return (Win32Error)Marshal.GetLastWin32Error();
}
if (success)
{
return Win32Error.ERROR_SUCCESS;
}
else
{
Win32Error result = (Win32Error)Marshal.GetLastWin32Error();
// Windows will permit fallback when these conditions are met:
// 1. The guest user account is enabled.
// 2. The guest user account does not have a password set.
// 3. The specified account does not exist.
// OR:
// The password is correct but 'limitblankpassworduse' is set to 1 (logon over a network is disabled for accounts without a password).
bool allowFallback = (!IsUserExists(message.UserName) || result == Win32Error.ERROR_ACCOUNT_RESTRICTION);
if (allowFallback && this.EnableGuestLogin)
{
authContext.IsGuest = true;
return Win32Error.ERROR_SUCCESS;
}
else
{
return result;
}
}
}
public override void DeleteSecurityContext(ref object context)
{
AuthContext authContext = context as AuthContext;
if (authContext == null)
{
return;
}
SecHandle handle = ((AuthContext)context).ServerContext;
SSPIHelper.DeleteSecurityContext(ref handle);
}
public override object GetContextAttribute(object context, GSSAttributeName attributeName)
{
AuthContext authContext = context as AuthContext;
if (authContext != null)
{
switch (attributeName)
{
case GSSAttributeName.AccessToken:
return authContext.ServerContext;
case GSSAttributeName.IsGuest:
return authContext.IsGuest;
case GSSAttributeName.MachineName:
return authContext.WorkStation;
case GSSAttributeName.SessionKey:
return authContext.SessionKey;
case GSSAttributeName.UserName:
return authContext.UserName;
}
}
return null;
}
/// <summary>
/// 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>
private bool EnableGuestLogin
{
get
{
return LoginAPI.ValidateUserPassword("Guest", String.Empty, LogonType.Network);
}
}
public static bool IsUserExists(string userName)
{
return NetworkAPI.IsUserExists(userName);
}
}
}

View file

@ -1,175 +0,0 @@
/* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
*
* You can redistribute this program and/or modify it under the terms of
* the GNU Lesser Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*/
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using Utilities;
using SMBLibrary.Authentication.NTLM;
using SMBLibrary.Win32.Security;
using Microsoft.Win32;
namespace SMBLibrary.Server.Win32
{
public class Win32UserCollection : UserCollection, INTLMAuthenticationProvider
{
private SecHandle m_serverContext;
private byte[] m_serverChallenge = new byte[8];
public Win32UserCollection()
{
List<string> users = NetworkAPI.EnumerateNetworkUsers();
foreach (string user in users)
{
this.Add(new User(user, String.Empty));
}
}
public ChallengeMessage GetChallengeMessage(NegotiateMessage negotiateMessage)
{
byte[] negotiateMessageBytes = negotiateMessage.GetBytes();
byte[] challengeMessageBytes = SSPIHelper.GetType2Message(negotiateMessageBytes, out m_serverContext);
ChallengeMessage challengeMessage = new ChallengeMessage(challengeMessageBytes);
m_serverChallenge = challengeMessage.ServerChallenge;
return challengeMessage;
}
/// <summary>
/// Authenticate will return false when the password is correct in these cases:
/// 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>
public bool Authenticate(AuthenticateMessage message)
{
if ((message.NegotiateFlags & NegotiateFlags.Anonymous) > 0)
{
return this.EnableGuestLogin;
}
// AuthenticateType3Message is not reliable when 'limitblankpassworduse' is set to 1 and the user has an empty password set.
// Note: Windows LogonUser API calls will be listed in the security event log.
if (!AreEmptyPasswordsAllowed() &&
IsPasswordEmpty(message) &&
LoginAPI.HasEmptyPassword(message.UserName))
{
if (FallbackToGuest(message.UserName))
{
return false;
}
else
{
throw new EmptyPasswordNotAllowedException();
}
}
byte[] messageBytes = message.GetBytes();
try
{
return SSPIHelper.AuthenticateType3Message(m_serverContext, messageBytes);
}
catch (Exception)
{
return false;
}
}
public bool IsPasswordEmpty(AuthenticateMessage message)
{
// See [MS-NLMP] 3.3.1 - NTLM v1 Authentication
// Special case for anonymous authentication:
if (message.LmChallengeResponse.Length == 1 || message.NtChallengeResponse.Length == 0)
{
return true;
}
if ((message.NegotiateFlags & NegotiateFlags.ExtendedSecurity) > 0)
{
if (AuthenticationMessageUtils.IsNTLMv1ExtendedSecurity(message.LmChallengeResponse))
{
// NTLM v1 extended security:
byte[] clientChallenge = ByteReader.ReadBytes(message.LmChallengeResponse, 0, 8);
byte[] emptyPasswordNTLMv1Response = NTLMCryptography.ComputeNTLMv1ExtendedSecurityResponse(m_serverChallenge, clientChallenge, String.Empty);
if (ByteUtils.AreByteArraysEqual(emptyPasswordNTLMv1Response, message.NtChallengeResponse))
{
return true;
}
}
else
{
// NTLM v2:
byte[] _LMv2ClientChallenge = ByteReader.ReadBytes(message.LmChallengeResponse, 16, 8);
byte[] emptyPasswordLMv2Response = NTLMCryptography.ComputeLMv2Response(m_serverChallenge, _LMv2ClientChallenge, String.Empty, message.UserName, message.DomainName);
if (ByteUtils.AreByteArraysEqual(emptyPasswordLMv2Response, message.LmChallengeResponse))
{
return true;
}
if (AuthenticationMessageUtils.IsNTLMv2NTResponse(message.NtChallengeResponse))
{
byte[] clientNTProof = ByteReader.ReadBytes(message.NtChallengeResponse, 0, 16);
byte[] clientChallengeStructurePadded = ByteReader.ReadBytes(message.NtChallengeResponse, 16, message.NtChallengeResponse.Length - 16);
byte[] emptyPasswordNTProof = NTLMCryptography.ComputeNTLMv2Proof(m_serverChallenge, clientChallengeStructurePadded, String.Empty, message.UserName, message.DomainName);
if (ByteUtils.AreByteArraysEqual(clientNTProof, emptyPasswordNTProof))
{
return true;
}
}
}
}
else
{
// NTLM v1:
byte[] emptyPasswordLMv1Response = NTLMCryptography.ComputeLMv1Response(m_serverChallenge, String.Empty);
if (ByteUtils.AreByteArraysEqual(emptyPasswordLMv1Response, message.LmChallengeResponse))
{
return true;
}
byte[] emptyPasswordNTLMv1Response = NTLMCryptography.ComputeNTLMv1Response(m_serverChallenge, String.Empty);
if (ByteUtils.AreByteArraysEqual(emptyPasswordNTLMv1Response, message.NtChallengeResponse))
{
return true;
}
}
return false;
}
public bool FallbackToGuest(string userName)
{
return (EnableGuestLogin && (IndexOf(userName) == -1));
}
/// <summary>
/// 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>
private bool EnableGuestLogin
{
get
{
return LoginAPI.ValidateUserPassword("Guest", String.Empty, LogonType.Network);
}
}
public static bool AreEmptyPasswordsAllowed()
{
RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\Lsa");
object value = key.GetValue("limitblankpassworduse", 1);
if (value is int)
{
if ((int)value != 0)
{
return false;
}
}
return true;
}
}
}

View file

@ -70,6 +70,8 @@
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<Compile Include="User.cs" />
<Compile Include="UserCollection.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SMBLibrary\SMBLibrary.csproj">

View file

@ -17,8 +17,9 @@ using System.Text;
using System.Windows.Forms;
using System.Xml;
using SMBLibrary;
using SMBLibrary.Authentication.NTLM;
using SMBLibrary.Server;
using SMBLibrary.Server.Win32;
using SMBLibrary.Win32.Security;
using Utilities;
namespace SMBServer
@ -61,10 +62,10 @@ namespace SMBServer
transportType = SMBTransportType.DirectTCPTransport;
}
INTLMAuthenticationProvider provider;
NTLMAuthenticationProviderBase provider;
if (chkIntegratedWindowsAuthentication.Checked)
{
provider = new Win32UserCollection();
provider = new IntegratedNTLMAuthenticationProvider();
}
else
@ -80,7 +81,7 @@ namespace SMBServer
return;
}
provider = new IndependentUserCollection(users);
provider = new IndependentNTLMAuthenticationProvider(users.GetUserPassword);
}

View file

@ -29,6 +29,16 @@ namespace SMBLibrary.Server
return -1;
}
public string GetUserPassword(string accountName)
{
int index = IndexOf(accountName);
if (index >= 0)
{
return this[index].Password;
}
return null;
}
public List<string> ListUsers()
{
List<string> result = new List<string>();