mirror of
https://github.com/TalAloni/SMBLibrary.git
synced 2025-05-15 17:06:59 +02:00
Improved NTLM authentication API
This commit is contained in:
parent
05f49c3128
commit
217451d18f
23 changed files with 602 additions and 526 deletions
18
SMBLibrary/Authentication/GSSAPI/Enums/GSSAttributeName.cs
Normal file
18
SMBLibrary/Authentication/GSSAPI/Enums/GSSAttributeName.cs
Normal 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,
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -30,6 +30,7 @@ namespace SMBLibrary.Server
|
|||
public NBTConnectionReceiveBuffer ReceiveBuffer;
|
||||
protected LogDelegate LogToServerHandler;
|
||||
public SMBDialect ServerDialect;
|
||||
public object AuthenticationContext;
|
||||
|
||||
public ConnectionState(LogDelegate logToServerHandler)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
42
SMBLibrary/Server/Helpers/LogonHelper.cs
Normal file
42
SMBLibrary/Server/Helpers/LogonHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
response.Challenge = challengeMessage.ServerChallenge;
|
||||
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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
176
SMBLibrary/Win32/IntegratedNTLMAuthenticationProvider.cs
Normal file
176
SMBLibrary/Win32/IntegratedNTLMAuthenticationProvider.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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">
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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>();
|
Loading…
Add table
Add a link
Reference in a new issue