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_FILE_LOCK_CONFLICT = 0xC0000054,
|
||||||
STATUS_LOGON_FAILURE = 0xC000006D, // Authentication failure.
|
STATUS_LOGON_FAILURE = 0xC000006D, // Authentication failure.
|
||||||
STATUS_ACCOUNT_RESTRICTION = 0xC000006E, // The user has an empty password, which is not allowed
|
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_DISK_FULL = 0xC000007F,
|
||||||
STATUS_INSUFFICIENT_RESOURCES = 0xC000009A,
|
STATUS_INSUFFICIENT_RESOURCES = 0xC000009A,
|
||||||
STATUS_MEDIA_WRITE_PROTECTED = 0xC00000A2,
|
STATUS_MEDIA_WRITE_PROTECTED = 0xC00000A2,
|
||||||
|
@ -36,9 +40,13 @@ namespace SMBLibrary
|
||||||
STATUS_TOO_MANY_OPENED_FILES = 0xC000011F,
|
STATUS_TOO_MANY_OPENED_FILES = 0xC000011F,
|
||||||
STATUS_CANNOT_DELETE = 0xC0000121,
|
STATUS_CANNOT_DELETE = 0xC0000121,
|
||||||
STATUS_FILE_CLOSED = 0xC0000128,
|
STATUS_FILE_CLOSED = 0xC0000128,
|
||||||
|
STATUS_LOGON_TYPE_NOT_GRANTED = 0xC000015B,
|
||||||
|
STATUS_ACCOUNT_EXPIRED = 0xC0000193,
|
||||||
STATUS_FS_DRIVER_REQUIRED = 0xC000019C,
|
STATUS_FS_DRIVER_REQUIRED = 0xC000019C,
|
||||||
STATUS_USER_SESSION_DELETED = 0xC0000203,
|
STATUS_USER_SESSION_DELETED = 0xC0000203,
|
||||||
STATUS_INSUFF_SERVER_RESOURCES = 0xC0000205,
|
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_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
|
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_DISK_FULL = 0x0070,
|
||||||
ERROR_DIR_NOT_EMPTY = 0x0091,
|
ERROR_DIR_NOT_EMPTY = 0x0091,
|
||||||
ERROR_ALREADY_EXISTS = 0x00B7,
|
ERROR_ALREADY_EXISTS = 0x00B7,
|
||||||
|
ERROR_NO_TOKEN = 0x03F0,
|
||||||
ERROR_LOGON_FAILURE = 0x052E,
|
ERROR_LOGON_FAILURE = 0x052E,
|
||||||
ERROR_ACCOUNT_RESTRICTION = 0x052F,
|
ERROR_ACCOUNT_RESTRICTION = 0x052F,
|
||||||
|
ERROR_INVALID_LOGON_HOURS = 0x0530,
|
||||||
|
ERROR_INVALID_WORKSTATION = 0x0531,
|
||||||
|
ERROR_PASSWORD_EXPIRED = 0x0532,
|
||||||
ERROR_ACCOUNT_DISABLED = 0x0533,
|
ERROR_ACCOUNT_DISABLED = 0x0533,
|
||||||
ERROR_LOGON_TYPE_NOT_GRANTED = 0x0569,
|
ERROR_LOGON_TYPE_NOT_GRANTED = 0x0569,
|
||||||
|
ERROR_ACCOUNT_EXPIRED = 0x0701,
|
||||||
|
ERROR_PASSWORD_MUST_CHANGE = 0x0773,
|
||||||
|
ERROR_ACCOUNT_LOCKED_OUT = 0x0775,
|
||||||
NERR_NetNameNotFound = 0x0906,
|
NERR_NetNameNotFound = 0x0906,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="Authentication\GSSAPI\Enums\GSSAttributeName.cs" />
|
||||||
<Compile Include="Authentication\GSSAPI\GSSAPIHelper.cs" />
|
<Compile Include="Authentication\GSSAPI\GSSAPIHelper.cs" />
|
||||||
<Compile Include="Authentication\GSSAPI\SPNEGO\DerEncodingHelper.cs" />
|
<Compile Include="Authentication\GSSAPI\SPNEGO\DerEncodingHelper.cs" />
|
||||||
<Compile Include="Authentication\GSSAPI\SPNEGO\SimpleProtectedNegotiationToken.cs" />
|
<Compile Include="Authentication\GSSAPI\SPNEGO\SimpleProtectedNegotiationToken.cs" />
|
||||||
|
@ -40,6 +41,8 @@
|
||||||
<Compile Include="Authentication\NTLM\Helpers\AVPairUtils.cs" />
|
<Compile Include="Authentication\NTLM\Helpers\AVPairUtils.cs" />
|
||||||
<Compile Include="Authentication\NTLM\Helpers\MD4.cs" />
|
<Compile Include="Authentication\NTLM\Helpers\MD4.cs" />
|
||||||
<Compile Include="Authentication\NTLM\Helpers\NTLMCryptography.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\AuthenticateMessage.cs" />
|
||||||
<Compile Include="Authentication\NTLM\Structures\ChallengeMessage.cs" />
|
<Compile Include="Authentication\NTLM\Structures\ChallengeMessage.cs" />
|
||||||
<Compile Include="Authentication\NTLM\Structures\Enums\AVPairKey.cs" />
|
<Compile Include="Authentication\NTLM\Structures\Enums\AVPairKey.cs" />
|
||||||
|
@ -181,12 +184,10 @@
|
||||||
<Compile Include="Server\ConnectionState\SMB1Session.cs" />
|
<Compile Include="Server\ConnectionState\SMB1Session.cs" />
|
||||||
<Compile Include="Server\ConnectionState\SMB2ConnectionState.cs" />
|
<Compile Include="Server\ConnectionState\SMB2ConnectionState.cs" />
|
||||||
<Compile Include="Server\ConnectionState\SMB2Session.cs" />
|
<Compile Include="Server\ConnectionState\SMB2Session.cs" />
|
||||||
<Compile Include="Server\Exceptions\EmptyPasswordNotAllowedException.cs" />
|
|
||||||
<Compile Include="Server\Exceptions\InvalidRequestException.cs" />
|
<Compile Include="Server\Exceptions\InvalidRequestException.cs" />
|
||||||
<Compile Include="Server\Exceptions\UnsupportedInformationLevelException.cs" />
|
<Compile Include="Server\Exceptions\UnsupportedInformationLevelException.cs" />
|
||||||
|
<Compile Include="Server\Helpers\LogonHelper.cs" />
|
||||||
<Compile Include="Server\Helpers\ServerPathUtils.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\NameServer.cs" />
|
||||||
<Compile Include="Server\Shares\FileSystemShare.cs" />
|
<Compile Include="Server\Shares\FileSystemShare.cs" />
|
||||||
<Compile Include="Server\Shares\ISMBShare.cs" />
|
<Compile Include="Server\Shares\ISMBShare.cs" />
|
||||||
|
@ -222,8 +223,6 @@
|
||||||
<Compile Include="Server\SMBServer.cs" />
|
<Compile Include="Server\SMBServer.cs" />
|
||||||
<Compile Include="Server\SMBServer.SMB1.cs" />
|
<Compile Include="Server\SMBServer.SMB1.cs" />
|
||||||
<Compile Include="Server\SMBServer.SMB2.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\Enums\PlatformName.cs" />
|
||||||
<Compile Include="Services\RemoteService.cs" />
|
<Compile Include="Services\RemoteService.cs" />
|
||||||
<Compile Include="Services\RemoteServiceHelper.cs" />
|
<Compile Include="Services\RemoteServiceHelper.cs" />
|
||||||
|
@ -532,12 +531,12 @@
|
||||||
<Compile Include="Utilities\LogEntry.cs" />
|
<Compile Include="Utilities\LogEntry.cs" />
|
||||||
<Compile Include="Utilities\PrefetchedStream.cs" />
|
<Compile Include="Utilities\PrefetchedStream.cs" />
|
||||||
<Compile Include="Utilities\SocketUtils.cs" />
|
<Compile Include="Utilities\SocketUtils.cs" />
|
||||||
|
<Compile Include="Win32\IntegratedNTLMAuthenticationProvider.cs" />
|
||||||
<Compile Include="Win32\Security\LoginAPI.cs" />
|
<Compile Include="Win32\Security\LoginAPI.cs" />
|
||||||
<Compile Include="Win32\Security\NetworkAPI.cs" />
|
<Compile Include="Win32\Security\NetworkAPI.cs" />
|
||||||
<Compile Include="Win32\Security\SSPIHelper.cs" />
|
<Compile Include="Win32\Security\SSPIHelper.cs" />
|
||||||
<Compile Include="Win32\Security\Structures\SecBuffer.cs" />
|
<Compile Include="Win32\Security\Structures\SecBuffer.cs" />
|
||||||
<Compile Include="Win32\Security\Structures\SecBufferDesc.cs" />
|
<Compile Include="Win32\Security\Structures\SecBufferDesc.cs" />
|
||||||
<Compile Include="Win32\Win32UserCollection.cs" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="Readme.txt" />
|
<Content Include="Readme.txt" />
|
||||||
|
|
|
@ -30,6 +30,7 @@ namespace SMBLibrary.Server
|
||||||
public NBTConnectionReceiveBuffer ReceiveBuffer;
|
public NBTConnectionReceiveBuffer ReceiveBuffer;
|
||||||
protected LogDelegate LogToServerHandler;
|
protected LogDelegate LogToServerHandler;
|
||||||
public SMBDialect ServerDialect;
|
public SMBDialect ServerDialect;
|
||||||
|
public object AuthenticationContext;
|
||||||
|
|
||||||
public ConnectionState(LogDelegate logToServerHandler)
|
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>
|
/// </summary>
|
||||||
public class NegotiateHelper
|
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();
|
NegotiateResponseNTLM response = new NegotiateResponseNTLM();
|
||||||
|
|
||||||
|
@ -38,8 +38,13 @@ namespace SMBLibrary.Server.SMB1
|
||||||
ServerCapabilities.LargeWrite;
|
ServerCapabilities.LargeWrite;
|
||||||
response.SystemTime = DateTime.UtcNow;
|
response.SystemTime = DateTime.UtcNow;
|
||||||
response.ServerTimeZone = (short)-TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).TotalMinutes;
|
response.ServerTimeZone = (short)-TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).TotalMinutes;
|
||||||
ChallengeMessage challengeMessage = users.GetChallengeMessage(CreateNegotiateMessage());
|
NegotiateMessage negotiateMessage = CreateNegotiateMessage();
|
||||||
response.Challenge = challengeMessage.ServerChallenge;
|
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.DomainName = String.Empty;
|
||||||
response.ServerName = String.Empty;
|
response.ServerName = String.Empty;
|
||||||
|
|
||||||
|
|
|
@ -19,56 +19,43 @@ namespace SMBLibrary.Server.SMB1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SessionSetupHelper
|
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();
|
SessionSetupAndXResponse response = new SessionSetupAndXResponse();
|
||||||
// The PrimaryDomain field in the request is used to determine with domain controller should authenticate the user credentials,
|
// The PrimaryDomain field in the request is used to determine with domain controller should authenticate the user credentials,
|
||||||
// However, the domain controller itself does not use this field.
|
// However, the domain controller itself does not use this field.
|
||||||
// See: http://msdn.microsoft.com/en-us/library/windows/desktop/aa378749%28v=vs.85%29.aspx
|
// See: http://msdn.microsoft.com/en-us/library/windows/desktop/aa378749%28v=vs.85%29.aspx
|
||||||
AuthenticateMessage message = CreateAuthenticateMessage(request.AccountName, request.OEMPassword, request.UnicodePassword);
|
AuthenticateMessage message = CreateAuthenticateMessage(request.AccountName, request.OEMPassword, request.UnicodePassword);
|
||||||
bool loginSuccess;
|
Win32Error loginStatus = securityProvider.Authenticate(state.AuthenticationContext, message);
|
||||||
try
|
if (loginStatus != Win32Error.ERROR_SUCCESS)
|
||||||
{
|
{
|
||||||
loginSuccess = users.Authenticate(message);
|
state.LogToServer(Severity.Information, "User '{0}' failed authentication. Win32 error: {1}", message.UserName, loginStatus);
|
||||||
}
|
header.Status = LogonHelper.ToNTStatus(loginStatus);
|
||||||
catch (EmptyPasswordNotAllowedException)
|
|
||||||
{
|
|
||||||
state.LogToServer(Severity.Information, "User '{0}' authentication using an empty password was rejected", message.UserName);
|
|
||||||
header.Status = NTStatus.STATUS_ACCOUNT_RESTRICTION;
|
|
||||||
return new ErrorResponse(request.CommandName);
|
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);
|
state.LogToServer(Severity.Information, "User '{0}' authenticated successfully", message.UserName);
|
||||||
SMB1Session session = state.CreateSession(message.UserName, message.WorkStation);
|
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;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
state.LogToServer(Severity.Information, "User '{0}' failed authentication", message.UserName);
|
state.LogToServer(Severity.Information, "User '{0}' failed authentication. logged in as guest", message.UserName);
|
||||||
header.Status = NTStatus.STATUS_LOGON_FAILURE;
|
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);
|
return new ErrorResponse(request.CommandName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header.UID = session.UserID;
|
||||||
|
response.PrimaryDomain = request.PrimaryDomain;
|
||||||
if ((request.Capabilities & ServerCapabilities.LargeRead) > 0)
|
if ((request.Capabilities & ServerCapabilities.LargeRead) > 0)
|
||||||
{
|
{
|
||||||
state.LargeRead = true;
|
state.LargeRead = true;
|
||||||
|
@ -83,7 +70,7 @@ namespace SMBLibrary.Server.SMB1
|
||||||
return response;
|
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();
|
SessionSetupAndXResponseExtended response = new SessionSetupAndXResponseExtended();
|
||||||
|
|
||||||
|
@ -117,7 +104,14 @@ namespace SMBLibrary.Server.SMB1
|
||||||
if (messageType == MessageTypeName.Negotiate)
|
if (messageType == MessageTypeName.Negotiate)
|
||||||
{
|
{
|
||||||
NegotiateMessage negotiateMessage = new NegotiateMessage(messageBytes);
|
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)
|
if (isRawMessage)
|
||||||
{
|
{
|
||||||
response.SecurityBlob = challengeMessage.GetBytes();
|
response.SecurityBlob = challengeMessage.GetBytes();
|
||||||
|
@ -131,35 +125,26 @@ namespace SMBLibrary.Server.SMB1
|
||||||
else // MessageTypeName.Authenticate
|
else // MessageTypeName.Authenticate
|
||||||
{
|
{
|
||||||
AuthenticateMessage authenticateMessage = new AuthenticateMessage(messageBytes);
|
AuthenticateMessage authenticateMessage = new AuthenticateMessage(messageBytes);
|
||||||
bool loginSuccess;
|
Win32Error loginStatus = securityProvider.Authenticate(state.AuthenticationContext, authenticateMessage);
|
||||||
try
|
if (loginStatus != Win32Error.ERROR_SUCCESS)
|
||||||
{
|
{
|
||||||
loginSuccess = users.Authenticate(authenticateMessage);
|
state.LogToServer(Severity.Information, "User '{0}' failed authentication. Win32 error: {0}", authenticateMessage.UserName, loginStatus);
|
||||||
}
|
header.Status = LogonHelper.ToNTStatus(loginStatus);
|
||||||
catch (EmptyPasswordNotAllowedException)
|
|
||||||
{
|
|
||||||
state.LogToServer(Severity.Information, "User '{0}' authentication using an empty password was rejected", authenticateMessage.UserName);
|
|
||||||
header.Status = NTStatus.STATUS_ACCOUNT_RESTRICTION;
|
|
||||||
return new ErrorResponse(request.CommandName);
|
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.LogToServer(Severity.Information, "User '{0}' authenticated successfully", authenticateMessage.UserName);
|
||||||
state.CreateSession(header.UID, authenticateMessage.UserName, authenticateMessage.WorkStation);
|
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.LogToServer(Severity.Information, "User '{0}' failed authentication. logged in as guest", authenticateMessage.UserName);
|
||||||
state.CreateSession(header.UID, "Guest", authenticateMessage.WorkStation);
|
state.CreateSession(header.UID, "Guest", authenticateMessage.WorkStation);
|
||||||
response.Action = SessionSetupAction.SetupGuest;
|
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)
|
if (!isRawMessage)
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace SMBLibrary.Server.SMB2
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SessionSetupHelper
|
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.
|
// [MS-SMB2] Windows [..] will also accept raw Kerberos messages and implicit NTLM messages as part of GSS authentication.
|
||||||
SessionSetupResponse response = new SessionSetupResponse();
|
SessionSetupResponse response = new SessionSetupResponse();
|
||||||
|
@ -49,7 +49,13 @@ namespace SMBLibrary.Server.SMB2
|
||||||
if (messageType == MessageTypeName.Negotiate)
|
if (messageType == MessageTypeName.Negotiate)
|
||||||
{
|
{
|
||||||
NegotiateMessage negotiateMessage = new NegotiateMessage(messageBytes);
|
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)
|
if (isRawMessage)
|
||||||
{
|
{
|
||||||
response.SecurityBuffer = challengeMessage.GetBytes();
|
response.SecurityBuffer = challengeMessage.GetBytes();
|
||||||
|
@ -63,33 +69,26 @@ namespace SMBLibrary.Server.SMB2
|
||||||
else // MessageTypeName.Authenticate
|
else // MessageTypeName.Authenticate
|
||||||
{
|
{
|
||||||
AuthenticateMessage authenticateMessage = new AuthenticateMessage(messageBytes);
|
AuthenticateMessage authenticateMessage = new AuthenticateMessage(messageBytes);
|
||||||
bool loginSuccess;
|
Win32Error loginStatus = securityProvider.Authenticate(state.AuthenticationContext, authenticateMessage);
|
||||||
try
|
if (loginStatus != Win32Error.ERROR_SUCCESS)
|
||||||
{
|
{
|
||||||
loginSuccess = users.Authenticate(authenticateMessage);
|
state.LogToServer(Severity.Information, "User '{0}' failed authentication. Win32 error: {1}", authenticateMessage.UserName, loginStatus);
|
||||||
}
|
NTStatus status = LogonHelper.ToNTStatus(loginStatus);
|
||||||
catch (EmptyPasswordNotAllowedException)
|
return new ErrorResponse(request.CommandName, status);
|
||||||
{
|
|
||||||
state.LogToServer(Severity.Information, "User '{0}' authentication using an empty password was rejected", authenticateMessage.UserName);
|
|
||||||
return new ErrorResponse(request.CommandName, NTStatus.STATUS_ACCOUNT_RESTRICTION);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.LogToServer(Severity.Information, "User '{0}' authenticated successfully", authenticateMessage.UserName);
|
||||||
state.CreateSession(request.Header.SessionID, authenticateMessage.UserName, authenticateMessage.WorkStation);
|
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.LogToServer(Severity.Information, "User '{0}' failed authentication. logged in as guest", authenticateMessage.UserName);
|
||||||
state.CreateSession(request.Header.SessionID, "Guest", authenticateMessage.WorkStation);
|
state.CreateSession(request.Header.SessionID, "Guest", authenticateMessage.WorkStation);
|
||||||
response.SessionFlags = SessionFlags.IsGuest;
|
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)
|
if (!isRawMessage)
|
||||||
{
|
{
|
||||||
|
|
|
@ -68,7 +68,7 @@ namespace SMBLibrary.Server
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return NegotiateHelper.GetNegotiateResponse(header, request, m_users);
|
return NegotiateHelper.GetNegotiateResponse(header, request, m_securityProvider, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -102,13 +102,13 @@ namespace SMBLibrary.Server
|
||||||
{
|
{
|
||||||
SessionSetupAndXRequest request = (SessionSetupAndXRequest)command;
|
SessionSetupAndXRequest request = (SessionSetupAndXRequest)command;
|
||||||
state.MaxBufferSize = request.MaxBufferSize;
|
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)
|
else if (command is SessionSetupAndXRequestExtended)
|
||||||
{
|
{
|
||||||
SessionSetupAndXRequestExtended request = (SessionSetupAndXRequestExtended)command;
|
SessionSetupAndXRequestExtended request = (SessionSetupAndXRequestExtended)command;
|
||||||
state.MaxBufferSize = request.MaxBufferSize;
|
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)
|
else if (command is EchoRequest)
|
||||||
{
|
{
|
||||||
|
|
|
@ -109,7 +109,7 @@ namespace SMBLibrary.Server
|
||||||
{
|
{
|
||||||
if (command is SessionSetupRequest)
|
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)
|
else if (command is EchoRequest)
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,6 +8,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
using SMBLibrary.Authentication.NTLM;
|
||||||
using SMBLibrary.NetBios;
|
using SMBLibrary.NetBios;
|
||||||
using SMBLibrary.Services;
|
using SMBLibrary.Services;
|
||||||
using SMBLibrary.SMB1;
|
using SMBLibrary.SMB1;
|
||||||
|
@ -24,7 +25,7 @@ namespace SMBLibrary.Server
|
||||||
public const bool EnableExtendedSecurity = true;
|
public const bool EnableExtendedSecurity = true;
|
||||||
|
|
||||||
private ShareCollection m_shares; // e.g. Shared folders
|
private ShareCollection m_shares; // e.g. Shared folders
|
||||||
private INTLMAuthenticationProvider m_users;
|
private NTLMAuthenticationProviderBase m_securityProvider;
|
||||||
private NamedPipeShare m_services; // Named pipes
|
private NamedPipeShare m_services; // Named pipes
|
||||||
private IPAddress m_serverAddress;
|
private IPAddress m_serverAddress;
|
||||||
private SMBTransportType m_transport;
|
private SMBTransportType m_transport;
|
||||||
|
@ -37,14 +38,14 @@ namespace SMBLibrary.Server
|
||||||
|
|
||||||
public event EventHandler<LogEntry> OnLogEntry;
|
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_shares = shares;
|
||||||
m_users = users;
|
m_securityProvider = securityProvider;
|
||||||
m_serverAddress = serverAddress;
|
m_serverAddress = serverAddress;
|
||||||
m_serverGuid = Guid.NewGuid();
|
m_serverGuid = Guid.NewGuid();
|
||||||
m_transport = transport;
|
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>
|
<DependentUpon>Settings.settings</DependentUpon>
|
||||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="User.cs" />
|
||||||
|
<Compile Include="UserCollection.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\SMBLibrary\SMBLibrary.csproj">
|
<ProjectReference Include="..\SMBLibrary\SMBLibrary.csproj">
|
||||||
|
|
|
@ -17,8 +17,9 @@ using System.Text;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using SMBLibrary;
|
using SMBLibrary;
|
||||||
|
using SMBLibrary.Authentication.NTLM;
|
||||||
using SMBLibrary.Server;
|
using SMBLibrary.Server;
|
||||||
using SMBLibrary.Server.Win32;
|
using SMBLibrary.Win32.Security;
|
||||||
using Utilities;
|
using Utilities;
|
||||||
|
|
||||||
namespace SMBServer
|
namespace SMBServer
|
||||||
|
@ -61,10 +62,10 @@ namespace SMBServer
|
||||||
transportType = SMBTransportType.DirectTCPTransport;
|
transportType = SMBTransportType.DirectTCPTransport;
|
||||||
}
|
}
|
||||||
|
|
||||||
INTLMAuthenticationProvider provider;
|
NTLMAuthenticationProviderBase provider;
|
||||||
if (chkIntegratedWindowsAuthentication.Checked)
|
if (chkIntegratedWindowsAuthentication.Checked)
|
||||||
{
|
{
|
||||||
provider = new Win32UserCollection();
|
provider = new IntegratedNTLMAuthenticationProvider();
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -80,7 +81,7 @@ namespace SMBServer
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
provider = new IndependentUserCollection(users);
|
provider = new IndependentNTLMAuthenticationProvider(users.GetUserPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,16 @@ namespace SMBLibrary.Server
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetUserPassword(string accountName)
|
||||||
|
{
|
||||||
|
int index = IndexOf(accountName);
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
return this[index].Password;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public List<string> ListUsers()
|
public List<string> ListUsers()
|
||||||
{
|
{
|
||||||
List<string> result = new List<string>();
|
List<string> result = new List<string>();
|
Loading…
Add table
Add a link
Reference in a new issue