web-dev-qa-db-ja.com

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

リモートの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シェルが存在することは明らかです。しかし、リモートコマンドを直接使用する場合、シェルがないように見えるので、なぜ二重引用符が必要なのでしょうか。

11
onlynone

常にリモートシェルがあります。 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最適化を無効にするシェルコマンドを送信します。

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

$ 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の親としてプロセスツリーに表示されることがわかります。

10
onlynone

他の答えが言ったことをサポートして、リモートでコマンドを呼び出すコードを調べました https://github.com/openssh/openssh-portable/blob/4f29309c4cb19bcb1774931db84cacc414f17d29/session.c#L166 ...

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);

...ご覧のとおり、最初の引数-cと2番目の引数Shellを使用して無条件に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 インタープリターにフィードするのはおかしくありませんでした。

0
zwol