- SSH est un outil qui va au-delà de l’accès distant et couvre aussi le port forwarding, les jump hosts, les fichiers de configuration et la gestion des clés ; les exemples avec un serveur web s’appliquent tels quels à d’autres services comme RDP ou SQL
- Les port forwardings local, distant et dynamique se configurent respectivement avec
-L, -R et -D ; la différence essentielle est de savoir de quel côté le port est ouvert et où circule le trafic
- Dans les réseaux où l’accès direct est bloqué, les jump hosts
-J et ProxyJump permettent d’enchaîner plusieurs relais SSH jusqu’à la destination
ssh-agent et le transfert d’agent -A permettent d’utiliser des clés locales même depuis un hôte distant, ce qui est pratique, mais il faut d’abord vérifier les risques de sécurité liés à une mauvaise utilisation de l’agent
- En combinant
~/.ssh/config, ssh-copy-id, ssh-keygen et la console SSH ~?·~C, on réduit la saisie répétitive d’options et l’on facilite l’ajout de forwardings en cours de session, le déploiement de clés publiques ainsi que la création et la vérification de clés
Prérequis pour comprendre le port forwarding SSH
- Le port forwarding SSH est difficile à saisir uniquement avec des diagrammes ; il est plus facile à comprendre en regardant à la fois les commandes réelles et des scénarios réseau
- Les exemples reposent sur l’accès à un serveur web, mais la même méthode s’applique à presque tous les services, comme RDP ou SQL
- Les options utilisées à plusieurs reprises ont les significations suivantes
-N : n’exécute pas de commande sur le serveur distant et n’ouvre pas non plus de shell
-f : envoie SSH en arrière-plan
root@host : se connecte avec l’utilisateur et l’hôte qui serviront à créer le tunnel
Port forwarding local -L
- Le port forwarding local transmet un port de la machine locale vers un port d’un serveur distant
- Situation d’exemple
internal-web.int héberge une page web accessible uniquement depuis l’interface loopback
- Depuis
campfire.int, il est possible d’accéder à internal-web.int en SSH
- On veut accéder au serveur web de
internal-web.int via un port local de campfire.int
- Commande utilisée
ssh -N -f -L 1337:127.0.0.1:80 root@internal-web.int
- Interprétation des options
-L : indique un forwarding local
1337:127.0.0.1:80 : associe le port local 1337 au 127.0.0.1:80 distant
- Une fois le tunnel créé, depuis
campfire.int, on peut envoyer des requêtes au port local 1337 pour interagir avec le port 80 de internal-web.int
- Ce qu’il faut retenir : avec
-L, le port local se trouve à gauche dans l’adresse
Port forwarding distant -R
- Le port forwarding distant expose sur un port du serveur distant un port accessible depuis la machine locale
- Situation d’exemple
internal-web.int héberge une page web accessible uniquement depuis la loopback
campfire.int ne peut pas accéder directement à internal-web.int à cause d’un pare-feu
vuln-server.int est accessible à la fois depuis campfire.int et internal-web.int
- Commande utilisée
ssh -N -f -R 3000:127.0.0.1:80 root@vuln-server.int
- Interprétation des options
-R : indique un forwarding distant
3000:127.0.0.1:80 : associe le port 3000 de vuln-server.int au 127.0.0.1:80 local
- Ensuite, en envoyant une requête
curl vers vuln-server.int:3000, on peut accéder à la page web interne qui fonctionne sur le port 80 de internal-web.int
- Ce qu’il faut retenir : avec
-R, le port local se trouve à droite dans l’adresse
Port forwarding dynamique -D et proxy SOCKS
- Le port forwarding dynamique crée un proxy SOCKS avec l’option
-D et envoie le trafic via le relais SSH
- Situation d’exemple
internal-web.int héberge une application web accessible uniquement depuis le réseau interne
vuln-server.int se trouve sur le même réseau interne et peut accéder à internal-web.int
- Depuis
campfire.int, on veut proxifier le trafic via vuln-server.int
- La configuration de
/etc/proxychains.conf doit correspondre au port de la commande SSH
socks5 : fait utiliser SOCKS5 par proxychains
127.0.0.1 : utilise localhost
8080 : doit correspondre au port indiqué à SSH avec -D
- Commande utilisée
ssh -N -f -D 8080 root@vuln-server.int
- Une fois le forwarding créé, en configurant
socks5 127.0.0.1 8080, on peut accéder à la page web interne avec proxychains curl 192.168.1.185
- Le DNS via SOCKS peut ne pas bien fonctionner selon l’environnement ; dans l’exemple, on utilise donc l’adresse IP plutôt que le nom d’hôte
- Dans Firefox aussi, il est possible d’utiliser le proxy SOCKS via la configuration manuelle du proxy
- Chemin : Settings → Privacy & Security → Network Settings
- Sélectionner Manual proxy configuration
- Cocher “Proxy DNS when using SOCKS V5”
- Définir le SOCKS host sur
127.0.0.1 et le port sur 8080
Jump host -J
- Un jump host permet de se connecter à une destination inaccessible directement depuis l’hôte actuel en passant par plusieurs relais SSH
- La chaîne d’exemple est
campfire.int → vuln-server.int → internal-web.int → dns.int
- Commande utilisée
ssh -J root@vuln-server.int,root@internal-web.int root@dns.int
- Plusieurs destinations de saut sont séparées par des virgules
Transfert d’agent -A
ssh-agent permet d’ajouter des clés privées ou des identités sur la machine locale avec ssh-add <private_key_file>
- Les clés ajoutées peuvent être vérifiées avec
ssh-add -l
- Ajouter une clé à
ssh-agent permet de se connecter en SSH avec cette clé sans ressaisir le mot de passe, ce qui est utile aussi bien pour les personnes que pour les comptes de service
- Le transfert d’agent
-A permet d’utiliser les clés de l’agent local même depuis la machine distante à laquelle on est connecté
- Avant de l’utiliser, il faut consulter Zero Effort Private Key Compromise: Abusing SSH-Agent for Lateral Movement pour comprendre les risques de sécurité
- Commande d’exemple
ssh -A -J root@vuln-server.int root@internal-web.int
- Cette commande se connecte à
internal-web.int en passant par vuln-server.int, tout en permettant d’utiliser les clés présentes dans l’agent SSH local de campfire.int
- Ensuite, depuis
internal-web.int, on peut exécuter ssh root@dns.int et se connecter sans préciser de clé privée ni saisir d’identifiants
Allocation de TTY pour une commande -t
- L’option
-t est utile pour exécuter rapidement sur un serveur distant une commande nécessitant une interaction
- Les exemples sont des commandes qui ont besoin d’un TTY, comme
Vim ou top
- Commande utilisée
ssh root@internal-web.int -t top
- À l’exécution, on obtient sur le serveur distant un TTY incluant la commande
top
-g pour permettre à des hôtes externes de se connecter au port de forwarding local
- L’option
-g permet à des hôtes distants de se connecter au port forwardé localement
- C’est similaire au port forwarding local
-L, mais avec la différence que des machines externes peuvent elles aussi utiliser ce port « local »
- Situation d’exemple
- On dispose d’un accès shell à
vuln-server.int
- On veut proxifier les connexions arrivant sur le port
2222 de vuln-server.int vers le port 22 de internal-web.int
- Commande utilisée
ssh -N -f -g -L 2222:localhost:22 root@internal-web.int
- Interprétation des options
-g : permet à des hôtes distants de se connecter au port de forwarding local
-L : indique un forwarding local
- Même si l’on se connecte en SSH au port
2222 de vuln-server.int, le shell réel se trouve sur internal-web.int
Console SSH ~? et forwarding en cours de session
- La console SSH est une fonctionnalité cachée qui permet de contrôler SSH lui-même sans interagir directement avec le système distant
- Elle est utile lorsque le shell est cassé ou qu’il faut contrôler la session SSH elle-même
- La console d’aide s’ouvre avec
~?
- Options utiles
~. : termine la session SSH actuelle
~C : ouvre la console SSH pour ajouter des options de forwarding
- Même si l’on est déjà connecté à
vuln-server.int avec une commande ssh classique, appuyer sur ~C puis saisir -D 8080 permet d’utiliser cette session comme une session de forwarding dynamique
- Si, sur
campfire.int, /etc/proxychains.conf est configuré pour utiliser le port 8080, on peut utiliser proxychains comme si l’on avait lancé ssh -D dès le départ
Fichier de configuration SSH ~/.ssh/config
- Le fichier de configuration SSH se trouve dans
~/.ssh/config et permet de gagner du temps en enregistrant les options SSH saisies à répétition
- Lors d’une connexion SSH, ce fichier est analysé ; s’il existe une configuration
host correspondant à la cible, les options correspondantes sont utilisées
- Les arguments de ligne de commande ont priorité sur le fichier de configuration
- Par exemple, même si le fichier de configuration définit l’utilisateur de
internal-web.int comme root, l’exécution de ssh graham@internal-web.int tentera une connexion en tant que graham
- Exemple de configuration de base
# You can put comments with a `#` at the beginning of the line only.
host internal-web.int
User root
IdentityFile /home/smores/ssh_agent/internal-web-no-pw
Port 2222
- Déroulement lors de l’exécution de
ssh internal-web.int
- Fait correspondre
internal-web.int de la ligne de commande avec host internal-web.int dans ~/.ssh/config
- En cas de correspondance, récupère dans le fichier de configuration les options qui n’ont pas été indiquées en ligne de commande
- En l’absence de correspondance, seules les options définies en ligne de commande sont utilisées
Mots-clés SSH config fréquemment utilisés
IdentityFile /path/to/private_key
- Spécifie la clé privée à utiliser pour l’hôte
- Joue le même rôle que
ssh -i
ForwardAgent
ProxyJump root@internal-web.int
- Spécifie le serveur par lequel proxifier le trafic
- Joue le même rôle que l’option
-J
- Dans l’exemple, cela montre que l’authentification auprès de
vuln-server.int est d’abord demandée et que le trafic passe par cet hôte
Match
- Applique des mots-clés SSH config selon des conditions
- Dans l’exemple, si le code de sortie de la commande
export | grep PROXYME=TRUE est 0, le ProxyJump sous le bloc Match est utilisé
- Si la variable d’environnement
PROXYME n’existe pas, seul le bloc host internal-web.int classique est utilisé
- Après avoir défini
export PROXYME=TRUE, exécuter le même ssh internal-web.int demande l’authentification auprès de vuln-server.int, puis donne un shell sur internal-web.int
scp et certains utilitaires basés sur SSH peuvent généralement utiliser eux aussi la configuration SSH
- Dans les environnements où elle n’est pas utilisée automatiquement, on peut la préciser avec
-F ~/.ssh/config
Copie de clé publique avec ssh-copy-id
ssh-copy-id est un petit utilitaire qui téléverse rapidement une clé publique vers un serveur
- Commande utilisée
ssh-copy-id -i internal-web root@internal-web.int
- Interprétation des options
-i internal-web : spécifie le nom de la clé privée à utiliser pour l’authentification auprès du serveur
root@internal-web.int : spécifie le serveur vers lequel téléverser la clé publique
Génération et vérification de clés avec ssh-keygen
ssh-keygen est un utilitaire qui génère des paires clé privée/clé publique
- Il est généralement recommandé d’utiliser l’option
-b pour spécifier une taille de clé plus grande
- Dans l’environnement d’exemple, la taille de clé par défaut était
3072
- L’algorithme par défaut est RSA, mais on peut spécifier un autre algorithme avec le flag
-t
- Exemple :
ssh-keygen -t ecdsa -b 521
- L’empreinte et la taille en octets d’une clé peuvent être vérifiées avec la commande suivante
ssh-keygen -lf <file-name>
1 commentaires
Avis de Hacker News
Il manque ici une directive étonnamment simple : configurer
sshd_configavec quelque chose commeAuthorizedKeysCommand /usr/bin/php /etc/ssh/auth.php %u, puis faire en sorte que le script récupèrehttps://github.com/{$user}.keyssur GitHubBien sûr, ce n’est pas du code de qualité production, mais cela montre l’idée
Après avoir vérifié l’appartenance à une organisation/un groupe GitHub, si l’utilisateur existe et est mappé avec quelque chose comme
nss-ato, on peut autoriser sa connexion au serveurLors de l’onboarding/offboarding des personnes, il suffit de les ajouter/supprimer du groupe GitHub pour accorder ou révoquer l’accès aux machines, ce qui réduit la friction
Sur Amazon Linux 2 et versions antérieures, il appelle la ligne de commande
opensslpour vérifier le format de chaque clé dans le fichierauthorized_keys, mais c’est codé en dur pour RSA ; même si la version d’OpenSSH prend en chargeed25519, il était donc impossible de s’authentifier aveced25519sur un hôte Amazon Linux 2En théorie, cela permet des fonctionnalités intéressantes¹, ce qui est bien, mais en pratique cela casse les fonctions de base même pour ceux qui n’utilisent pas ces fonctionnalités, et rend Amazon Linux moins digne de confiance
La première fois que j’ai rencontré ce problème, j’essayais de me connecter en SSH à une machine appartenant à un collègue DevOps cloud-first, et comme je ne pouvais pas y toucher directement, c’était difficile à diagnostiquer
Il connaît bien AWS mais moins Linux, donc il ne savait pas où regarder ; il avait choisi Amazon Linux en pensant que, puisque c’était une distribution créée par le propriétaire de la plateforme cloud, elle serait « plus compatible », mais ici « plus compatible » signifiait en réalité « davantage de surprises stupides »
¹ https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/connect-...
Comme « le réseau ne marche pas correctement » est mon périmètre, je passe à côté de ce genre de fonctionnalités sympas
Beaucoup de gens ignorent probablement que le parseur de configuration d’OpenSSH ignore les directives en doublon et que, parmi des directives identiques, seule la première prend effet
Dans les parseurs de configuration ou les moteurs de règles, il est souvent courant que les directives plus loin dans le fichier écrasent les précédentes, donc c’est assez contre-intuitif
Cela peut sembler anodin, mais lorsqu’ils modifient des valeurs par défaut comme celles de
/etc/ssh/sshd_config, les humains comme les logiciels ont tendance à ajouter les changements à la fin du fichier ou d’un bloc de directives, et s’attendent à ce qu’ils soient appliquésDes sociétés de sécurité, des organisations et plusieurs produits de bastion SSH se trompent aussi là-dessus ; les recommandations CIS Benchmarks et la plupart des outils d’audit CIS tiers ne prennent pas en compte la priorité, ou la traitent mal
Pour vérifier que les directives de configuration OpenSSH sont définies comme prévu, il ne faut pas parcourir le fichier de configuration à la main, mais dumper la configuration interne dérivée avec
sshd -Toussh -Gsudoersfonctionne de la même manièrePour les logiciels d’authentification/autorisation utilisateur, je trouve ce fonctionnement souhaitable
Parce qu’il permet de créer facilement des paramètres qu’on ne peut pas écraser en ajoutant un nouveau fichier dans un répertoire
whatever.conf.dIl suffit de définir ce paramètre dans le fichier de configuration principal avant de charger
whatever.conf.d/*, puis d’appliquer une protection spéciale à ce fichierMême si personne ne cherche à contourner le contrôle, c’est avantageux pour la gestion de configuration : si un nouvel arrivant qui ne connaît pas tout le contexte ajoute un nouveau fichier, ou si un paquet installe des valeurs par défaut étranges pour son service, la configuration de référence conserve la priorité
Si l’on voit plus souvent le comportement inverse dans d’autres contextes, c’est parce que ce qu’on veut alors n’est pas une « configuration de référence », mais des valeurs par défaut au sens strict, utilisées quand l’utilisateur/le développeur/l’administrateur n’a rien configuré explicitement
AllowUsersCela dit, hier, NixOS a soudainement changé l’ordre de fusion, et le
AllowUsersde mon fichier s’est retrouvé sous unMatchdans un autre fichier, ce qui m’a bloquéLa phrase la plus importante et la plus concise de l’article est l’explication selon laquelle, avec
-L, en forwarding local, le local est à gauche de l’adresse, tandis qu’avec-R, en forwarding distant, le port local est à droite de l’adresseDepuis le début, je mélangeais
-Let-R, et le fait que l’instance de port qui est locale change selon L/R est assez pénibleJe comprends que
-Let-Rchangent la direction voulue, c’est-à-dire où se trouvent l’initiateur et le répondant, mais on aurait pu faire en sorte queport:address:portsignifie toujourslocal:binding:remote, et que-L/-Rdéterminent quel côté écoute et quel côté émet-Lécoute sur le port local indiqué juste après, et que-Récoute sur le port distant indiqué juste aprèsLe reste,
host:port, n’est que le format habituel qui indique où se connecterComme il s’agit de faire du port forwarding via un tunnel SSH, il en découle naturellement que l’hôte est contacté depuis l’autre côté du tunnel par rapport à celui où se trouve le port d’écoute
SSH dispose d’une fonctionnalité peu connue mais utile : la multiplexion de connexion.
Au lieu de créer une nouvelle connexion TCP et de repasser par la procédure d’authentification, on peut réutiliser une connexion existante.
Le protocole lui-même possède une notion de canal, et chaque trame de données comporte des métadonnées permettant de distinguer les différents flux ; cette fonctionnalité s’appuie dessus.
Le gros avantage, c’est que les sessions suivantes n’ont pas besoin de refaire toute l’authentification.
C’est particulièrement appréciable quand il n’y a pas
tmuxou équivalent sur la machine distante et qu’on utilise plusieurs fenêtres de terminal comme plusieurs panneaux, et encore plus sensible si l’authentification implique une phrase de passe ou un toucher sur HSM qui prend quelques secondes.Il existe aussi un réglage de « persistance de connexion », qui évite de se réauthentifier à chaque fois qu’on passe d’un serveur à l’autre.
Globalement, je vois ça comme une fonctionnalité agréable à avoir, sans être au point de changer la vie.
Sur certains serveurs, elle est désactivée, et son absence se remarque davantage quand elle est désactivée que sa présence quand elle est activée.
En savoir plus : https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing
Contrairement à TLS, optimisé pour réduire le nombre d’allers-retours, SSH est un protocole assez bavard, et cette option de multiplexion est presque la seule exception.
ProxyJump, cela peut rendre Ansible beaucoup plus agréable à utiliser.Si vous avez beaucoup d’hôtes dans
~/.ssh/config, la directiveInclude, qui prend en charge les jokers, permet d’éviter que le fichier devienne trop fouillis.Par exemple, on peut mettre
Include config.d/*.confdans~/.ssh/config, puis répartir les réglageshost,hostnameetuserdans des fichiers comme~/.ssh/config.d/work.confouclient1.conf.Hostprend elle aussi en charge les jokers.Par exemple, on peut ajouter
host *_worket y mettre les réglages communs à tous les hôtes de travail, commehost1_work.Includeà l’intérieur d’une directiveHost/Match.Par exemple, avec
Host proj1.*.corpetInclude ~/.ssh/proj1.confdans~/.ssh/config, on peut garder les correspondances par projet près du haut du fichier et réduire le besoin de fouiller dans une multitude de fichiers séparés lors des revues.Pour le forwarding, je n’utilise presque jamais
-f.Cela peut devenir un fusil pointé sur ses pieds, parce que ça rend difficile de savoir quels forwardings sont encore ouverts et actifs.
-test une astuce élégante, et c’était une fonctionnalité que je ne connaissais pas.Un point important, facile à manquer dans la liste des commandes d’échappement
~, est qu’on peut aussi imbriquer les échappements dans des sessions imbriquées.C’est utile quand, pour une raison quelconque, on n’utilise pas
-J.La liste colle bien aux usages utiles, et j’y ai aussi appris quelques trucs.
-test excellent. Je l’utilise comme ceci :ssh -t my-dev-vps 'tmux new-session -A -s main', pour revenir directement à l’emplacement précédent de ma session tmux à chaque exécution.J’aimerais que SSH expose l’état des forwardings de façon raisonnable, autrement qu’en allant fouiller soi-même dans la liste des processus.
Il y a actuellement une pull request qui ajoute la prise en charge de
AF_UNIX, et si elle est intégrée, elle rendra possibles toutes sortes de forwardings intéressants.Il deviendra plus facile de proxifier une connexion SSH via n’importe quel processus local, ce processus pouvant ensuite gérer comme il le souhaite la transmission des données jusqu’à l’extrémité distante.
https://github.com/openssh/openssh-portable/pull/431
-DavecAF_UNIX, mais c’est une bonne chose que tout puisse fonctionner au-dessus deAF_UNIX.Depuis environ un an,
curlsemble pouvoir utiliser SOCKS surAF_UNIXvia la syntaxeALL_PROXYsocks5://localhost/pathousocks5h.Cela semble avoir été ajouté parce que Tor utilise un proxy SOCKS
AF_UNIX.Ce serait bien de pouvoir configurer l’accès réseau avec les permissions Unix standard, et personnellement j’aimerais encore mieux pouvoir écarter complètement TCP/IP du noyau.
La première fois que j’ai vu la console SSH, j’ai été sidéré.
Un collègue m’a montré
~#, et j’ai eu l’impression de découvrir un menu secret de triche tout droit sorti d’un jeu SEGA Genesis.Pourquoi le tilde ? Parce que
rloginetrshl’utilisaient.Pourquoi
rloginetrshutilisaient-ils le tilde ? Parce quecul’utilisait.Pourquoi
cu? Quand on avait un modem ou une ligne série, on communiquait aveccu, et il fallait envoyer des codes Hayes ; or, en utilisant la séquence d’échappement des codes Hayes, on sortait vers le modem, donc il fallait un signal distinct pour sortir verscu.Pourquoi pas
^[? Parce que c’est telnet.Donc si l’on se connectait à un hôte via telnet, puis à un modem via
cu, il fallait une syntaxe d’échappement distincte pour revenir àcusans sortir vers telnet.Au final, c’est une pile infinie de syntaxes d’échappement.
Et en réalité, ce n’est pas un tilde, c’est un tilde.
CR, tilde,.; est-ce que je l’ai mal utilisé tout ce temps ?La section sur
ssh-copy-idexplique que la commande téléverse la clé publique, puis passe soudain à l’idée qu’elle téléverse la clé privée ; ça ressemble à une coquille.De plus, cette commande ne se contente pas de téléverser une clé : elle l’ajoute à
~/.ssh/authorized_keys, ce qui la rend beaucoup plus utile.Enfin, dans la section
ssh-keygen, d’après ce que j’ai lu récemment,ed25519est aujourd’hui préféré àecdsa.