6 points par GN⁺ 2023-08-24 | 1 commentaires | Partager sur WhatsApp
  • 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.intvuln-server.intinternal-web.intdns.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

 
GN⁺ 2023-08-24
Avis de Hacker News
  • Il manque ici une directive étonnamment simple : configurer sshd_config avec quelque chose comme AuthorizedKeysCommand /usr/bin/php /etc/ssh/auth.php %u, puis faire en sorte que le script récupère https://github.com/{$user}.keys sur GitHub
    Bien 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 serveur
    Lors 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

    • Amazon Linux fait quelque chose de similaire, mais c’est probablement beaucoup plus complexe au motif que c’est de qualité production
      Sur Amazon Linux 2 et versions antérieures, il appelle la ligne de commande openssl pour vérifier le format de chaque clé dans le fichier authorized_keys, mais c’est codé en dur pour RSA ; même si la version d’OpenSSH prend en charge ed25519, il était donc impossible de s’authentifier avec ed25519 sur un hôte Amazon Linux 2
      En 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-...
    • Quand je vois ce genre de choses, je regrette d’être du côté réseau
      Comme « le réseau ne marche pas correctement » est mon périmètre, je passe à côté de ce genre de fonctionnalités sympas
    • Pour cet usage, il vaut mieux utiliser des certificats SSH à la place
  • 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és
    Des 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 -T ou ssh -G

    • Le fichier sudoers fonctionne de la même manière
      Pour 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.d
      Il 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 fichier
      Mê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
    • Certaines directives semblent pouvoir être répétées. Par exemple AllowUsers
      Cela dit, hier, NixOS a soudainement changé l’ordre de fusion, et le AllowUsers de mon fichier s’est retrouvé sous un Match dans 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’adresse
    Depuis le début, je mélangeais -L et -R, et le fait que l’instance de port qui est locale change selon L/R est assez pénible
    Je comprends que -L et -R changent la direction voulue, c’est-à-dire où se trouvent l’initiateur et le répondant, mais on aurait pu faire en sorte que port:address:port signifie toujours local:binding:remote, et que -L/-R déterminent quel côté écoute et quel côté émet

    • Le plus simple est de retenir que -L écoute sur le port local indiqué juste après, et que -R écoute sur le port distant indiqué juste après
      Le reste, host:port, n’est que le format habituel qui indique où se connecter
      Comme 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 tmux ou é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

    • Si la latence entre le client et le serveur est élevée, la différence est énorme.
      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.
    • Avec un hôte bastion ProxyJump, cela peut rendre Ansible beaucoup plus agréable à utiliser.
  • Si vous avez beaucoup d’hôtes dans ~/.ssh/config, la directive Include, qui prend en charge les jokers, permet d’éviter que le fichier devienne trop fouillis.
    Par exemple, on peut mettre Include config.d/*.conf dans ~/.ssh/config, puis répartir les réglages host, hostname et user dans des fichiers comme ~/.ssh/config.d/work.conf ou client1.conf.

    • La directive Host prend elle aussi en charge les jokers.
      Par exemple, on peut ajouter host *_work et y mettre les réglages communs à tous les hôtes de travail, comme host1_work.
    • Autre astuce : on peut placer Include à l’intérieur d’une directive Host/Match.
      Par exemple, avec Host proj1.*.corp et Include ~/.ssh/proj1.conf dans ~/.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.
    -t est 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.

    • -t est 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.
    • Le même problème demeure si plusieurs shells sont ouverts sur le serveur cible.
      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

    • Ce qui m’intéresse, c’est -D avec AF_UNIX, mais c’est une bonne chose que tout puisse fonctionner au-dessus de AF_UNIX.
      Depuis environ un an, curl semble pouvoir utiliser SOCKS sur AF_UNIX via la syntaxe ALL_PROXY socks5://localhost/path ou socks5h.
      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 rlogin et rsh l’utilisaient.
    Pourquoi rlogin et rsh utilisaient-ils le tilde ? Parce que cu l’utilisait.
    Pourquoi cu ? Quand on avait un modem ou une ligne série, on communiquait avec cu, 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 vers cu.
    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 à cu sans 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.

    • Je pensais que c’était dans l’ordre CR, tilde, . ; est-ce que je l’ai mal utilisé tout ce temps ?
  • La section sur ssh-copy-id explique 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, ed25519 est aujourd’hui préféré à ecdsa.