ssh远程命令行参数如何解析

ssh远程命令行参数如何解析

我已经看到了有关需要双重转义远程 ssh 命令参数的问题和答案。我的问题是:第二次解析到底在何时何地完成?

如果我运行以下命令:

$ ssh otherhost pstree -a -p

我在输出中看到以下内容:

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

远程命令 ( pstree) 的父进程是sshd,那里似乎没有任何 shell 会解析远程命令的命令行参数,因此似乎不需要双引号或转义(但确实如此)。相反,如果我先 ssh 并获取登录 shell,然后运行,pstree -a -p我会在输出中看到以下内容:

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

很明显,有一个bashshell 可以在这种情况下进行命令行解析。但是我直接使用远程命令的情况下,好像没有shell,那为什么需要双引号呢?

答案1

总是有一个远程 shell。在SSH协议中,客户端向服务器发送一个要执行的字符串。 SSH 命令行客户端获取其命令行参数,并用参数之间的空格将它们连接起来。服务器获取该字符串,运行用户的登录 shell 并将该字符串传递给它。 (更准确地说:服务器运行在用户数据库中注册为用户 shell 的程序,向其传递两个命令行参数:-c以及客户端发送的字符串。该 shell 不会作为登录 shell 被调用:服务器不会将第零个参数设置为以 开头的字符串-。)

绕过远程 shell 是不可能的。该协议没有发送可在服务器上解析为 argv 数组的字符串数组之类的内容。并且 SSH 服务器不会绕过远程 shell,因为这可能是安全限制:使用受限程序作为用户的 shell 是提供仅允许运行某些命令的受限帐户的一种方式(例如仅 rsync 帐户或仅限 git 的帐户)。

您可能看不到外壳,pstree因为它可能已经消失了。许多 shell 都有一个优化,如果它们检测到即将执行“运行此外部命令,等待其完成,并以命令状态退出”,则 shell 运行“execve这个外部命令”代替。这就是您的第一个示例中发生的情况。对比以下三个命令:

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

前两者是相同的:客户端向服务器发送完全相同的数据。第三个发送一个 shell 命令,该命令破坏了 shell 的 exec 优化。

答案2

我想我已经明白了:

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

参数为pstree:显示命令行参数、显示 pid 以及仅显示给定 pid 的父进程。这'$$'是一个特殊的 shell 变量,当 bash 计算命令行参数时,bash 将用它自己的 pid 替换它。它被引用一次以阻止它被我的本地 shell 解释。但它没有被双引号或转义以允许它被远程 shell 解释。

正如我们所看到的,它被替换为12001因此,这就是 shell 的 pid。从输出中我们还可以看到:pstree,12001pid为12001的进程就是pstree本身。pstree壳也这样吗?

我收集到的情况是,它bash正在被调用,并且正在解析命令行参数,但随后它会调用exec以将其自身替换为正在运行的命令。

似乎它只在单个远程命令的情况下执行此操作:

$ 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

在本例中,我请求运行两个命令:pstree后跟echo.我们可以在这里看到,实际上它bash确实作为 的父级出现在进程树中pstree

答案3

支持其他答案所说的内容,我查找了调用远程命令的代码,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);

...如您所见,它无条件地调用shell第一个参数-c和第二个参数command。早些时候,该shell变量被设置为用户的登录 shell,如 中所记录/etc/passwdcommand是此函数的一个参数,最终被设置为逐字读取的字符串(请参阅session_exec_req在同一个文件中)。因此,服务器根本不解释该命令,但始终在远程调用 shell。

但是,那SSH协议规范的相关部分不是似乎需要这种行为;它只说

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

该消息将请求服务器开始执行给定的命令。 “命令”字符串可能包含路径。必须采取正常的预防措施来防止执行未经授权的命令。

这可能是因为并非所有操作系统都有命令行 shell 的概念。例如,经典 MacOS ssh 服务器将“exec”命令字符串提供给苹果脚本代替口译员。

相关内容