From ce5452551b28ea643e650f5249fc88add36b452c Mon Sep 17 00:00:00 2001 From: TheGWalk Date: Fri, 17 Aug 2018 11:32:49 -0600 Subject: [PATCH] Added sental to ClientAuthentication.TryAuthenticate() to prevent stack overflow. --- Renci.SshNet.sln | 28 ++ Renci.SshNet/ClientAuthentication.cs | 336 +++++++++--------- TestConsole/TestConsole/App.config | 6 + TestConsole/TestConsole/Program.cs | 34 ++ .../TestConsole/Properties/AssemblyInfo.cs | 36 ++ TestConsole/TestConsole/TestConsole.csproj | 65 ++++ 6 files changed, 344 insertions(+), 161 deletions(-) create mode 100644 Renci.SshNet.sln create mode 100644 TestConsole/TestConsole/App.config create mode 100644 TestConsole/TestConsole/Program.cs create mode 100644 TestConsole/TestConsole/Properties/AssemblyInfo.cs create mode 100644 TestConsole/TestConsole/TestConsole.csproj diff --git a/Renci.SshNet.sln b/Renci.SshNet.sln new file mode 100644 index 0000000..ff4f033 --- /dev/null +++ b/Renci.SshNet.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.40629.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Renci.SshNet", "Renci.SshNet\Renci.SshNet.csproj", "{2F5F8C90-0BD1-424F-997C-7BC6280919D1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestConsole", "TestConsole\TestConsole\TestConsole.csproj", "{96BCEC43-5B29-4E29-82F8-CC4A2DDC97BD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2F5F8C90-0BD1-424F-997C-7BC6280919D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F5F8C90-0BD1-424F-997C-7BC6280919D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F5F8C90-0BD1-424F-997C-7BC6280919D1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F5F8C90-0BD1-424F-997C-7BC6280919D1}.Release|Any CPU.Build.0 = Release|Any CPU + {96BCEC43-5B29-4E29-82F8-CC4A2DDC97BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {96BCEC43-5B29-4E29-82F8-CC4A2DDC97BD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96BCEC43-5B29-4E29-82F8-CC4A2DDC97BD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {96BCEC43-5B29-4E29-82F8-CC4A2DDC97BD}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Renci.SshNet/ClientAuthentication.cs b/Renci.SshNet/ClientAuthentication.cs index c379ee1..2fcf516 100755 --- a/Renci.SshNet/ClientAuthentication.cs +++ b/Renci.SshNet/ClientAuthentication.cs @@ -5,165 +5,179 @@ namespace Renci.SshNet { - internal class ClientAuthentication - { - public void Authenticate(IConnectionInfoInternal connectionInfo, ISession session) - { - if (connectionInfo == null) - throw new ArgumentNullException("connectionInfo"); - if (session == null) - throw new ArgumentNullException("session"); - - session.RegisterMessage("SSH_MSG_USERAUTH_FAILURE"); - session.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS"); - session.RegisterMessage("SSH_MSG_USERAUTH_BANNER"); - session.UserAuthenticationBannerReceived += connectionInfo.UserAuthenticationBannerReceived; - - try - { - // the exception to report an authentication failure with - SshAuthenticationException authenticationException = null; - - // try to authenticate against none - var noneAuthenticationMethod = connectionInfo.CreateNoneAuthenticationMethod(); - - var authenticated = noneAuthenticationMethod.Authenticate(session); - if (authenticated != AuthenticationResult.Success) - { - if (!TryAuthenticate(session, new AuthenticationState(connectionInfo.AuthenticationMethods.ToList()), noneAuthenticationMethod.AllowedAuthentications.ToList(), ref authenticationException)) - { - throw authenticationException; - } - } - } - finally - { - session.UserAuthenticationBannerReceived -= connectionInfo.UserAuthenticationBannerReceived; - session.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"); - session.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"); - session.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"); - } - - } - - private bool TryAuthenticate(ISession session, - AuthenticationState authenticationState, - ICollection allowedAuthenticationMethods, - ref SshAuthenticationException authenticationException) - { - if (!allowedAuthenticationMethods.Any()) - { - authenticationException = new SshAuthenticationException("No authentication methods defined on SSH server."); - return false; - } - - // we want to try authentication methods in the order in which they were - // passed in the ctor, not the order in which the SSH server returns - // the allowed authentication methods - var matchingAuthenticationMethods = authenticationState.SupportedAuthenticationMethods.Where(a => allowedAuthenticationMethods.Contains(a.Name)).ToList(); - if (!matchingAuthenticationMethods.Any()) - { - authenticationException = new SshAuthenticationException(string.Format("No suitable authentication method found to complete authentication ({0}).", string.Join(",", allowedAuthenticationMethods.ToArray()))); - return false; - } - - foreach (var authenticationMethod in GetOrderedAuthenticationMethods(authenticationState, matchingAuthenticationMethods)) - { - if (authenticationState.FailedAuthenticationMethods.Contains(authenticationMethod)) - continue; - - // when the authentication method was previously executed, then skip the authentication - // method as long as there's another authentication method to try; this is done to avoid - // a stack overflow for servers that do not update the list of allowed authentication - // methods after a partial success - - if (!authenticationState.ExecutedAuthenticationMethods.Contains(authenticationMethod)) - { - // update state to reflect previosuly executed authentication methods - authenticationState.ExecutedAuthenticationMethods.Add(authenticationMethod); - } - - var authenticationResult = authenticationMethod.Authenticate(session); - switch (authenticationResult) - { - case AuthenticationResult.PartialSuccess: - if (TryAuthenticate(session, authenticationState, authenticationMethod.AllowedAuthentications.ToList(), ref authenticationException)) - { - authenticationResult = AuthenticationResult.Success; - } - break; - case AuthenticationResult.Failure: - authenticationState.FailedAuthenticationMethods.Add(authenticationMethod); - authenticationException = new SshAuthenticationException(string.Format("Permission denied ({0}).", authenticationMethod.Name)); - break; - case AuthenticationResult.Success: - authenticationException = null; - break; - } - - if (authenticationResult == AuthenticationResult.Success) - return true; - } - - return false; - } - - private IEnumerable GetOrderedAuthenticationMethods(AuthenticationState authenticationState, IEnumerable matchingAuthenticationMethods) - { - var skippedAuthenticationMethods = new List(); - - foreach (var authenticationMethod in matchingAuthenticationMethods) - { - if (authenticationState.ExecutedAuthenticationMethods.Contains(authenticationMethod)) - { - skippedAuthenticationMethods.Add(authenticationMethod); - continue; - } - - yield return authenticationMethod; - } - - foreach (var authenticationMethod in skippedAuthenticationMethods) - yield return authenticationMethod; - } - - private class AuthenticationState - { - private readonly IList _supportedAuthenticationMethods; - - public AuthenticationState(IList supportedAuthenticationMethods) - { - _supportedAuthenticationMethods = supportedAuthenticationMethods; - ExecutedAuthenticationMethods = new List(); - FailedAuthenticationMethods = new List(); - } - - /// - /// Gets the list of authentication methods that were previously executed. - /// - /// - /// The list of authentication methods that were previously executed. - /// - public IList ExecutedAuthenticationMethods { get; private set; } - - /// - /// Gets the list of authentications methods that failed. - /// - /// - /// The list of authentications methods that failed. - /// - public IList FailedAuthenticationMethods { get; private set; } - - /// - /// Gets the list of supported authentication methods. - /// - /// - /// The list of supported authentication methods. - /// - public IEnumerable SupportedAuthenticationMethods - { - get { return _supportedAuthenticationMethods; } - } - } - } + internal class ClientAuthentication + { + private int _maximumAuthTry = 50; + + public int MaximumAuthTry + { + get { return _maximumAuthTry; } + set { _maximumAuthTry = value; } + } + + public void Authenticate(IConnectionInfoInternal connectionInfo, ISession session) + { + if (connectionInfo == null) + throw new ArgumentNullException("connectionInfo"); + if (session == null) + throw new ArgumentNullException("session"); + + session.RegisterMessage("SSH_MSG_USERAUTH_FAILURE"); + session.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS"); + session.RegisterMessage("SSH_MSG_USERAUTH_BANNER"); + session.UserAuthenticationBannerReceived += connectionInfo.UserAuthenticationBannerReceived; + + try + { + // the exception to report an authentication failure with + SshAuthenticationException authenticationException = null; + + // try to authenticate against none + var noneAuthenticationMethod = connectionInfo.CreateNoneAuthenticationMethod(); + + var authenticated = noneAuthenticationMethod.Authenticate(session); + if (authenticated != AuthenticationResult.Success) + { + if (!TryAuthenticate(session, new AuthenticationState(connectionInfo.AuthenticationMethods.ToList()), noneAuthenticationMethod.AllowedAuthentications.ToList(), ref authenticationException)) + { + throw authenticationException; + } + } + } + finally + { + session.UserAuthenticationBannerReceived -= connectionInfo.UserAuthenticationBannerReceived; + session.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"); + session.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"); + session.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"); + } + + } + + private bool TryAuthenticate(ISession session, + AuthenticationState authenticationState, + ICollection allowedAuthenticationMethods, + ref SshAuthenticationException authenticationException, + int tryCount = 0) + { + if (!allowedAuthenticationMethods.Any()) + { + authenticationException = new SshAuthenticationException("No authentication methods defined on SSH server."); + return false; + } + + // we want to try authentication methods in the order in which they were + // passed in the ctor, not the order in which the SSH server returns + // the allowed authentication methods + var matchingAuthenticationMethods = authenticationState.SupportedAuthenticationMethods.Where(a => allowedAuthenticationMethods.Contains(a.Name)).ToList(); + if (!matchingAuthenticationMethods.Any()) + { + authenticationException = new SshAuthenticationException(string.Format("No suitable authentication method found to complete authentication ({0}).", string.Join(",", allowedAuthenticationMethods.ToArray()))); + return false; + } + + foreach (var authenticationMethod in GetOrderedAuthenticationMethods(authenticationState, matchingAuthenticationMethods)) + { + if (authenticationState.FailedAuthenticationMethods.Contains(authenticationMethod)) + continue; + + // when the authentication method was previously executed, then skip the authentication + // method as long as there's another authentication method to try; this is done to avoid + // a stack overflow for servers that do not update the list of allowed authentication + // methods after a partial success + + if (!authenticationState.ExecutedAuthenticationMethods.Contains(authenticationMethod)) + { + // update state to reflect previosuly executed authentication methods + authenticationState.ExecutedAuthenticationMethods.Add(authenticationMethod); + } + + var authenticationResult = authenticationMethod.Authenticate(session); + switch (authenticationResult) + { + case AuthenticationResult.PartialSuccess: + if (tryCount > _maximumAuthTry) + { + authenticationException = new SshAuthenticationException(string.Format("Permission denied ({0}).", authenticationMethod.Name)); + return false; + } + if (TryAuthenticate(session, authenticationState, authenticationMethod.AllowedAuthentications.ToList(), ref authenticationException, tryCount + 1)) + { + authenticationResult = AuthenticationResult.Success; + } + break; + case AuthenticationResult.Failure: + authenticationState.FailedAuthenticationMethods.Add(authenticationMethod); + authenticationException = new SshAuthenticationException(string.Format("Permission denied ({0}).", authenticationMethod.Name)); + break; + case AuthenticationResult.Success: + authenticationException = null; + break; + } + + if (authenticationResult == AuthenticationResult.Success) + return true; + } + + return false; + } + + private IEnumerable GetOrderedAuthenticationMethods(AuthenticationState authenticationState, IEnumerable matchingAuthenticationMethods) + { + var skippedAuthenticationMethods = new List(); + + foreach (var authenticationMethod in matchingAuthenticationMethods) + { + if (authenticationState.ExecutedAuthenticationMethods.Contains(authenticationMethod)) + { + skippedAuthenticationMethods.Add(authenticationMethod); + continue; + } + + yield return authenticationMethod; + } + + foreach (var authenticationMethod in skippedAuthenticationMethods) + yield return authenticationMethod; + } + + private class AuthenticationState + { + private readonly IList _supportedAuthenticationMethods; + + public AuthenticationState(IList supportedAuthenticationMethods) + { + _supportedAuthenticationMethods = supportedAuthenticationMethods; + ExecutedAuthenticationMethods = new List(); + FailedAuthenticationMethods = new List(); + } + + /// + /// Gets the list of authentication methods that were previously executed. + /// + /// + /// The list of authentication methods that were previously executed. + /// + public IList ExecutedAuthenticationMethods { get; private set; } + + /// + /// Gets the list of authentications methods that failed. + /// + /// + /// The list of authentications methods that failed. + /// + public IList FailedAuthenticationMethods { get; private set; } + + /// + /// Gets the list of supported authentication methods. + /// + /// + /// The list of supported authentication methods. + /// + public IEnumerable SupportedAuthenticationMethods + { + get { return _supportedAuthenticationMethods; } + } + } + } } diff --git a/TestConsole/TestConsole/App.config b/TestConsole/TestConsole/App.config new file mode 100644 index 0000000..88fa402 --- /dev/null +++ b/TestConsole/TestConsole/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/TestConsole/TestConsole/Program.cs b/TestConsole/TestConsole/Program.cs new file mode 100644 index 0000000..95684fa --- /dev/null +++ b/TestConsole/TestConsole/Program.cs @@ -0,0 +1,34 @@ +using System; +using Renci.SshNet; + +namespace TestConsole +{ + class Program + { + static void Main(string[] args) + { + var host = ""; + var port = 22; + var user = ""; + var pass = ""; + + if (string.IsNullOrWhiteSpace(host)) + { + Console.WriteLine("host?\r\n"); + host = Console.ReadLine(); + //... Finish this.... maybe + } + + #region Make Client + + var sftpClient = new SftpClient(host, port, user, pass); + sftpClient.BufferSize = 1024 * 32 - 100; //Default size can exceed payload max for some reason + sftpClient.Connect(); + if (!sftpClient.IsConnected) + throw new Exception("Failed to connect FtpClientSsh"); + + #endregion Make Client + + } + } +} diff --git a/TestConsole/TestConsole/Properties/AssemblyInfo.cs b/TestConsole/TestConsole/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..43aa231 --- /dev/null +++ b/TestConsole/TestConsole/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TestConsole")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TestConsole")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f8385aaa-4746-429d-951c-0e9059a55e4a")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TestConsole/TestConsole/TestConsole.csproj b/TestConsole/TestConsole/TestConsole.csproj new file mode 100644 index 0000000..1c2167f --- /dev/null +++ b/TestConsole/TestConsole/TestConsole.csproj @@ -0,0 +1,65 @@ + + + + + Debug + AnyCPU + {96BCEC43-5B29-4E29-82F8-CC4A2DDC97BD} + Exe + Properties + TestConsole + TestConsole + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + {2f5f8c90-0bd1-424f-997c-7bc6280919d1} + Renci.SshNet + + + + + \ No newline at end of file