Le serveur openssh, implémentation libre du protocole SSH proposée à l’origine par Theo De Raadt pour OpenBSD, est à mon avis une pépite du logiciel libre. J’estime que c’est probablement le daemon qui est le plus sécurisé de mes serveurs Linux, loin devant le serveur web par exemple. Je m’intéresse néanmoins aux astuces permettant de limiter les risques et vulnérabilités sous Linux (Debian 9, raspbian pour Raspberry, …).

Mise en place de clés d’authentification

En théorie, la meilleure méthode d’authentification auprès d’un serveur SSH, c’est la clé générée avec une passphrase. Cela constitue une authentification à deux niveau avec un élément à posséder (la clé) et un élément à connaitre (la passphrase).

Les assaillants qui ne possèdent pas la clé sont directement rejetés, c’est l’idéal. Les attaques bruteforce sont contrecarrées.

Pour mettre en place une clé sur votre client SSH c’est assez simple avec la commande ssh-keygen.

user@ordinateur:~# ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/user/.ssh/id_rsa): 
Created directory '/home/user/.ssh'.
Enter passphrase (empty for no passphrase): <votre-passphrase>
Enter same passphrase again: <votre-passphrase>
Your identification has been saved in /home/user/.ssh/id_rsa.
Your public key has been saved in /home/user/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:AjG[...] user@ordinateur
The key's randomart image is:
+---[RSA 2048]----+
|  ..  ooxx.      |
|    ..xxxx       |
|    . *xx.       |
|.   *+ ..+       |
|E. * +  S        |
+----[SHA256]-----+

Pour que le serveur SSH distant reconnaisse la clé présentée par le client, il faut lui communiquer. La commande ssh-copy-id permet d’ajouter automatiquement la clé dans serveur:~/.ssh/authorized_keys .

user@ordinateur:~$ ssh-copy-id user@serveur
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/user/.ssh/id_rsa.pub"
The authenticity of host 'serveur' can't be established.
ECDSA key fingerprint is SHA256:jfgbg[..].
Are you sure you want to continue connecting (yes/no)? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
user@serveur's password: 

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'user@serveur'"
and check to make sure that only the key(s) you wanted were added.

La tentative de connexion suivante de l’utilisateur user auprès de serveur permettra normalement l’utilisation de la clé. Pour le vérifier on peut utiliser le mode verbose.

user@ordinateur:~$ ssh -v user@serveur
OpenSSH_7.4p1 Debian-10+deb9u2, OpenSSL 1.0.2l  25 May 2017
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 19: Applying options for *
debug1: Connecting to localhost [::1] port 22.
debug1: Connection established.
debug1: identity file /home/user/.ssh/id_rsa type 1
debug1: key_load_public: No such file or directory
debug1: identity file /home/user/.ssh/id_rsa-cert type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/user/.ssh/id_dsa type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/paul2/.ssh/id_dsa-cert type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/user/.ssh/id_ecdsa type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/user/.ssh/id_ecdsa-cert type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/user/.ssh/id_ed25519 type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/user/.ssh/id_ed25519-cert type -1
debug1: Enabling compatibility mode for protocol 2.0
debug1: Local version string SSH-2.0-OpenSSH_7.4p1 Debian-10+deb9u2
debug1: Remote protocol version 2.0, remote software version OpenSSH_7.4p1 Debian-10+deb9u2
debug1: match: OpenSSH_7.4p1 Debian-10+deb9u2 pat OpenSSH* compat 0x04000000
debug1: Authenticating to localhost:22 as 'paul2'
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug1: kex: algorithm: curve25519-sha256
debug1: kex: host key algorithm: ecdsa-sha2-nistp256
debug1: kex: server->client cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
debug1: kex: client->server cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
debug1: Server host key: ecdsa-sha2-nistp256 SHA256:yFSlcVYqt0ICjcHMVnxfPif309AxaXvAOIPH/yvelqQ
debug1: Host 'localhost' is known and matches the ECDSA host key.
debug1: Found key in /home/user/.ssh/known_hosts:1
debug1: rekey after 134217728 blocks
debug1: SSH2_MSG_NEWKEYS sent
debug1: expecting SSH2_MSG_NEWKEYS
debug1: SSH2_MSG_NEWKEYS received
debug1: rekey after 134217728 blocks
debug1: SSH2_MSG_EXT_INFO received
debug1: kex_input_ext_info: server-sig-algs=<ssh-ed25519,ssh-rsa,ssh-dss,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521>
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: publickey,password
debug1: Next authentication method: publickey
debug1: Offering RSA public key: /home/user/.ssh/id_rsa
debug1: Server accepts key: pkalg ssh-rsa blen 279
debug1: Authentication succeeded (publickey).
Authenticated to localhost ([::1]:22).
debug1: channel 0: new [client-session]
debug1: Requesting no-more-sessions@openssh.com
debug1: Entering interactive session.
debug1: pledge: network
debug1: client_input_global_request: rtype hostkeys-00@openssh.com want_reply 0
debug1: Sending environment.
debug1: Sending env LANG = fr_FR.UTF-8
Linux serveur 4.9.0-4-amd64 #1 SMP Debian 4.9.65-3+deb9u1 (2017-12-23) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Thu Feb  1 12:14:20 2018
user@serveur:~$

Après cette vérification on pourra interdire l’utilisation du mot de passe simple auprès du serveur avec la directive  PasswordAuthentication no  et  PermitRootLogin prohibit-password  dans le fichier de configuration coté serveur /etc/ssh/sshd_config.

Si vous mettez en place les clés d’authentification, vous vous mettez en excellente posture du point de vue de la sécurité. Les mesures qui suivent ne me semblent du coup pas obligatoires, juste conseillées si vous recherchez la protection maximale.

Sécuriser openssh

En pratique, l’utilisation du mot de passe est bien pratique. Cela fonctionne par défaut, aussi bien avec un client SSH Linux qu’avec une appli smartphone ou tablette. Le mot de passe est un procédé ancestral et universel très largement adopté.

Les mesures qui suivent permettent de sécuriser l’utilisation du protocole SSH même en utilisant l’authentification par mot de passe.

Emploi des bons algorithmes de chiffrement

Dans le cadre de cet article, je m’intéresse plus à l’administration systèmes et au bonnes pratiques de hardening SSH. La confidentialité des échanges dépend des algorithmes de chiffrement utilisés d’une part pour l’authentification, et d’autre part pour le cryptage des échanges lorsque la connexion est établie. Cet article décrit très bien les algorithmes à autoriser et à interdire.

Interdire root avec un mot de passe

Coté serveur, la directive PermitRootLogin no  permet d’empêcher que l’utilisateur root ne se logue par SSH, autant par clé que par mot de passe. Pour mettre en place cette interdiction seulement pour le mot de place on remplacera « no » par « prohibit-password ». C’est la configuration par défaut sur Debian.

Limiter le nombre de tentatives d’authentification par connexion

La directive MaxAuthTries <num>  permet de limiter le nombre d’invites d’authentification. Les tentatives comptabilisées concernent à la fois la clé et le mot de passe. Les clients SSH ont tendance à proposer leur clé en premier puis à se rabattre sur le mot de passe en cas d’échec, si bien que la valeur numérique renseignée doit être choisie en tenant compte de cela.

Timeout d’authentification

Le délai de fermeture de connexion en cas de mot de passe ou de passphrase non renseigné est configurable avec la directive LoginGraceTime.

Modification du port d’écoute

La modification des ports standards pour l’écoute des services est intéressante, car les attaques automatiques sont calibrées justement pour des systèmes standards. Peu d’assaillants essaieront d’attaquer un serveur SSH par le port 80 par exemple (réservé à HTTP) ou encore le port 12345. Par contre j’ai déjà remarqué des tentatives sur le port 222.

Pour changer le port d’écoute SSH on pourrait mettre en place une règle iptables, ou encore toucher à la directive Port 22  dans /etc/ssh/sshd_config. Des options particulières peuvent également être spécifiées au démarrage du service SSH grâce au fichier /etc/default/ssh  sous Debian.

Le client ssh devra être lancé avec l’option -p pour spécifier le port distant.

Limiter la connexion à seulement certains utilisateurs

La directive AllowUsers permet de spécifier la liste limitative des seuls utilisateurs pouvant se connecter. La directive DenyUsers permet d’en interdire. La syntaxe prend en compte les noms d’utilisateurs simples (mike) ainsi que les couples utilisateur-hôte (john@ip).

AllowUsers mike john@ip

Chrooter les utilisateurs dans leurs répertoires respectifs

La directive ChrootDirectory %h  permet de chrooter les utilisateurs dans leurs répertoires utilisateurs respectifs tels que définis dans /etc/passwd. C’est ce même fichier qui spécifie l’emplacement du shell, emplacement relatif à la racine absolue du système de fichiers ou à la racine chroot.

C’est pourquoi vous aurez le message d’erreur suivant si vous ne prenez pas de précautions particulières avec une arborescence chroot suffisante (exécutables comme bash, certaines librairies, …).

user@ordinateur:~$ ssh user@server
user@server's password: 
[...]
/bin/bash: No such file or directory
Connection to server closed.

Pour l’exécutable bash, on se rend compte avec la commande ldd que certaines librairies présentes dans /lib et /lib64 sont requises. On peut les monter comme ceci :

user@ordinateur:~$ mount -o bind,ro /lib/ lib/
user@ordinateur:~$ mount -o bind,ro /lib64/ lib64/

Mais le plus simple est encore de trouver un bash statique, c’est à dire ne nécessitant pas de librairies externes. Le paquet Debian bash-static est prévu pour cela.

Par ailleurs, openssh nous oblige à ce que les répertoires de chroot appartiennent à root. Par exemple le répertoire /home/user doit appartenir à root pour l’utiliser en tant que racine chroot. Sans cela, vous aurez le message d’erreur suivant lors de vos tentatives de connexion.

packet_write_wait: Connection to ::1 port 22: Broken pipe

Et également dans auth.log :

Feb  1 14:36:45 server sshd[28436]: fatal: bad ownership or modes for chroot directory "/home/user"

Il est toujours bon de tester le bon fonctionnement du chroot en ligne de commande avant de le configurer sur openssh.

Sécuriser l’usage de SSH par les possibilités Linux

Les conseils qui suivent font appel aux possibilités du système d’exploitation, plus que du daemon SSH en lui-même.

Délai de pause après un échec d’authentification (PAM)

Je trouve intéressant d’obliger le client SSH à partienter après un échec d’authentification. Cela décourage rapidement les petits curieux qui voudraient tester une attaque par force brute.

A l’heure actuelle il n’existe pas de directive de configuration coté serveur. Cela ce joue en amont, par le PAM (pluggable authentification modules).

La ligne permettant cela est la suivante, que l’on peut renseigner dans /etc/pam.d/ssh.

auth       optional   pam_faildelay.so  delay=9000000

Je trouve que la configuration de PAM n’est vraiement pas simple, et j’ai souvent tenté des modifications sans qu’elles prennent effet. L’ordre des lignes de conf est important. Sur Debian 9 il faut intercaler la ligne ci-dessus au-dessus de @include common-auth .

La valeur numérique est en milisecondes, donc ici 9 secondes.

Désactivation du login de certains utilisateurs

On peut refuser à un utilisateur de s’authentifier auprès du système (par SSH ou la console) en préfixant le hash du mot de passe par un point d’exclamation dans /etc/shadow . Exemple :

user:!$6$Pz[...]:17555:0:99999:7:::

Emploi d’un shell restreint

Le shell proposé à l’utilisateur suite à sa connexion est spécifié dans /etc/passwd . On remarque souvent /bin/bash  comme shell par défaut. Le choix le plus restrictif est /bin/false  qui n’est pas un shell mais plutôt un non-shell, un « rien du tout ».

Pour pousser un peu le hardening dans certaines situations (exemple : tunnel SSH simple), je recommande de chrooter l’utilisateur dans une arborescence minimale, avec un restricted shell. Il existe par exemple rssh dont le but est de permettre simplement SCP/SFTP/Rsync vis SSH en excluant tout le reste.

Bannissement des assaillants avec fail2ban

Les assaillants qui reviennent un peu trop souvent peuvent être bannis via iptables grâce à un programme de surveillance des logs comme fail2ban. Sur Debian, l’installation de fail2ban suffit à surveiller les échecs de connexion SSH.

user@ordinateur:~# apt-get install fail2ban

Règles pare-feu contre les essais répétés

Le pare-feu iptables permet de bloquer une connexion SSH si son émetteur se montre un peu trop insistant. A utiliser avec précautions, car cette règle peut également bloquer une connexion légitime virt-manager (ssh+qemu://) ou d’autres tunnels démarrés automatiquement.

iptables -N SSH_CHECK
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -j SSH_CHECK
iptables -A SSH_CHECK -m recent --set --name SSH
iptables -A SSH_CHECK -m recent --update --seconds 60 --hitcount 6 --name SSH -j DROP

 

 

Pour aller plus loin

Je vous recommande la lecture du dossier PDF de l’agence française de la sécurité des systèmes d’information (ANSSI).