我已经看到了有关需要双重转义远程 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
很明显,有一个bash
shell 可以在这种情况下进行命令行解析。但是我直接使用远程命令的情况下,好像没有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,12001
pid为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/passwd
。 command
是此函数的一个参数,最终被设置为逐字读取的字符串(请参阅session_exec_req
在同一个文件中)。因此,服务器根本不解释该命令,但始终在远程调用 shell。
但是,那SSH协议规范的相关部分做不是似乎需要这种行为;它只说
byte SSH_MSG_CHANNEL_REQUEST uint32 recipient channel string "exec" boolean want reply string command
该消息将请求服务器开始执行给定的命令。 “命令”字符串可能包含路径。必须采取正常的预防措施来防止执行未经授权的命令。
这可能是因为并非所有操作系统都有命令行 shell 的概念。例如,经典 MacOS ssh 服务器将“exec”命令字符串提供给苹果脚本代替口译员。