リモートの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
シェルが存在することは明らかです。しかし、リモートコマンドを直接使用する場合、シェルがないように見えるので、なぜ二重引用符が必要なのでしょうか。
常にリモートシェルがあります。 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
の親としてプロセスツリーに表示されることがわかります。
他の答えが言ったことをサポートして、リモートでコマンドを呼び出すコードを調べました 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 インタープリターにフィードするのはおかしくありませんでした。