Comment les arguments de ligne de commande distante ssh sont-ils analysés

11

J'ai vu les questions et réponses sur la nécessité de double-échapper aux arguments des commandes ssh distantes. Ma question est: exactement où et quand la deuxième analyse est-elle effectuée?

Si je lance ce qui suit:

$ ssh otherhost pstree -a -p

Je vois ce qui suit dans la sortie:

  |-sshd,3736
  |   `-sshd,1102
  |       `-sshd,1109
  |           `-pstree,1112 -a -p

Le processus parent de la commande distante ( pstree) est sshd, il ne semble pas y avoir de shell qui analyserait les arguments de la ligne de commande à la commande distante, il ne semble donc pas que des guillemets doubles ou des échappements soient nécessaires ( mais c'est certainement). Si au lieu de cela, je ssh d'abord et obtenez un shell de connexion, puis exécutez, pstree -a -pje vois ce qui suit dans la sortie:

  ├─sshd,3736
     └─sshd,3733
         └─sshd,3735
             └─bash,3737
                 └─pstree,4130 -a -p

Il est donc clair qu'il y a un bashshell qui ferait une analyse de ligne de commande dans ce cas. Mais dans le cas où j'utilise directement une commande à distance, il ne semble pas y avoir de shell, alors pourquoi la citation double est-elle nécessaire?

personne
la source

Réponses:

22

Il y a toujours un shell distant. Dans le protocole SSH, le client envoie au serveur une chaîne à exécuter. Le client de ligne de commande SSH prend ses arguments de ligne de commande et les concatène avec un espace entre les arguments. Le serveur prend cette chaîne, exécute le shell de connexion de l'utilisateur et lui transmet cette chaîne.

Il est impossible de contourner le shell distant. Le protocole n'a rien à voir avec l'envoi d'un tableau de chaînes qui pourrait être analysé comme un tableau argv sur le serveur. Et le serveur SSH ne contournera pas le shell distant car cela pourrait être une restriction de sécurité: l'utilisation d'un programme restreint comme shell de l'utilisateur est un moyen de fournir un compte restreint qui n'est autorisé qu'à exécuter certaines commandes (par exemple un compte rsync uniquement ou un compte git uniquement).

Vous ne pouvez pas voir le shell pstreecar il est peut-être déjà parti. De nombreux shells ont une optimisation où s'ils détectent qu'ils sont sur le point de faire "exécuter cette commande externe, attendre qu'elle se termine et quitter avec l'état de la commande", puis le shell exécute " execvede cette commande externe" à la place. C'est ce qui se passe dans votre premier exemple. Comparez les trois commandes suivantes:

ssh otherhost pstree -a -p
ssh otherhost 'pstree -a -p'
ssh otherhost 'pstree -a -p; true'

Les deux premiers sont identiques: le client envoie exactement les mêmes données au serveur. Le troisième envoie une commande shell qui annule l'optimisation d'exécution du shell.

Gilles 'SO- arrête d'être méchant'
la source
2
Ha! Je ne peux pas croire que vous m'ayez battu pour répondre à ma propre question. Je l'ai compris à mi-chemin de la publication de la question et j'ai pensé que je devrais simplement passer par la poser et y répondre moi-même.
seulement le
10

Je pense que je l'ai compris:

$ ssh otherhost pstree -a -p -s '$$'
init,1         
  `-sshd,3736
      `-sshd,11998
          `-sshd,12000
              `-pstree,12001 -a -p -s 12001

Les arguments à pstreesont les suivants: afficher les arguments de la ligne de commande, afficher les pids et afficher uniquement les processus parents du pid donné. Il '$$'s'agit d'une variable shell spéciale que bash remplacera par son propre pid lorsque bash évalue les arguments de la ligne de commande. Il est cité une fois pour l'empêcher d'être interprété par mon shell local. Mais il n'est pas doublement cité ou échappé pour qu'il puisse être interprété par le shell distant.

Comme nous pouvons le voir, il est remplacé par 12001donc c'est le pid de la coque. Nous pouvons également voir à la sortie: pstree,12001que le processus avec un pid de 12001 est pstree lui-même. La pstreecoquille aussi?

Ce que je comprends, c'est qu'il bashest invoqué et qu'il analyse les arguments de la ligne de commande, mais il invoque ensuite execpour se remplacer par la commande en cours d'exécution.

Il semble qu'il ne le fasse que dans le cas d'une seule commande à distance:

$ ssh otherhost pstree -a -p -s '$$' \; echo hi
init,1         
  `-sshd,3736
      `-sshd,17687
          `-sshd,17690
              `-bash,17691 -c pstree -a -p -s $$ ; echo hi
                  `-pstree,17692 -a -p -s 17691
hi

Dans ce cas, je demande l'exécution de deux commandes: pstreesuivi de echo. Et nous pouvons voir ici que bashcela apparaît en fait dans l'arbre de processus en tant que parent de pstree.

personne
la source
Ouaip ! + 1. Cela illustre ce que Gilles a mis plus formellement en premier et en second lieu. Peut-être que lui donner crédit pour sa réponse précoce est en règle?
Cbhihe
0

Soutenant ce que les autres réponses ont dit, j'ai recherché le code qui appelle des commandes sur la télécommande, https://github.com/openssh/openssh-portable/blob/4f29309c4cb19bcb1774931db84cacc414f17d29/session.c#L1660 ...

1660    /*
1661     * Execute the command using the user's shell.  This uses the -c
1662     * option to execute the command.
1663     */
1664    argv[0] = (char *) shell0;
1665    argv[1] = "-c";
1666    argv[2] = (char *) command;
1667    argv[3] = NULL;
1668    execve(shell, argv, env);
1669    perror(shell);
1670    exit(1);

... qui, comme vous pouvez le voir, invoque inconditionnellement shellavec le premier argument -cet le deuxième argument command. Auparavant, la shellvariable a été définie sur le shell de connexion de l'utilisateur comme enregistré dans /etc/passwd. commandest un argument de cette fonction, et est finalement défini sur une chaîne lue textuellement sur le fil (voir session_exec_reqdans le même fichier ). Ainsi, le serveur n'interprète pas du tout la commande, mais un shell est toujours appelé sur la télécommande.

Cependant, la partie pertinente de la spécification du protocole SSH ne pas semble exiger ce comportement; ça dit seulement

 byte      SSH_MSG_CHANNEL_REQUEST
 uint32    recipient channel
 string    "exec"
 boolean   want reply
 string    command

Ce message demandera au serveur de démarrer l'exécution de la commande donnée. La chaîne de commande peut contenir un chemin. Des précautions normales DOIVENT être prises pour empêcher l'exécution de commandes non autorisées.

C'est probablement parce que tous les systèmes d'exploitation n'ont pas le concept d'un shell de ligne de commande. Par exemple, il n'aurait pas été fou qu'un serveur ssh MacOS classique fournisse à la place des chaînes de commande "exec" à l' interpréteur AppleScript .

zwol
la source