Reverse SSH, pour se connecter à un ordinateur distant protégé par un pare-feu

Cet article est le troisième d'une série de quatre :

  1. SSH, pour se connecter en ligne de commande à un ordinateur distant
  2. SSH, se connecter sans mot de passe à l'aide de la cryptographie
  3. Reverse SSH, pour se connecter à un ordinateur distant protégé par un pare-feu
  4. SSH, des tunnels pour tous les services

Dans un article précédent, nous avons vu comment se connecter directement à un ordinateur distant présent sur le même réseau local, comment se connecter directement à un ordinateur distant situé derrière un pare-feu convenablement configuré, et comment se connecter à un ordinateur distant accessible par rebond via un ordinateur intermédiaire. Ne reste plus qu'à traiter, donc, le cas de l'ordinateur distant... injoignable. Ce cas est en fait le plus courant. Il s'agit d'un ordinateur distant installé derrière un routeur, lui-même inaccessible par SSH et empêchant toute connexion directe au serveur. Bref, le cas typique de l'ordinateur domestique installé derrière sa box Internet. Avec une adresse IP publique dynamique. Et des utilisateurs qui ont d'autres centres d'intérêts que l'administration réseau (et qui le vivent, semble-t-il, plutôt bien).

Connexion bloquée par un pare-feu
Connexion bloquée par un pare-feu

Autant il est impossible de se connecter au serveur distant, autant on peut faire en sorte que le serveur puisse se connecter au client. Car le client, c'est nous ; et là-dessus, on peut agir. Si on ouvre un tunnel SSH depuis le serveur vers le client, il est possible au client de remonter le tunnel pour accéder au serveur. C'est ce qu'on appelle le reverse SSH — ou SSH inversé. Ça peut sembler tordu, mais ça marche. Et encore, la situation est simple ; et présente d'ailleurs certains défauts. Par exemple, si le client est mobile et change de connexion Internet, le tunnel mis en place ne fonctionne plus. On pourrait imaginer un tunnel opérant via un serveur VPN, auquel le client est connecté en permanence. Néanmoins, l'accès serait alors uniquement possible depuis le poste client connecté au VPN. Une solution plus universelle est envisageable. Plus complexe, certes, mais qui présente de nombreux avantages.

Connexion par SSH inversé
Connexion par SSH inversé

Imaginons que SERVEUR_B, auquel on souhaite se connecter, est installé derrière un pare-feu qui empêche toute connexion entrante. Il n'est pas possible de se connecter au serveur, mais celui-ci a bel et bien accès au réseau. Ainsi, SERVEUR_B peut ouvrir un tunnel SSH vers SERVEUR_A, qui nous appartient et qui est convenablement configuré pour cela ; ce qui implique d'éventuelles règles de routage particulières, ou un potentiel accès direct. Si CLIENT peut également se connecter à SERVEUR_A, il peut alors aussi se connecter à SERVEUR_B, comme nous l'avons déjà vu. L'astuce réside donc dans l'usage d'un serveur intermédiaire, SERVEUR_A. Si SERVEUR_B peut se connecter à SERVEUR_A et que CLIENT peut aussi se connecter à SERVEUR_A, alors CLIENT peut se connecter à SERVEUR_B.

Connexion par SSH inversé, avec rebond
Connexion par SSH inversé, avec rebond

Toute l'intelligence du système repose sur SERVEUR_A. Mais attention ! SERVEUR_B, ainsi que la multitude d'ordinateurs qui ont accès à SERVEUR_A afin de pouvoir s'y connecter à distance par SSH inversé, n'entrent pas dans votre sphère de contrôle. Et tous ces ordinateurs, que l'on peut raisonnablement considérer comme hostiles, disposent d'un accès direct à SERVEUR_A ! Autant dire qu'il va falloir réduire un minimum leur pouvoir d'action... Pour cela, on pourrait créer un utilisateur système aux droits réduits. Mieux, on peut enfermer cet utilisateur dans un chroot minimaliste, grâce à Jailkit.

Situation initiale

Faisons le point.

SERVEUR_B, auquel on souhaite se connecter, n'est pas accessible directement. Il est néanmoins nécessaire d'y opérer une configuration initiale afin d'automatiser l'ouverture et le maintient d'un tunnel SSH vers SERVEUR_A.

SERVEUR_A, qui sert de relais, nécessite une configuration spéciale afin de limiter les droits des serveurs distants qui s'y connectent, considérés comme hostiles — tel que SERVEUR_B, notamment. SERVEUR_A doit être accessible par SSH, en permanence. L'objet de cet article n'est pas d'expliquer comment déployer des règles de routages particulières ni toute autre configuration système ou réseau pour y parvenir.

CLIENT, qui souhaite accéder à SERVEUR_B, a juste besoin de pouvoir se connecter à SERVEUR_A.

Configuration de SERVEUR_A

SERVEUR_A nécessite l'installation d'un serveur SSH, comme expliqué dans l'article précédemment cité. Afin de permettre l'utilisation directe du tunnel par CLIENT, il est nécessaire d'activer deux options de configuration du serveur SSH. Pour cela, il faut tout d'abord se connecter à SERVEUR_A en tant que super-utilisateur :

su -

Modifier la configuration du serveur SSH :

sed -i "s/#AllowTcpForwarding yes/AllowTcpForwarding yes/" /etc/ssh/sshd_config
sed -i "s/#GatewayPorts no/GatewayPorts yes/" /etc/ssh/sshd_config

Puis la recharger :

systemctl reload ssh.service

SERVEUR_A nécessite également l'installation et la configuration de Jailkit afin de pouvoir isoler un utilisateur système, comme expliqué dans cet autre article.

La seule différence consiste en une initialisation minimaliste de l'environnement isolé :

jk_init -v -j /home/jail limitedshell

Il n'est ici pas utile de définir Bash comme shell interactif pour l'utilisateur JAIL_USER, puisque personne n'aura à se connecter en ligne de commande à SERVEUR_A ; ce dernier ne servant que de serveur relais pour la connexion à SERVEUR_B. D'ailleurs, Bash n'est même pas installé dans cet environnement isolé.

Configuration de SERVEUR_B

SERVEUR_B a uniquement besoin d'un client SSH. Les distributions GNU/Linux les plus répandues en étant dotées par défaut, nous devrions d'ores-et-déjà pouvoir nous connecter manuellement à SERVEUR_A depuis SERVEUR_B :

ssh USER_A@IP_SERVEUR_A

Puis nous déconnecter :

exit

Le but du jeu étant d'automatiser l'ouverture et le maintient d'un tunnel SSH vers SERVEUR_A, l'authentification par mot de passe n'est pas envisageable. Il faut configurer une authentification cryptographique, comme expliqué dans cet article. La seule différence — contrainte par ce même soucis d'automatisation — étant que la clef privée elle-même ne doit pas être protégée par un mot de passe.

Copier la clef publique sur SERVEUR_A

Comme expliqué dans l'article indiqué, la clef publique doit être copiée sur SERVEUR_A, pour le compte utilisateur JAIL_USER. Malheureusement, l'environnement isolé installé est trop restrictif pour permettre l'usage de ssh-copy-id. Il est évidemment possible d'y remédier, mais cela augmente notablement la surface d'attaque. S'agissant d'une opération peu fréquente, il est préférable de la réaliser manuellement. Pour cela, il faut tout d'abord, copier la clef publique dans le dossier personnel d'un utilisateur standard de SERVEUR_A :

scp .ssh/id_ed25519.pub USER_A@IP_SERVEUR_A:/home/USER_A/

Puis se connecter à ce compte utilisateur :

ssh USER_A@IP_SERVEUR_A

Se connecter en tant que super-utilisateur :

su -

Importer la clef dans le compte utilisateur JAIL_USER :

cd /home/jail/home/JAIL_USER
umask 077
mkdir -p .ssh
cat /home/USER_A/id_ed25519.pub >> .ssh/authorized_keys
chown -R JAIL_USER: .ssh
rm /home/USER_A/id_ed25519.pub

Se déconnecter (deux fois) :

exit

Création d'un tunnel SSH

Afin d'automatiser l'ouverture et le maintient d'un tunnel SSH vers SERVEUR_A, il nous faut installer le paquet autossh. Pour cela, il faut tout d'abord se connecter à SERVEUR_B en tant que super-utilisateur :

su -

Installer le paquet autossh :

apt install autossh

Créer un service systemd correspondant :

vim /etc/systemd/system/autossh.service

Et y coller ceci :

[Unit]
Description=Keep a tunnel open on port 22
After=network.target

[Service]
User=USER_B
ExecStart=/usr/bin/autossh -o ServerAliveInterval=60 -NR 22222:localhost:22 JAIL_USER@IP_SERVEUR_A
Restart=on-failure

[Install]
WantedBy=multi-user.target

Le port 22222 correspond à l'entrée du tunnel sur SERVEUR_A, qui débouche sur le port 22 de SERVEUR_B. Autant le port d'entrée du tunnel peut être choisi — presque — librement, autant le port de sortie doit correspondre au port d'écoute du serveur SSH sur SERVEUR_B.

Activer le service au démarrage du système, et le démarrer :

systemctl --now enable autossh.service

Se déconnecter :

exit

Se connecter à SERVEUR_B depuis CLIENT

Depuis SERVEUR_A, on peut désormais se connecter à SERVEUR_B ainsi :

ssh -p 22222 USER_B@localhost

Où le port 22222 sur localhost est bien l'entrée du tunnel relié au port 22 de SERVEUR_B.

Puisque le but du jeu n'est pas de se connecter directement depuis SERVEUR_A, connectons-nous donc depuis CLIENT, via SERVEUR_A :

ssh -J JAIL_USER@IP_SERVEUR_A -p 22222 USER_B@localhost

Nous y voilà ! Nous pouvons enfin nous connecter à cette satanée machine installée derrière un pare-feu empêchant toute connexion externe... ou presque, donc Wink Évidemment, si l'on souhaite créer d'autres tunnels SSH pour d'autres serveurs distants, il suffit d'utiliser des ports d'entrées différents que celui choisi pour SERVEUR_B.

Dans ce dernier exemple, nous nous sommes connectés à SERVEUR_B via SERVEUR_A, par rebond. Si, par des règles de routage particulières ou un accès direct à SERVEUR_A, il est possible de se connecter directement au port 22222 de SERVEUR_A, la connexion par rebond n'est pas indispensable.

Connexion par SSH inversé via SERVEUR_A, sans rebond
Connexion par SSH inversé via SERVEUR_A, sans rebond

Dans ce cas, la commande suivante suffit :

ssh -p 22222 USER_B@IP_SERVEUR_A

À noter l'utilisation a priori étrange de l'utilisateur USER_B sur IP_SERVEUR_A.

Dans cet article, nous avons vu comment créer un tunnel SSH pour se connecter en ligne de commande à un ordinateur distant ; comment relier le port d'une machine externe au port SSH de la machine à contacter, et ainsi contourner la protection d'un pare-feu empêchant, a priori, une telle connexion. Comme nous le verrons dans un prochain article, cette méthode peut être généralisée à l'usage de biens d'autres services que la seule connexion en ligne de commande.

Article sous licence Creative Commons BY-SA 3.0 France.

Publié le

Lundi 15 octobre 2018 à 16h34

Ajouter un commentaire

HTML filtré

  • Balises HTML autorisées : <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <p>
  • Textual smiley will be replaced with graphical ones.
  • Les adresses de pages web et de courriels sont transformées en liens automatiquement.
  • Les lignes et les paragraphes vont à la ligne automatiquement.

Texte brut

  • Aucune balise HTML autorisée.
  • Les adresses de pages web et de courriels sont transformées en liens automatiquement.
  • Les lignes et les paragraphes vont à la ligne automatiquement.

Flux RSS des commentaires de cet article.