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

2-Factor authentication is not working #894

Open
jacky15 opened this issue Feb 13, 2017 · 12 comments
Open

2-Factor authentication is not working #894

jacky15 opened this issue Feb 13, 2017 · 12 comments

Comments

@jacky15
Copy link

jacky15 commented Feb 13, 2017

Hi there:

I am trying to connect to server which needs two authentication: public-key and password.
I always get the authentication failed exception when using paramiko to connect to server.

when I using sftp client, got the logs below:

debug1: Authentications that can continue: publickey,password
debug1: Next authentication method: publickey
debug1: Trying private key: /path/to/key
debug1: read PEM private key done: type RSA
Authenticated with partial success.
debug1: Authentications that can continue: password
debug1: Next authentication method: password
username@server's password: 
debug1: Authentication succeeded (password).

and the paramiko logs show

DEB [20170213-18:00:28.247] thr=1   paramiko.transport: Switch to new keys ...
DEB [20170213-18:00:32.829] thr=1   paramiko.transport: userauth is OK
INF [20170213-18:00:32.879] thr=1   paramiko.transport: Authentication continues...
DEB [20170213-18:00:32.879] thr=1   paramiko.transport: Methods: [u'password']
INF [20170213-18:00:36.810] thr=1   paramiko.transport: Disconnect (code 7): The request to start service ssh-userauth while another service is against protocol. 

here is my python code:

import paramiko
paramiko.util.log_to_file('/path/to/log')
hostname = 'server.name'
port = 12345
username = 'username'
password = 'password'
sshcon   = paramiko.SSHClient()
sshcon.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 
pkey = paramiko.RSAKey.from_private_key_file('/path/to/key')

transport = paramiko.Transport((hostname, port))
transport.connect()
transport.auth_publickey(username=username, key=pkey)
transport.auth_password(username=username, password=password)
@ploxiln
Copy link
Contributor

ploxiln commented Feb 13, 2017

possibly related to #840

@jacky15
Copy link
Author

jacky15 commented Feb 14, 2017

Hi @ploxiln ,
thanks for the reply, but I think this issue is different from #840 .

I try to trace the code in paramiko, maybe it should not sent cMSG_SERVICE_REQUEST twice? And use cMSG_USERAUTH_REQUEST instead of it?

@jacky15
Copy link
Author

jacky15 commented Feb 14, 2017

I have solved the problem and maybe there is a bug in paramiko?
Here is the code how I solved the problem

import paramiko
paramiko.util.log_to_file('/path/to/log')
hostname = 'server.name'
port = 12345
username = 'username'
password = 'password' 
pkey = paramiko.RSAKey.from_private_key_file('/path/to/key')

transport = paramiko.Transport((hostname, port))
transport.connect()

# auth the public key as usual, auth service now is activated on server 
transport.auth_publickey(username=username, key=pkey)

# try to send another userauth request without request auth service
m = paramiko.Message()
m.add_byte(paramiko.common.cMSG_USERAUTH_REQUEST)
m.add_string(username)
m.add_string('ssh-connection')
m.add_string('password')
m.add_boolean(False)
py3_password = paramiko.py3compat.bytestring(password)
m.add_string(py3_password)
transport._send_message(m)

# now it works! : )
sftp_client = paramiko.SFTPClient.from_transport(transport)

Maybe something like a service lock is need in AuthHandler module, in other to avoid requesting auth service twice?

@jacky15
Copy link
Author

jacky15 commented Feb 20, 2017

Hi @bitprophet

FYI, the server of this case is vshell

@bitprophet
Copy link
Member

@jacky15 Good to know, though unfortunately that makes it harder to test/troubleshoot, we almost exclusively deal with OpenSSH :(

That said it's definitely possible there are some state-machine/order-of-ops bugs regarding connecting that vserver is more sensitive to, which we could fix. I don't have time to dig right now but offhand if I were you I'd:

  • doublecheck what you're doing in both snippets against how SSHClient behaves, it's a semi-canonical workflow for using Transport. Might hold a clue.
  • search for other tickets relating to what you think the issue might be (re: extra MSG_SERVICE_REQUEST etc) in case it's come up elsewhere before

Thanks!

@henryk
Copy link

henryk commented Nov 10, 2021

I have encountered this bug in a different way, but it's the same problem, and IMHO it's a bug in how paramiko handles authentication.
I'm trying to do SFTP with a special server in an online banking environment. They require publickey and password authentication. paramiko.client.SSHClient._auth() has special code paths to deal with the two_factor case, so I thought it should work, but it doesn't.

What I'm doing and what is happening

I'm using paramiko.client.SSHClient.connect() and specify the password and pkey parameters with suitable values.

Patching paramiko to display incoming and outgoing messages shows the following sequence of events:

  1. ... Connection establishment and key setup, yada yada. Ends with message 21 from server.
  2. Paramiko sends message 5 (service request "ssh-userauth")
  3. Server sends message 6 (service accept "ssh-userauth")
  4. Paramiko sends message 50 (user authentication with publickey)
  5. Server sends message 53 (banner "UNLAWFUL ENTRY INTO THIS SYSTEM BY INDIVIDUALS, NOT SPECIFICALLY AUTHORIZED, IS A CRIMINAL OFFENSE...", this happens a few times, I'm going to ignore this packet for the rest of this post, since it doesn't affect anything)
  6. Server sends message 51 (user authentication partial success, next authentication "password")
  7. Paramiko sends message 5 (service request "ssh-userauth")
  8. Server sends message 6 (service accept "ssh-userauth")
  9. Paramiko sends message 50 (user authentication with password)
  10. Server sends message 51 (user authentication partial success, next authentication "publickey")

At this point .connect() returns (from ._auth()), but no authenticated session exists.

What should be happening

A connection with the OpenSSH ssh client to the same server is successful, with the following sequence of events:

  1. ... Connection establishment etc.
  2. ssh sends message 5 (service request "ssh-userauth")
  3. Server sends message 6 (service accept "ssh-userauth")
  4. ssh sends message 50 (user authentication with mechanism "none", to retrieve the list of possible authentication mechanisms)
  5. Server sends message 53 (banner etc.)
  6. Server sends message 51 (user authentication failed, possible authentication methods: "publickey,password")
  7. ssh sends message 50 (user authentication with public key, test only whether this key would be accepted)
  8. Server sends message 60 (key is acceptable)
  9. ssh sends message 50 (user authentication with public key)
  10. Server sends message 51 (user authentication partial success, next authentication "password")
  11. ssh sends message 50 (user authentication with password)
  12. Server sends message 52 (user authentication succeeded)

Note: Steps 4 through 6 are purely optional and allow the ssh client to retrieve the list of authentication methods before trying any of them. Steps 7 and 8 are likewise optional and allow testing whether a pubkey is acceptable before requesting the passphrase for that pubkey.

Analysis

The bug in my case and in jacky15s case is in step 7 of the paramiko behaviour, vs. step 11 in the openssh behaviour. Message type 5 from RFC 4253 requests a service, in this case "ssh-userauth", which is further specified in RFC 4252. As far as I can see neither RFC specifies what is to happen if the same service is requested multiple times in sequence.
From observing the behavior:

  • OpenSSH ssh server: Requesting ssh-userauth the first time starts the service. Additional requests for the service are ignored/don't change anything.
  • The server jacky15 is seeing: Requesting another service while ssh-userauth is still active is rejected
  • The server I am seeing: Requesting ssh-userauth again re-initializes the service, clearing the current authentication state.

From my understanding of the RFCs all three behaviors are sane and valid. The OpenSSH ssh client behavior works with all three servers and is unconditionally safe. paramiko is at fault here.

The bug happens in paramiko in the interaction between paramiko.auth_handler.AuthHandler._request_auth() via .auth_publickey() (and .auth_password(), etc.) and ._parse_service_accept(). Calling any auth_*() method inevitably leads to the sending of a service request (message type 5) packet, and the only way that paramiko actually sends the authentication request (message type 50) is as a response to a service accept (message type 6) packet.

Fix

I haven't got a pull request for this, since I'm not comfortable with how the auth_event things work in the auth_*() methods. Which is why I'm providing this detailed write-up in the how that someone else can fix it.

But the control flow in AuthHandler (and corresponding calls from transport) needs to be changed in a major way: The auth_* methods need to check whether the ssh-userauth service is already active, and then either request starting of the service, wait for the start of the service, then send the authentication, or, if the service is already active, immediately send the authentication.

Workaround

The workaround provided by jacky15 is correct: The transport.auth_publickey() call requests both the ssh-userauth service and arranges for the public key authentication to be performed. Simply sending the password authentication request afterwards completes the authentication, with no extraneous packets exchanged.

@shoaib-intro
Copy link

shoaib-intro commented Jun 22, 2022

Hi I'm trying to connect server which ask:
Password:
Passcode:

I tried following code:

    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(host, username = username, password = password)
    channel = ssh.invoke_shell()
    password = input('Please enter password!')
    channel.send(password + "\n")
    passcode = input('Please enter passcode!')
    channel.send(passcode + "\n")

It failed password authentication then I tried

def interactive_auth_handler(title, instructions, prompt_list):
    if prompt_list:
        if prompt_list[0][0]=="Password: ":
            return [password]
        return [getpass.getpass(prompt_list[0][0])]
    return []


client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
    client.connect(host, port, username=username, password=password, pkey=None)
except paramiko.ssh_exception.SSHException as e:
    transport = client.get_transport()
    transport.auth_interactive(username, interactive_auth_handler)

but again authentication failed could you please tell me what actually problem is there. following is error from paramiko debug:

INFO:paramiko.transport:Auth banner: b"#######################################################################        \n#                                                                     #\n#  This is not a public system.  Authorised users only.               #\n#                                                                     #\n#  Any attempt at unauthorised access or modification of any          #\n#  computer material whatsoever could be a criminal offence           #    \n#  under UK law.                                                      #\n#                                                                     #\n#  By using this system, you consent to such interception,            #\n#  monitoring, recording, copying, auditing, inspection, and          #\n#  disclosure as Sky deems necessary in order to ensure compliance    #\t\n#  with its Acceptable Use Policy (which can be found within the      #\n#  technology pages on the intranet).                                 #\n#                                                                     #\n#  Unauthorised or improper use of this system may result in civil    #\n#  and criminal penalties and administrative or disciplinary action,  #        \n#  as appropriate.                                                    #    \n#                                                                     #         \n#  If you proceed any further you indicate your awareness of and      #\n#  consent to these terms and conditions and you will be bound by     #\n#  Sky's (and any applicable third party's) rules for computer use.   #\n#                                                                     #         \n#######################################################################\n\n\n"
INFO:paramiko.transport:Authentication (password) failed.

@bskinn
Copy link
Contributor

bskinn commented Jun 23, 2022

@shoaib-intro, I think your problem is distinct from the one described in this issue -- you're trying to send two text passphrases, whereas this issue is about a combined password plus public key authentication process. Please open a new issue for this problem you're facing. Thanks!

@shoaib-intro
Copy link

shoaib-intro commented Jun 26, 2022

I have opened a new issue could you please answer it! #2074

@mathman79
Copy link

I encountered the same issue as described here and @jacky15's suggestion worked.

Has there been any progress updating Paramiko to work better in this particular case?

I feel like it might be good to have an auth method that could handle public key + password rather than having to call two that interact in an unexpected way.

As for me calling auth_password showed that the next expected auth method would be 'publickey' and calling auth_publickey after that bounced me back to 'password' as the next expected auth method, never breaking out of this loop.

What is strange is that I'm interacting with another SFTP server that also uses 'password' + 'publickey' and there calling the auth_password first and then auth_publickey after works just fine, but @jacky15's suggestion does not!

@mathman79
Copy link

@bskinn bskinn added the Keys label May 12, 2023
@bskinn
Copy link
Contributor

bskinn commented May 12, 2023

Possibly under the umbrella of the key/auth overhaul of #387

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants