Yuri
2020-Jan-11 09:59 UTC
Why are the arguments supplied for the command run through ssh interpreted by shell before they are passed to the command on the server side?
On 2020-01-11 01:38, Darren Tucker wrote:> The command you give is always handled on the server by your shell in some > fashion. It has to be, because SSH only specifies an opaque string for the > remote command, so without doing so you would not be able to specify > arguments at all.It's not obvious why does it have to be this way. ssh sends the command as an array of strings. The first string is the command, and the subsequent strings are arguments. It can easily call the same command with the same arguments on the remote host. Also this sentence from the man page seems to be false: > If a command is specified, it is executed on the remote host instead of a login shell. Login shell still interprets the command. Interestingly, the shell process isn't running on the remote host, the command is a direct child of sshd. Yuri
Darren Tucker
2020-Jan-11 12:46 UTC
Why are the arguments supplied for the command run through ssh interpreted by shell before they are passed to the command on the server side?
On Sat, 11 Jan 2020 at 20:59, Yuri <yuri at rawbw.com> wrote: [...]> It's not obvious why does it have to be this way. ssh sends the command > as an array of strings. > > The first string is the command, and the > subsequent strings are arguments. It can easily call the same command > with the same arguments on the remote host.Why do you say that? From RFC 4254 section 6.5: """ byte SSH_MSG_CHANNEL_REQUEST uint32 recipient channel string "exec" boolean want reply string command This message will request that the server start the execution of the given command. The 'command' string may contain a path. Normal precautions MUST be taken to prevent the execution of unauthorized commands. """ Note the single string specifying the command.> Also this sentence from the man page seems to be false: > > > If a command is specified, it is executed on the remote host instead > of a login shell. > > Login shell still interprets the command.No, it's interpreted by a non-login shell. A login shell is a subset of shell invocations with some specific behaviour. The bash man page describes some of this: """ INVOCATION A login shell is one whose first character of argument zero is a -, or one started with the --login option. [...] Login shells: On login (subject to the -noprofile option): if /etc/profile exists, source it. if ~/.bash_profile exists, source it, else if ~/.bash_login exists, source it, else if ~/.profile exists, source it. On exit: if ~/.bash_logout exists, source it. """> Interestingly, the shell > process isn't running on the remote host, the command is a direct child > of sshd.That's probably because the shell is skipping the fork in the simple command case as an optimization. If you repeat the experiment with a non-trivial command you'll see something like this: $ ssh localhost "sleep 1000 | sleep 1000" [...] $ ps -eaf | grep "sleep 1000" dtucker 22321 1 0 22:56 ? 00:00:00 bash -c sleep 1000 | sleep 1000 [...] $ pstree -s -p 22355 systemd(1)---bash(22321)---sleep(22355) -- Darren Tucker (dtucker at dtucker.net) GPG key 11EAA6FA / A86E 3E07 5B19 5880 E860 37F4 9357 ECEF 11EA A6FA (new) Good judgement comes with experience. Unfortunately, the experience usually comes from bad judgement.
Colin Watson
2020-Jan-11 13:38 UTC
Why are the arguments supplied for the command run through ssh interpreted by shell before they are passed to the command on the server side?
I do think it's a problem that, as far as I can see, the exact behaviour of ssh command interpretation isn't explained anywhere in its man pages, even though it seems like the only possibility if you understand the protocol. ssh(1) just says "If command is specified, it is executed on the remote host instead of a login shell", but that doesn't provide any hint about what quoting of metacharacters might be required, which is important since ssh is often used as plumbing. Now, I understand that ssh doesn't itself control how the command is parsed: it just executes the user's shell with -c and the command as arguments (passing the whole command as a single argument). However, that's not the only possible interpretation of the bit from ssh(1) that I quote above, and it would be helpful to clarify the documentation to say so explicitly. People who need to work out subtle details of quoting rules could then at least know to consult the documentation of the appropriate shell. Another thing that's poorly-explained in ssh(1) is the handling of the case where the command is passed to ssh as multiple arguments (e.g. 'ssh host echo foo' rather than 'ssh host "echo foo"'). The behaviour is that all the arguments are concatenated into a single string with a space character between them, but as far as I can see ssh(1) makes no mention of this whatsoever and so I don't think this is well-understood. It's important to explain this because the following example is not handled in the way that one might naturally expect: ssh host cat 'path with spaces' The command sent to the server is the string "cat path with spaces", which will then typically be split into four words by the shell at the other end. If you want to preserve the quoting then you need to write it as something like this: ssh host "cat 'path with spaces'" -- Colin Watson [cjwatson at debian.org]
Thorsten Glaser
2020-Jan-11 16:57 UTC
Why are the arguments supplied for the command run through ssh interpreted by shell before they are passed to the command on the server side?
On Sat, 11 Jan 2020, Yuri wrote:> It's not obvious why does it have to be this way. ssh sends the command as an > array of strings. The first string is the command, and the subsequent stringsIt doesn?t. This works: 94 ssh root@$hostbase " 95 PS4='(${hostbase%%.*})++++ ' 96 set -ex 97 psql -U veraweb -h 127.0.0.1 veraweb 98 rm -rf /var/lib/tomcat$tomcat/webapps/veraweb 99 rm -rf /var/lib/tomcat$tomcat/webapps/vwor 100 " <core/src/main/files/upgrade.sql Here, $hostbase and $tomcat are expanded locally. Full code: https://evolvis.org/plugins/scmgit/cgi-bin/gitweb.cgi?p=veraweb/veraweb.git;a=blob;f=upload.sh;h=b8b0cf92ad36dbc929afd86effe44d9bec888344;hb=HEAD If you wish for no local expansion, quote locally, such as: ssh -l luser remotehost ' command1 command2 ? ' Quote any ?'? inside as ?'\''?, that?s it. (Easily done, for example in AT&T ksh93/GNU bash/mksh: ${cmd//\'/\'\\\'\'}) bye, //mirabilos -- tarent solutions GmbH Rochusstra?e 2-4, D-53123 Bonn ? http://www.tarent.de/ Tel: +49 228 54881-393 ? Fax: +49 228 54881-235 HRB 5168 (AG Bonn) ? USt-ID (VAT): DE122264941 Gesch?ftsf?hrer: Dr. Stefan Barth, Kai Ebenrett, Boris Esser, Alexander Steeg
Yuri
2020-Jan-11 17:03 UTC
Why are the arguments supplied for the command run through ssh interpreted by shell before they are passed to the command on the server side?
On 2020-01-11 08:57, Thorsten Glaser wrote:> If you wish for no local expansion, quote locally, such as: > > ssh -l luser remotehost ' > command1 > command2 > ? > 'This didn't work for me because single quotes only prevent local expansion. The string is expanded on the remote host. Yuri
Jeremy Lin
2020-Jan-11 22:52 UTC
Why are the arguments supplied for the command run through ssh interpreted by shell before they are passed to the command on the server side?
On Sat, Jan 11, 2020 at 2:02 AM Yuri <yuri at rawbw.com> wrote:> > On 2020-01-11 01:38, Darren Tucker wrote: > > The command you give is always handled on the server by your shell in some > > fashion. It has to be, because SSH only specifies an opaque string for the > > remote command, so without doing so you would not be able to specify > > arguments at all. > > It's not obvious why does it have to be this way. ssh sends the command > as an array of strings. The first string is the command, and the > subsequent strings are arguments. It can easily call the same command > with the same arguments on the remote host.While it's important to understand how local shell processing works (and this is obviously out-of-scope of the ssh(1) man page), I think ssh(1) doesn't adequately explain how the "command" is executed on the remote side, and how a command is formed from multiple command tokens.>From https://github.com/openssh/openssh-portable/blob/ed3ad71b17adcd1fb4431d145f53cee1c6a1135e/ssh.c#L1069-L1072,you can see that multiple command tokens are simply joined with spaces into a single actual "command". That implies that the remote side sees all of the following variations as exactly equivalent: $ ssh host 'printargs "foo bar" baz | cat' # ssh sees a single command token locally $ ssh host printargs '"foo' 'bar"' baz \| cat # ssh sees 6 command tokens locally $ ssh host printargs '"foo bar" baz |' cat # ssh sees 3 command tokens locally>From https://github.com/openssh/openssh-portable/blob/ed3ad71b17adcd1fb4431d145f53cee1c6a1135e/session.c#L1703-L1711,you can see that these would all result approximately in running this command remotely: $SHELL -c 'printargs "foo bar" baz | cat' I say "approximately" because for simpler presentation, I used shell syntax above (as if you were to manually run the command in a remote shell), but you can see from the source that ssh actually does this via a fork-and-exec, so there would be no single quotes anywhere, for example. The output would look something like this: argv[0] = {/usr/local/bin/printargs} argv[1] = {foo bar} argv[2] = {baz} Here's a bash implementation of printargs: #!/bin/bash for (( i = 0; i <= $#; ++i )); do printf 'argv[%d] = {%s}\n' $i "${!i}" done
Jochen Bern
2020-Jan-13 08:14 UTC
Why are the arguments supplied for the command run through ssh interpreted by shell before they are passed to the command on the server side?
On 01/11/2020 10:59 AM, Yuri wrote:> On 2020-01-11 01:38, Darren Tucker wrote: >> The command you give is always handled on the server by >> your shell in some fashion. > > It's not obvious why does it have to be this way.Because sshd ignoring the target account's configured, possibly restricted, shell and running whatever executable the client asked for would promise to be a backdoor large enough to drive an aircraft carrier through. Sideways. Not to mention that running commands on the server without having the login shell set up the environment - $PATH, $LD_LIBRARY_PATH, etc. etc. - would very likely be an issue no less complicated than figuring out the nested quoting. Regards, -- Jochen Bern Systemingenieur Binect GmbH Robert-Koch-Stra?e 9 64331 Weiterstadt -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/pkcs7-signature Size: 4278 bytes Desc: S/MIME Cryptographic Signature URL: <http://lists.mindrot.org/pipermail/openssh-unix-dev/attachments/20200113/5f729378/attachment.p7s>
Yuri
2020-Jan-13 19:23 UTC
Why are the arguments supplied for the command run through ssh interpreted by shell before they are passed to the command on the server side?
On 2020-01-13 00:14, Jochen Bern wrote:> Because sshd ignoring the target account's configured, possibly > restricted, shell and running whatever executable the client asked for > would promise to be a backdoor large enough to drive an aircraft carrier > through. Sideways. > > Not to mention that running commands on the server without having the > login shell set up the environment - $PATH, $LD_LIBRARY_PATH, etc. etc. > - would very likely be an issue no less complicated than figuring out > the nested quoting.Your objection is really only about how the command is handled, not its arguments, and my concern is mostly how arguments are expanded. So I would reformulate my suggestion: ???? -z ???????????? Disable command arguments expansion. When the command is ???????????? run remotely, ssh passes arguments to the remote command ???????????? verbatim, as they were supplied, without any expansions. The ???????????? command itself is treated the same way as commands are ???????????? treated without the -z argument. Yuri
Reasonably Related Threads
- Why are the arguments supplied for the command run through ssh interpreted by shell before they are passed to the command on the server side?
- The first command of a nested compound command receives no arguments
- Why are the arguments supplied for the command run through ssh interpreted by shell before they are passed to the command on the server side?
- Why are the arguments supplied for the command run through ssh interpreted by shell before they are passed to the command on the server side?
- Why are the arguments supplied for the command run through ssh interpreted by shell before they are passed to the command on the server side?