Testing if SSH allows passwords
Published 2018-10-31Backstory: Why test if SSH allows passwords?
I'm developing telebit which, among other things, provides an option for you to access your devices (such as your laptop) via ssh.
Since there's an implicit trust in the physical security of the device, people don't typically worry about having good passwords on a local computer (which is a potential problem if you turn on SSH without thinking it through).
Also, because new ssh users generally aren't introduced to the more secure key-based authentication from the get-go, most people aren't using it.
I believe that security and convenience are inextricably linked - and that unless your security measures are also an increase in convenience for end-users, you're actually decreasing security. That said, I can't solve every problem all at once, and I don't want to make it inconvenient to use Telebit, so my meet-in-the-middle solution for the time being is to make it very easy for users to see their SSH config and make sure to let them know what's not-as-good as it could be, and how to fix it.
Looking at sshd_config
If you happen to know where sshd_config
is and have access to it, you'll know
what the ssh server should do.
There are three options worth looking at:
PermitRootLogin without-password
PasswordAuthentication no
Typically the server config will live in one of the following locations:
/etc/ssh/sshd_config
on macOS, Linux, and other UnixesC:\ProgramData\ssh\sshd_config
(%PROGRAMDATA%\ssh\sshd_config
) on Windows
There's also the matter of setting ChallengeResponseAuthentication no
, but
I'm not convinced this is necessary since it might be implemented for OTP or 2FA/MF2.
Testing if SSH requests for a password with Bash
Since there could be a big difference between what the config says the server should do and what it actually does - such as if a different config file or server is in use, and since you may not even have access to the config file anyway, it's also worth checking the server directly.
This will work for most use cases:
ssh -v -n \
-o Batchmode=yes \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
DOES_NOT_EXIST@localhost
You should see something like this, which will show the broadest scope of authentication methods allowed.
debug1: Authentications that can continue: publickey,password,keyboard-interactive
Here's a breakdown of the ssh options:
-v
causes the authentication methods to be displayed-o Batchmode=yes
causes ssh to enter a non-interactive mode where any prompts result in immediate failure-n
causes ssh to not open a shell (often used with tunneling), which in this case will cause it to immediately exit, even on success (which is important if connecting to a service that allows clients without authentication - such as honeypot or serveo.net)-o StrictHostKeyChecking=no
and-o UserKnownHostsFile=/dev/null
are used to automatically accept the host without reading it from or writing it to the known-hosts file.- On Windows you'll want to use
NUL
(in cmd.exe) or$null
(in PowerShell) instead of/dev/null
- On Windows you'll want to use
- I suppose which
user@host
you use doesn't matter that much since only the globalPasswordAuthentication
affects the output. For examplePermitRootLogin: prohibit-password
takes precedence, but is not applied until after.
The debug output will likely be output to stderr
rather than stdout
and
you'll probably want to limit the output. Adding 2>&1 | grep password
will
do the trick:
ssh -v -n \
-o Batchmode=yes \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
DOES_NOT_EXIST@localhost 2>&1 | grep password
Though, I'm not sure what the equivalent would be on Windows...
Testing if SSH requests for a password with node.js
Here's my initial draft of putting this all together programmatically in node.js:
'use strict';
/*global Promise*/
var PromiseA = Promise;
var crypto = require('crypto');
var util = require('util');
var readFile = util.promisify(require('fs').readFile);
var exec = require('child_process').exec;
function sshAllowsPassword(user) {
// SSH on Windows is a thing now (beta 2015, standard 2018)
// https://stackoverflow.com/questions/313111/is-there-a-dev-null-on-windows
var nullfile = '/dev/null';
if (/^win/i.test(process.platform)) {
nullfile = 'NUL';
}
var args = [
'ssh', '-v', '-n'
, '-o', 'Batchmode=yes'
, '-o', 'StrictHostKeyChecking=no'
, '-o', 'UserKnownHostsFile=' + nullfile
, user + '@localhost'
, '| true'
];
return new PromiseA(function (resolve) {
// not using promisify because all 3 arguments convey information
exec(args.join(' '), function (err, stdout, stderr) {
stdout = (stdout||'').toString('utf8');
stderr = (stderr||'').toString('utf8');
if (/\bpassword\b/.test(stdout) || /\bpassword\b/.test(stderr)) {
resolve('yes');
return;
}
if (/\bAuthentications\b/.test(stdout) || /\bAuthentications\b/.test(stderr)) {
resolve('no');
return;
}
resolve('maybe');
});
});
}
module.exports.checkSecurity = function () {
var conf = {};
var noRootPasswordRe = /(?:^|[\r\n]+)\s*PermitRootLogin\s+(prohibit-password|without-password|no)\s*/i;
var noPasswordRe = /(?:^|[\r\n]+)\s*PasswordAuthentication\s+(no)\s*/i;
var sshdConf = '/etc/ssh/sshd_config';
if (/^win/i.test(process.platform)) {
// TODO use %PROGRAMDATA%\ssh\sshd_config
sshdConf = 'C:\\ProgramData\\ssh\\sshd_config';
}
return readFile(sshdConf, null).then(function (sshd) {
sshd = sshd.toString('utf8');
var match;
match = sshd.match(noRootPasswordRe);
conf.permit_root_login = match ? match[1] : 'yes';
match = sshd.match(noPasswordRe);
conf.password_authentication = match ? match[1] : 'yes';
}).catch(function () {
// ignore error as that might not be the correct sshd_config location
}).then(function () {
var doesntExist = crypto.randomBytes(16).toString('hex');
return sshAllowsPassword(doesntExist).then(function (maybe) {
conf.requests_password = maybe;
});
}).then(function () {
return conf;
});
};
if (require.main === module) {
module.exports.checkSecurity().then(function (conf) {
console.log(conf);
return conf;
});
}
I'm not going to go through the trouble of explaining that line-by-line, but it'll probably end up on npm as its own module sooner or later.
FYI, I tried testing different configurations and specifically root@localhost
instead of a random user, but that didn't have an affect on the output.
See also
By AJ ONeal
Did I make your day?
Buy me a coffee
(you can learn about the bigger picture I'm working towards on my patreon page )