Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MFA authentication depends on the order of methods in AuthenticationMethods #872

Open
essa opened this issue Jun 29, 2022 · 0 comments · May be fixed by #873
Open

MFA authentication depends on the order of methods in AuthenticationMethods #872

essa opened this issue Jun 29, 2022 · 0 comments · May be fixed by #873

Comments

@essa
Copy link

essa commented Jun 29, 2022

Expected behavior

sshd can forces users multi factor authentication by AuthenticationMethods.

net-ssh can pass the authentication for servers with this configuration but fails when the order of the authentication doesn't match with the server.

ssh command doesn't depend on the order. So I expect net-ssh same behavior.

Actual behavior

net-ssh fails on authentication for sshd servers with following configuration.

AuthenticationMethods "password,publickey"

System configuration

  • net-ssh 6.1
  • Ruby version 2.7.1

Example App

Start sshd with following configuration.

AuthenticationMethods "password,publickey"

This doesn't match with the order of the default

And provide the password and key file to following script.

#!/usr/bin/env ruby

require 'net/ssh'
puts Net::SSH::Version::CURRENT

host='******'
user='******'
options = {
#:auth_methods=>["password", "publickey" ], # this is a workaround 
:password=>"******",
:keys => "(secret_key_path)",
:logger=>Logger.new(STDOUT, level: Logger::DEBUG),
}

Net::SSH.start(host, user, options) do |ssh|
  puts ssh.exec!('echo "hello"')
end

This will fail with following log.

D, [2022-06-29T15:43:12.249311 #22084] DEBUG -- net.ssh.authentication.session[5c8]: beginning authentication of `****'
D, [2022-06-29T15:43:12.249403 #22084] DEBUG -- socket[5b4]: using encrypt-then-mac
D, [2022-06-29T15:43:12.249484 #22084] DEBUG -- socket[5b4]: queueing packet nr 3 type 5 len 32
D, [2022-06-29T15:43:12.249548 #22084] DEBUG -- socket[5b4]: sent 100 bytes
D, [2022-06-29T15:43:12.305919 #22084] DEBUG -- socket[5b4]: read 100 bytes
D, [2022-06-29T15:43:12.306231 #22084] DEBUG -- socket[5b4]: received packet nr 3 type 6 len 32
D, [2022-06-29T15:43:12.306480 #22084] DEBUG -- net.ssh.authentication.session[5c8]: trying none
D, [2022-06-29T15:43:12.306687 #22084] DEBUG -- socket[5b4]: using encrypt-then-mac
D, [2022-06-29T15:43:12.306857 #22084] DEBUG -- socket[5b4]: queueing packet nr 4 type 50 len 48
D, [2022-06-29T15:43:12.306959 #22084] DEBUG -- socket[5b4]: sent 116 bytes
D, [2022-06-29T15:43:12.314768 #22084] DEBUG -- socket[5b4]: read 100 bytes
D, [2022-06-29T15:43:12.315014 #22084] DEBUG -- socket[5b4]: received packet nr 4 type 51 len 32
D, [2022-06-29T15:43:12.315122 #22084] DEBUG -- net.ssh.authentication.session[5c8]: allowed methods: password
D, [2022-06-29T15:43:12.315227 #22084] DEBUG -- net.ssh.authentication.methods.none[5dc]: none failed
D, [2022-06-29T15:43:12.315309 #22084] DEBUG -- net.ssh.authentication.session[5c8]: trying password
D, [2022-06-29T15:43:12.315580 #22084] DEBUG -- socket[5b4]: using encrypt-then-mac
D, [2022-06-29T15:43:12.315772 #22084] DEBUG -- socket[5b4]: queueing packet nr 5 type 50 len 64
D, [2022-06-29T15:43:12.315939 #22084] DEBUG -- socket[5b4]: sent 132 bytes
D, [2022-06-29T15:43:12.329686 #22084] DEBUG -- socket[5b4]: read 100 bytes
D, [2022-06-29T15:43:12.329868 #22084] DEBUG -- socket[5b4]: received packet nr 5 type 51 len 32
D, [2022-06-29T15:43:12.329939 #22084] DEBUG -- net.ssh.authentication.session[5c8]: allowed methods: publickey
D, [2022-06-29T15:43:12.330000 #22084] DEBUG -- net.ssh.authentication.methods.password[5f0]: password failed
E, [2022-06-29T15:43:12.330062 #22084] ERROR -- net.ssh.authentication.session[5c8]: all authorization methods failed (tried none, password)
bundler: failed to load command: ./test_ssh.rb (./test_ssh.rb)
Net::SSH::AuthenticationFailed: Authentication failed for user ec2-user@3.113.25.141
  /Users/tnaka/tmp/sftp/vendor/bundle/ruby/2.7.0/gems/net-ssh-6.1.0/lib/net/ssh.rb:268:in `start'
  /Users/tnaka/tmp/sftp/test_ssh.rb:14:in `<top (required)>'

If passes when the workaround is enabled.

D, [2022-06-29T15:52:12.903008 #23498] DEBUG -- net.ssh.authentication.session[5c8]: beginning authentication of `ec2-user'
D, [2022-06-29T15:52:12.903081 #23498] DEBUG -- socket[5b4]: using encrypt-then-mac
D, [2022-06-29T15:52:12.903154 #23498] DEBUG -- socket[5b4]: queueing packet nr 3 type 5 len 32
D, [2022-06-29T15:52:12.903211 #23498] DEBUG -- socket[5b4]: sent 100 bytes
D, [2022-06-29T15:52:12.961372 #23498] DEBUG -- socket[5b4]: read 100 bytes
D, [2022-06-29T15:52:12.961751 #23498] DEBUG -- socket[5b4]: received packet nr 3 type 6 len 32
D, [2022-06-29T15:52:12.962022 #23498] DEBUG -- net.ssh.authentication.session[5c8]: trying password
D, [2022-06-29T15:52:12.962284 #23498] DEBUG -- socket[5b4]: using encrypt-then-mac
D, [2022-06-29T15:52:12.962380 #23498] DEBUG -- socket[5b4]: queueing packet nr 4 type 50 len 64
D, [2022-06-29T15:52:12.962481 #23498] DEBUG -- socket[5b4]: sent 132 bytes
D, [2022-06-29T15:52:12.983382 #23498] DEBUG -- socket[5b4]: read 100 bytes
D, [2022-06-29T15:52:12.983737 #23498] DEBUG -- socket[5b4]: received packet nr 4 type 51 len 32
D, [2022-06-29T15:52:12.983917 #23498] DEBUG -- net.ssh.authentication.session[5c8]: allowed methods: publickey
D, [2022-06-29T15:52:12.984045 #23498] DEBUG -- net.ssh.authentication.methods.password[5dc]: password failed
D, [2022-06-29T15:52:12.984163 #23498] DEBUG -- net.ssh.authentication.session[5c8]: trying publickey
D, [2022-06-29T15:52:12.985511 #23498] DEBUG -- net.ssh.authentication.agent[5f0]: connecting to ssh-agent
...
D, [2022-06-29T15:52:13.136039 #23498] DEBUG -- net.ssh.authentication.methods.publickey[618]: publickey succeeded (ce:08:b1:f7:fb:b3:e2:47:f8:c3:d5:c9:10:78:eb:13)
D, [2022-06-29T15:52:13.136344 #23498] DEBUG -- socket[5b4]: using encrypt-then-mac
D, [2022-06-29T15:52:13.136526 #23498] DEBUG -- socket[5b4]: queueing packet nr 9 type 90 len 32
...
D, [2022-06-29T15:52:13.308920 #23498] DEBUG -- socket[5b4]: sent 84 bytes
hello
I, [2022-06-29T15:52:13.308994 #23498]  INFO -- net.ssh.connection.session[654]: closing remaining channels (0 open)

What's wrong.

https://github.com/net-ssh/net-ssh/blob/master/lib/net/ssh/authentication/session.rb#L71-L95

         @auth_methods.each do |name|
            next unless @allowed_auth_methods.include?(name)

            attempted << name

            debug { "trying #{name}" }
            begin
               # try the method
            return true if method.authenticate(next_service, username, password)
          rescue Net::SSH::Authentication::DisallowedMethod
          end

          error { "all authorization methods failed (tried #{attempted.join(', ')})" }
          return false
  

When MFA is required on the server and one of auth succeeded, sshd returns SSH_MSG_USERAUTH_FAILURE with method list of "authentications that can continue" and "partial success" flag on.

net-ssh assigns the list to @allowed_auth_methods and skips the method when it is not included in the list.

If the order of methods doesn't match with @auth_methods, one of the method will be skipped and never tried.

Possible Solution

I tried to write a test but I couldn't understand how to write tests in this project.

But here's a quick fix.

I have inserted three lines to Net:SSH:Authetication:Session#authenticate so that it tries all the methods.

while @auth_methods.size > 0 ### <<<====
  @auth_methods.each do |name|
    # original loop
    ... 
  end
  @auth_methods.delete(name) ### <<<====
end ### <<<====
        while @auth_methods.size > 0 ### <<<====
          @auth_methods.each do |name|
            begin
              next unless @allowed_auth_methods.include?(name)
              attempted << name

              debug { "trying #{name}" }
              begin
                auth_class = Methods.const_get(name.split(/\W+/).map { |p| p.capitalize }.join)
                method = auth_class.new(self, key_manager: key_manager, password_prompt: options[:password_prompt])
              rescue NameError
                debug {"Mechanism #{name} was requested, but isn't a known type.  Ignoring it."}
                next
              end

              return true if method.authenticate(next_service, username, password)
            rescue Net::SSH::Authentication::DisallowedMethod
            end
            @auth_methods.delete(name) ### <<<====
          end
        end ### <<<====

        error { "all authorization methods failed (tried #{attempted.join(', ')})" }
        return false

With this fix, MFA passes with any order of auth_methods.

@essa essa linked a pull request Jun 30, 2022 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant