SSHリモートコマンドライン引数はどのように解析されますか


11

リモートsshコマンドへの引数を二重にエスケープする必要があるという質問と回答を見てきました。私の質問は次のとおりです。正確に、いつ、2番目の解析が行われますか?

次を実行すると:

$ ssh otherhost pstree -a -p

次の出力が表示されます。

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

リモートコマンド(pstree)の親プロセスはsshdであり、リモートコマンドへのコマンドライン引数を解析するシェルがないように見えるため、二重引用符またはエスケープが必要であるように見えません(しかし、それは間違いなくです)。代わりに最初にそこにsshし、ログインシェルを取得して実行するpstree -a -pと、出力に次のように表示されます。

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

したがってbash、その場合にコマンドライン解析を実行するシェルがそこにあるのは明らかです。しかし、リモートコマンドを直接使用する場合、シェルがないように見えるので、なぜ二重引用符が必要なのでしょうか。

回答:


22

常にリモートシェルがあります。SSHプロトコルでは、クライアントはサーバーに実行する文字列を送信します。SSHコマンドラインクライアントは、コマンドライン引数を取り、引数間のスペースでそれらを連結します。サーバーはその文字列を取得し、ユーザーのログインシェルを実行して、その文字列を渡します。

リモートシェルをバイパスすることはできません。プロトコルには、サーバーでargv配列として解析できる文字列の配列を送信するようなものはありません。また、SSHサーバーはリモートシェルをバイパスしません。これは、セキュリティ上の制限がある可能性があるためです。ユーザーのシェルとして制限付きプログラムを使用すると、特定のコマンド(rsync専用アカウントまたはgitのみのアカウント)。

シェルpstreeが既になくなっているため、シェルが表示されない場合があります。多くのシェルには、「この外部コマンドを実行し、完了するまで待機し、コマンドのステータスで終了する」ことを検出すると、execve代わりに「この外部コマンドの」を実行する最適化があります。これが最初の例で起こっていることです。次の3つのコマンドと比較してください。

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

最初の2つは同一です。クライアントはまったく同じデータをサーバーに送信します。3つ目は、シェルのexec最適化を無効にするシェルコマンドを送信します。


2
ハ!あなたが私自身の質問に答えるために私を倒したなんて信じられません。質問の投稿の途中でそれを理解し、自分で質問して答えるだけで済むようになりました。
18年

10

私はそれを理解したと思います:

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

の引数pstreeは次のとおりです。コマンドライン引数の表示、pidの表示、および指定されたpidの親プロセスのみの表示。'$$'bashは、コマンドライン引数を評価する際bashは、独自のpidと交換することを特別なシェル変数です。ローカルシェルによって解釈されないようにするために、一度引用されています。ただし、リモートシェルで解釈できるように二重引用符やエスケープされていません。

ご覧のとおり、これは12001シェルの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

:この場合、私は2つのコマンドを実行することを要求していますpstreeが続きますecho。そしてbash、実際にはプロセスツリーにの親として表示されることがわかりますpstree


うん !+ 1.それは、Gillesが最初により正式に置き、2番目に例示したものを例示しています。おそらく、彼に彼の初期の答えを称賛するのは正しいでしょうか?
Cbhihe

0

他の答えが言ったことをサポートして、リモートでコマンドを呼び出すコードを調べました、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と2番目の引数を使用して無条件に呼び出しますcommand。以前は、shell変数はに記録されて/etc/passwdいるようにユーザーのログインシェルに設定されていました。 commandはこの関数の引数であり、最終的にはネットワークから逐語的に読み取られる文字列に設定されます(session_exec_req同じファイルを参照)。そのため、サーバーはコマンドをまったく解釈しませんが、シェルは常にリモートで呼び出されます。

しかし、SSHプロトコル仕様の関連部分がないではない、この動作を必要とするように思われます。それは言うだけです

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

このメッセージは、サーバーが指定されたコマンドの実行を開始することを要求します。'command'文字列にはパスを含めることができます。不正なコマンドの実行を防ぐために、通常の予防策を講じる必要があります。

これはおそらく、すべてのオペレーティングシステムにコマンドラインシェルの概念があるわけではないためです。たとえば、Classic MacOSのsshサーバーが "exec"コマンド文字列をAppleScriptインタープリターに渡すのは、おかしくありませんでした。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.