Wednesday, November 17, 2021

[SOLVED] PHPSeclib Public Key Authentication Successful on Localhost; Failing when deployed

Issue

I've created a script which connects to a server using the phpseclib extension, using public key autentication (as you see here), by generating a key pair using ssh-keygen.

What I'm facing now is that the connection works very well when I run the php script from my localhost, but when I run the script from the server, I always get a failed login attempt, with the following error reported:

SSH_MSG_USERAUTH_FAILURE

(which I get when using $sftp->getLastError() after trying $sftp->login()).

Any idea why this happens?????

UPDATE

Thought it may be due to some error related to file_get_contents(), so I've checked on it, it's the value of the key indeed. I've also directly pasted the private key into the PublicKeyLoader::load() function, same error happened, so that's not the reason.

The main log events where I think the issue happened are, when running the exact same script trying to establish a connection to the target server:

from the localhost (working):

NET_SSH2_MSG_SERVICE_REQUEST;
NET_SSH2_MSG_SERVICE_ACCEPT;
NET_SSH2_MSG_USERAUTH_REQUEST (ssh-connection none); 
NET_SSH2_MSG_USERAUTH_FAILURE; 
NET_SSH2_MSG_USERAUTH_REQUEST(ssh-connection publickey); 
NET_SSH2_MSG_USERAUTH_PK_OK;

from the server (not working):

NET_SSH2_MSG_SERVICE_REQUEST;
NET_SSH2_MSG_SERVICE_ACCEPT;
NET_SSH2_MSG_USERAUTH_REQUEST (ssh-connection none); 
NET_SSH2_MSG_USERAUTH_FAILURE;
NET_SSH2_MSG_USERAUTH_REQUEST (ssh-connection publickey); 
NET_SSH2_MSG_USERAUTH_FAILURE;

I generated three key pairs, ran the localhost and the server-client test with all of them, and I always get what's shown above. And when I echo it out, I definitely get the key, and don't worry it's not an ecrypted OpenSSH private key, that's not the reason (not supported in phpseclib), and that would then fail on the localhost too (which I've tested).

So looks like it's failing on the step of the public key authentication on the server, although I'm using the exact same script, and although I'm getting the exactly correct key when echoing the retrieved key out, also on the server-side.

I've then checked what happens if I change a single character of my key; if that reproduces the above-displayed SSH - log, but no. If I do so, the script interrupts before logging anything, throwing an phpseclib3\Exception\NoKeyLoadedException: Unable to read key Exception.

From this I concluded that the only problem could be that the key is encoded in a certain wrong way before being sent to the server, and that the server misunderstands the key due to that.

Considering this, I've found that phpseclib requires the paragonie package according to this, and that one of phpseclibs dependencies of paragonie do some base64 - encoding. I thus supposed that it may the case that the encoding used to send the key is not properly done for some reason, and went to check on how the phpseclib extension was installed on the server (done by server admins, not by me).

Turned out it is a plesk server, which requires you to upload new composer packages in a somewhat unusual way, namely via the composer.json file, according to this. I've only installed composer packages either via command prompt, via SSH directly on the server, or to a local environment + deployed on the server. Neither of this work, and the instruction above also did not work.

I've then checked the composer.json file, and found its content to be:

{ 
  "name": "packagex/packagex", 
  "type": "plugin", 
  "description": "secret_description", 
  "keywords": ["secret_keyword"], 
  "homepage": "secret_url", 
  "license": "secret_license", 
  "authors": [ { "name": "secret_name", "email": "secret_mail" } ],
  "require": 
    {
      "php": ">=5.3", 
      "phpseclib/phpseclib": "^3.0"
    }
}

The file contained an extension already, which we cannot touch, but we need to add phpseclib. To do so, the line "phpseclib/phpseclib": "^3.0" has simply been added into the require key of the JSON, but I feel that this is not the correct way. IMHO, this kind of interpretes the phpseclib as a dependency of the already used package, which are completely independent from each other.

I'm starting to think now that this may be the final reason why the script fails when run on the server-side: the package is not properly installed in terms of its dependencies, hence the dependencies phpseclib itself needs are not properly used, as the phpseclib package itself is listed as a dependency. Due to this, some missing base64-encoding or similar (paragonie dependency of phpseclib package, see above) may be causing the issue. Is this possible? To eliminate this reason; I'd need to know how to properly install an extension via composer via composer.json on a plesk server, without touching the previously in-this-way installed packages. Any idea on how to do that? I've also posted a separate question for this, for further details; you may check on it.

What's also maybe causing an issue is that the current composer.json file requires a php version below the one which is required atm by phpseclib (php: >=5.6.1). But again, to change this, I need to know how to modify the composer.json file in the proper way.

UPDATE

I've updated the composer.json file by copying its contents from the plesk server to a local environment where I downloaded the composer. There I then installed the phpseclib package via command prompt, and then copied the resulting composer.json back to the Plesk Server. And I get exactly the same issue again (and besides also the same composer.json content as before). Help..?


Solution

So what happened is that I've checked (among blocked IPs etc) on the target server on which port it listens for SFTP connections. And I noticed that it A) was not listening at the standard port and B) blocked incoming connection requests on the standard port.

Weirdly, when run from the local machine, the SSH connection could be established without any problem at all, without specifying any port, hence using the standard port, all the time.

In the script deployed on the server, the SSH connection could exclusively be established if the connection request was made on the port the server was configured to listen for SFTP.

Even more interestingly, the script run on the localhost fails in the way the deployed script failed when I specify the non-standard port in it.

So in the end, no idea why this happens, but it's definitely all confirmed with tests.



Answered By - DevelJoe