[HN Gopher] OpenSSH has some peculiar handling around command li...
       ___________________________________________________________________
        
       OpenSSH has some peculiar handling around command line arguments
        
       Author : madworx
       Score  : 146 points
       Date   : 2023-07-14 11:57 UTC (11 hours ago)
        
 (HTM) web link (blog.devops.dev)
 (TXT) w3m dump (blog.devops.dev)
        
       | hfkwer wrote:
       | So many things could be fixed if we didn't insist on backwards
       | compatibility with ancient systems. Shell command line parsing is
       | just one example.
        
         | superq wrote:
         | Backwards compatibility is there for a reason.
         | 
         | So many things could be broken if we didn't insist on backwards
         | compatibility with ancient systems. Virtually everything, for
         | just one example.
         | 
         | Can you imagine if we didn't bother with backwards
         | compatibility for servers? Or even just your desktop - you try
         | to boot one day and find that your desktop doesn't boot because
         | we dropped compatibility with MBR systems and only support
         | UEFI, or that you choose ext4 for your filesystem, but the
         | kernel only supports superqFS version 2023.07 ...
         | 
         | or that SSH backup script that has been working fine for 20
         | years suddenly, silently, stops working, and then your critical
         | production systems die.
         | 
         | Or how about your favorite photo software decided to just
         | change all of its keybindings, or stopped loading your photos
         | -- from last year!
        
           | hfkwer wrote:
           | Your examples aren't relevant at all and full of hyperbole.
           | Are you replying to what I wrote or showing off your oratory
           | skills to the peanuts gallery?
           | 
           | SSH is a protocol. Just have the client negotiate the version
           | of the protocol when establishing a connection. Use the old
           | protocol by default, write new scripts with a flag requiring
           | the new protocol from the server. Boom. You maintain
           | backwards compatibility with ancient systems, but you don't
           | force old mistakes upon current day users.
        
             | ndsipa_pomu wrote:
             | > SSH is a protocol. Just have the client negotiate the
             | version of the protocol when establishing a connection. Use
             | the old protocol by default, write new scripts with a flag
             | requiring the new protocol from the server. Boom. You
             | maintain backwards compatibility with ancient systems, but
             | you don't force old mistakes upon current day users.
             | 
             | The problem isn't so much the protocol (SSH already does
             | the client/server protocol negotiation), but the command
             | line interface to the SSH tool. Changing the interface can
             | stop older scripts from working in the same manner, so
             | either you have to add completely new options to the CLI so
             | that the older usage still works, or you have a renamed
             | version of the tool (e.g. nussh) that won't need to worry
             | about backwards compatibility.
        
               | hfkwer wrote:
               | >Use the old protocol by default, write new scripts with
               | a flag requiring the new protocol from the server.
               | 
               | Next time, read the whole comment before replying.
        
           | CrampusDestrus wrote:
           | Not a problem for free software, just update the old code if
           | stuff breaks ;)
        
             | mcny wrote:
             | > Not a problem for free software, just update the old code
             | if stuff breaks ;)
             | 
             | I know one qa team at Google had the slogan "if it isn't
             | broken, you aren't working hard enough" but they are the qa
             | team. It is their job to find defects before anyone else.
             | 
             | It is not our job as developers to create defects for no
             | reason.
        
             | [deleted]
        
             | superq wrote:
             | If you can even still get the updates on the backwards-
             | incompatible package manager. (as anyone who's ever
             | maintained an Arch system has probably run into, and even
             | Red Hat used to run into this problem way back in the day.)
        
               | CrampusDestrus wrote:
               | well, that's another problem. there is a difference
               | between what the developers see and what the users see.
               | 
               | the developers should have access to the updated API/ABI
               | code and maybe some migration documentation in order to
               | update the program. the user should have a clear
               | migration process to avoid data loss or corruption.
               | 
               | in this case, the package manager's core functionality
               | should not depend upon any other packages. this way you
               | can just store all the previous versions of the program
               | and provide an upgrade path. sure, it will take a bit if
               | you're doing a big upgrade, but that's tour fault for not
               | keeping stuff updated
        
           | brazzledazzle wrote:
           | >suddenly, silently, stops working
           | 
           | Not to detract from your overall point but for anyone out
           | there wondering about a way to handle this in general: your
           | job should update or annotate something (a file, a table, a
           | bucket, etc.) upon success. Then you use a "dead man's
           | switch"-style check/monitor that alerts if the job hasn't
           | updated its proof of life, so to speak.
        
           | sim- wrote:
           | It'd be nice if they didn't break, but for example, since
           | around OpenSSH 7.7, the order argument parsing changed from
           | last wins to first wins. This change broke all of my aliases
           | where I used to be able to have an alias sr="ssh -l root" and
           | override the user with something such as "sr foo@host".
           | 
           | Last wins is how most other UNIX tools work, since it's the
           | laziest approach (parse from first to last and just overwrite
           | any old value). But I guess somebody tried to be explicit
           | with the order of everything from the configuration file
           | including the command line, and now this happened.
           | 
           | I really wish this would be reverted, but now it's probably
           | been so long that it would break things for people again.
        
         | slome wrote:
         | Author doesn't know why this supposed bug can't be fixed and
         | makes a quick assumption. Later on he asks the reader for
         | insights. Crazy.
         | 
         | Just ask the developers directly via the official mailing list
         | [1], or send a bug report as you are invited to do by the
         | developers [2].
         | 
         | [1] https://www.openssh.com/list.html
         | 
         | [2] https://www.openssh.com/report.html
        
         | pif wrote:
         | So many things could _stop working_ if we didn 't insist on
         | backwards compatibility with ancient systems.
        
         | arp242 wrote:
         | The origins of the behaviours might be "ancient systems", but
         | contemporary programs still rely on them. I do wish there was a
         | bit more movement towards fixing some obvious mistakes, but
         | it's not that easy - if it were, it would have been done
         | already.
        
       | johnea wrote:
       | I told the Dr., "Dr. when I do this it hurts!". The Dr. said
       | "Don't do that"...
        
       | Filligree wrote:
       | Reminds me that OpenSSH fails with some insane error indicating
       | memory corruption if you try to run it with tcmalloc, which can
       | happen by accident if you call it from a Python program that uses
       | it.
       | 
       | (tcmalloc is commonly used to fix memory 'leaks' (fragmentation?)
       | in Pytorch, so this happens a lot to me. At least, I've been
       | bitten twice.)
       | 
       | It's hard to imagine what might be going on to make it sensitive
       | to the details of malloc/free... and I'm not sure that I want to.
        
         | citrin_ru wrote:
         | It might be a good idea not to pass current environment
         | (including LD_PRELOAD for tcmalloc) to security sensitive
         | external commands.
        
           | zeusk wrote:
           | It's not about LD_PRELOAD; more like you're calling clone in
           | python process and the process already has tcmalloc
           | overriding malloc in the address space.
        
             | Filligree wrote:
             | In this case it was about LD_PRELOAD. The behaviour is
             | still surprising. I would've expected it to be a drop in
             | replacement -- it is in most cases.
             | 
             | Sure, sanitizing the environment for process calls is
             | doable, but this also means I can't use libssh2 at all.
        
               | yjftsjthsd-h wrote:
               | Hang on - you can't use libssh2, or openssh? They're
               | totally separate projects
        
               | Filligree wrote:
               | I tried OpenSSH, libssh and libssh2. All three fail,
               | though admittedly I don't know for sure this was the
               | cause of failure for the two latter; they gave me no
               | diagnostics.
               | 
               | They all worked fine without LD_PRELOAD though, so...
        
             | AshamedCaptain wrote:
             | This makes no sense. The moment you call exec the entire
             | address space would go away.
        
               | [deleted]
        
         | bheadmaster wrote:
         | OpenSSH security features are tightly coupled to the underlying
         | OS. I remember reading the code and seeing how sshd forks and
         | re-execs itself in order to leverage dynamic library address
         | randomization in each connection. I wouldn't be surprised if
         | there are some malloc/free-related tweaks in a similar manner.
        
       | lamontcg wrote:
       | now imagine if you're writing a script that calls ssh in a loop
       | which runs on the remote host and uses sudo to run an awk cli
       | command with a single-quoted one-liner.
        
       | jjnoakes wrote:
       | I've always just used                   ssh host 'single command
       | string "with regular shell quoting"'
       | 
       | and that avoids the problem.
        
       | contravariant wrote:
       | > It was only after nearly 23 years of using OpenSSH on an almost
       | daily basis that I encountered this issue.
       | 
       | If it takes 23 years before software does something you don't
       | expect it can't be _that_ bad.
        
         | INTPenis wrote:
         | That's such a useless statement when it comes to OpenSSH
         | though. It's like saying I rode a bike for 23 years and it was
         | fine until I tried to ride over a mountain.
        
           | sshd wrote:
           | This is frankly a stupid analogy. To use your analogy, so
           | because your bike can't ride over a mountain, the one time
           | you decided to ride over a mountain in 23 years, you conclude
           | your trusty bike of 23 years is not good?
        
             | INTPenis wrote:
             | Of course you can ride over a mountain, it's just harder.
             | ;)
        
       | Wicher wrote:
       | A while ago I wrote a Rust crate to deal exactly with this
       | problem (which is a problem if you want to use SSH
       | programmatically to run commands remotely).
       | 
       | The README contains my own explanation of the phenomenon.
       | 
       | https://crates.io/crates/arghsh
        
         | pcthrowaway wrote:
         | Good call shelling out there instead of trying to pass it to
         | ssh using the ssh bindings lib, as Rust doesn't have an up to
         | date one
        
       | unilynx wrote:
       | Oh wow, good to know it's that broken. I still had figuring out
       | how to properly quote SSH command lines on my todo list because
       | I've got some shell wrappers that try to transparently execute
       | stuff inside containers on remote machines and they weren't
       | dealing right with spaces and quotes (think trying to build
       | `remote @server sql <statement>`)
       | 
       | It's weird that this has been known for over a decade, and noone
       | ever added a 'pass on the argv[] array unchanged' in all that
       | time
       | 
       | Now that I know I can't fix this in stock openSSH I think I'll
       | just look into throwing argv[] into a base64 encoded JSON array
       | and somehow have jq fix and exec it on the other end.
        
         | aidenn0 wrote:
         | If you use bash on both sides, you can use printf for any words
         | in the command that need quoting (or all words if you are
         | writing a script):                   ssh foo@bar baz "$(printf
         | '%q\n' "something that needs $quoting")
         | 
         | For cases where one or both sides are not bash, but still POSIX
         | like, it's relatively easy to write a function that uses single
         | quotes; just replace every single quote with '\'' before
         | wrapping in single quotes.
        
       | [deleted]
        
       | raimue wrote:
       | The author missed that sshd will always execute the user's shell
       | and pass it the command with arguments as a `-c` argument. This
       | means that the given command string will always be parsed by the
       | remote shell. This is required to restrict users to certain
       | commands with special shells like scponly or rbash.
       | 
       | When you keep in mind that the given command string will be
       | parsed twice, first by your local shell and then again by the
       | remote shell, it becomes clear why a running a remote ssh command
       | behaves like this.
        
         | zokier wrote:
         | yeah, this exec trace from the article is wrong, it is missing
         | one sh -c from the chain                 $ ssh localhost figlet
         | foobar bar\ baz       execve("/usr/bin/ssh", ["ssh",
         | "localhost", "figlet", "foobar", "bar baz"], ...
         | execve("/usr/bin/figlet", ["figlet", "foobar", "bar", "baz"],
         | ...
         | 
         | in practice it looks more like this (traced with execsnoop):
         | PCOMM            PID     PPID    RET ARGS         ssh
         | 4255    2058      0 "/usr/bin/ssh" "localhost" "figlet"
         | "foobar" "bar baz"         sshd             4256    2147      0
         | "/usr/bin/sshd" "-D" "-R"         bash             4259    4258
         | 0 "/bin/bash" "-c" "figlet foobar bar baz"         figlet
         | 4259    4258      0 "/usr/bin/figlet" "foobar" "bar" "baz"
        
         | jchw wrote:
         | Yep! God though, this hits me in the face so often. Trying to
         | add `sh -c` to fix it is a trap, because obviously, you just
         | create yet another layer of escaping.
         | 
         | It really becomes one hell of a puzzle sometimes, especially if
         | you're necessarily nesting another layer of escaping. It feels
         | like you're trying to write a quine.
         | 
         | This works:                   ssh host -- ls "folder\ name"
         | 
         | This also works:                   ssh host -- ls \"folder
         | name\"
         | 
         | This works:                   ssh host -- sh -c \"ls \\\"folder
         | name\\\"\"
         | 
         | OK, so clearly, just throwing more escaping at it fixes it. But
         | even if you figure that out, the real mental gymnastics would
         | be figuring out which of the three shells interpreting your
         | command line in the last case would handle shell expansion.
         | 
         | In this case, it's the host:                   ssh host -- sh
         | -c \"ls \\\"folder nam\\\"*\"
         | 
         | In this case it's the remote:                   ssh host -- sh
         | -c "\"ls \\\"folder nam\\\"*\""
         | 
         | Of course where you put the quotes makes no difference. All it
         | does is prevent your shell from processing it. So this works
         | just as well:                   ssh host -- "sh -c \"ls
         | \\\"folder nam\\\"*\""
         | 
         | If you sit and think each layer through, it usually isn't
         | completely impossible to understand, but the odds that you are
         | going to get something wrong the first time is astonishingly
         | high.
         | 
         | It does make me wonder why ssh handles it the way it does,
         | though. Because with the way SSH handles it, it may as well
         | just automatically escape the spaces. Right now, not putting an
         | SSH command in quotes doesn't make much sense unless you for
         | some reason want local shell expansion for something.
        
           | mananaysiempre wrote:
           | > This works:                 ssh host -- ls "folder\ name"
           | 
           | > This also works:                 ssh host -- ls \"folder
           | name\"
           | 
           | Uh, why not                 ssh host -- ls '"folder name"'
           | 
           | ? Single quotes are the shell's ultimate bulk "no touchy"
           | escape, so if you don't need them in the inner command, it
           | seems easier to use them for everything. (Also when passing
           | programs to sed, awk, jq, xmlstarlet, etc.)
        
             | jchw wrote:
             | I believe that also works, but I don't do it often because
             | I often wind up wanting to be able to use variables from
             | the host, especially e.g. in cron jobs and scripts.
        
               | mananaysiempre wrote:
               | Yes, that's a valid thing to want.
               | 
               | At that point, though, I'd try to write a general shell
               | escaping function, because I don't trust myself to figure
               | out which host things are OK to include unescaped in such
               | a situation. (Here's when I start to long for Tcl,
               | despite all the times I've had to spell out [string index
               | ...].)
               | 
               | The optimal solution would be to have a separate side
               | channel for passing things to the quoted program, like -v
               | in awk or --arg in jq (or whatever it is in your
               | favourite SQL DBMS binding) but I don't think SSH will
               | let you do that.
        
             | theK wrote:
             | On another note, avoiding having paths with troublesome
             | chars in them on servers does generally make the sailing
             | much more pleasurable.
        
             | nneonneo wrote:
             | If you _do_ need single quotes in the inner command, you
             | can use the  "'" trick:                   ssh host -- ls
             | "'"'folder$name'"'"
        
           | wolletd wrote:
           | From my experience, it's possible to just do
           | ssh host 'ls "folder name"'
           | 
           | and practically ignore the `command [arguments]` split in
           | SSHs synopsis. It's all passed to a shell and parsed again,
           | anyway.
        
             | 1vuio0pswjnm7 wrote:
             | For simple tasks, that's how I have always done it.
             | 
             | Alternatively,                  echo ls \"folder name\"|ssh
             | -T host
             | 
             | or                  cat > 1        ls "folder name"
             | ^D             ssh -T host < 1
        
           | brazzledazzle wrote:
           | Once I run into nested escape wrangling I start to seriously
           | question how I'm trying to accomplish something.
        
             | Arnavion wrote:
             | `printf '%q'` is your friend.
        
             | lanstin wrote:
             | If I can't fix it in like 3 tries, I switch to my local
             | shell script having the script to run on the remote end be
             | in a HEREDOC and just scp it it over and then ssh exec it.
             | Inside the HEREDOC, it's a sane environment.
        
               | septune wrote:
               | wrap it in base64 then cat it | ssh << base64 -d | bash
               | >>
        
               | theK wrote:
               | Copying over the commands to run also has another added
               | benefit, you have quite good documentation of what was
               | run and when. Also it allows one to easily run a failing
               | thing again (in case output gets mangled in the executing
               | script)
        
             | josephg wrote:
             | Once I run into nested escape wrangling I seriously
             | question the sanity of my tools.
        
           | Wicher wrote:
           | If you can invest a little bit of time in configuration of
           | the remote host, then my "special shell" (also mentioned
           | elsewhere on this page) may be of use, and you will no longer
           | need to hit yourself in the face so often!
           | 
           | mention: https://news.ycombinator.com/item?id=36726111
           | 
           | crate: https://crates.io/crates/arghsh
        
             | zokier wrote:
             | neat. one little pedantic note, argsh requires all
             | arguments to be valid unicode strings while I think at
             | least Linux allows argv to be arbitrary sequences of non-
             | zero bytes. But then I really hope there are not many cases
             | where that is relevant consideration.
        
         | sureglymop wrote:
         | Only tangentially related.. Is there something like rbash that
         | is actually secure and more restrictive? Like a shell that only
         | "sees" certain files and folders and can only execute certain
         | commands in a non privileged manner.
        
         | formerly_proven wrote:
         | This is more or less baked into the SSH protocol because the
         | exec request only has a single string for the command line.
         | 
         | Also the reason echoing a MOTD from rc files or similar crap
         | breaks tools like rsync or scp which use SSH as a neutral
         | transport. SFTP isn't affected because, while using the three-
         | pipe as a transport, it's a separate _subsystem_ with its own
         | SSH request type to initiate the channel (just like X11 or port
         | forwarding).
         | 
         | If you control client and server you can define your own
         | subsystems and invoke them directly, which avoids this whole
         | mess.
        
           | raimue wrote:
           | I think the confusion comes from the documentation where
           | ssh(1) says that the command "is executed on the remote host
           | instead of a login shell.". Which is true from the
           | perspective of ssh(1) in the sense of the protocol. The
           | client has no control over what the server does with that
           | string.
           | 
           | However, sshd(8) clearly documents that it will always
           | execute the login shell, even when a command has been passed.
           | ("All commands are run under the user's login shell as
           | specified in the system password database.")
           | 
           | Subsystems open secondary channels to communicate separately
           | from stdin/stdout of the remote command. I used the X11
           | forwarding before to run a remote command with sudo without
           | getting the password prompt interfering with the protocol:
           | https://raimue.blog/2016/09/25/how-to-run-rsync-on-remote-
           | ho...
        
             | formerly_proven wrote:
             | An extra subtlety is that it is the user's login shell (in
             | the getpwnam sense) but the command is not run in a login
             | shell (in the sh - / sh -l sense). That's why proper motds
             | or profile files work ok (requesting a shell runs the
             | user's login shell as a login shell) and don't break
             | applications. Also the reason why $PATH often differs
             | between the two modes.
        
       | madworx wrote:
       | OpenSSH has some (to my mind) very peculiar handling around
       | command line arguments.
        
       | NoZebra120vClip wrote:
       | This is probably an instance of the DWIM principle. You want this
       | tool to do a thing when you invoke it a certain way, but its
       | objectives and methods differ from yours, so despite best
       | intentions on both sides, you end up with unexpected behavior.
       | 
       | Argument parsing, whitespace detection, tokenizing a string,
       | these are really difficult things even when a remote host is not
       | involved. Try writing a shell script that always handles
       | variables so perfectly that you can access any pathname in the
       | filesystem, whether it has spaces, tabs, unprintables, Unicode,
       | or whatever.
       | 
       | There are, of course, solutions, or at least workarounds to this.
       | ssh can be configured on the receiving side so that it runs a
       | particular command, and parsing whitespace/arguments should be
       | significantly easier when you configure it this way.
       | Alternatively, you could write custom Bash or Python to do what
       | you want, and just have that tool on the receiving end. (The
       | latter assumption is easier said than done!)
       | 
       | Configuring ssh to run a dedicated command line is probably an
       | underutilized, underrated mode of operation. I used it to safely
       | operate LAN backups: I had a client machine that would ssh in to
       | a special user account, which had no actual shell, but
       | immediately kicked off the backup process. That's the
       | stereotypical use.
       | 
       | But for _ad hoc_ use cases, like this blogger was probably trying
       | to implement, you don 't really have a chance to pre-populate the
       | receivers with scripts or special configurations. And yeah, that
       | becomes a pain when you're trying to do a clever one-liner and it
       | chokes. I feel your pain. (Have you considered improvising a
       | Python or Perl one-liner for ssh to run?)
        
         | Wicher wrote:
         | > ssh can be configured on the receiving side so that it runs a
         | particular command
         | 
         | Yes -- but it will always, ALWAYS launch your shell as
         | (typically) defined in /etc/passwd, and will pass it two string
         | arguments: "-c" "and your particular command with arguments and
         | all". Go strace it, go look at the argument vector, you'll see.
         | Thus if your shell is bash, zsh, tcsh, dash, sh, whatever: the
         | moment that it sees and tokenizes "and your particular command
         | with arguments and all", you'll have lost.
         | 
         | The only winning move is not to play.
         | 
         | > Configuring ssh to run a dedicated command line is probably
         | an underutilized, underrated mode of operation.
         | 
         | Perhaps, but in any case, it doesn't save you. As I explained
         | above, your shell runs first, so you've already lost. Maybe
         | it's not underrated, maybe it's rated exactly right because of
         | this problem!
         | 
         | But what if I tell you you can make your problems go away. You
         | juuuuuust have to use this very special shell. I don't want to
         | be downvoted for linking it for a third time on this page, so I
         | won't. Thus: see my other comments on this page :-)
        
         | harry8 wrote:
         | >[can't] pre-populate the receivers with scripts or special
         | configurations
         | 
         | Write a script in say, python. Then from memory you can do
         | something like:
         | 
         | $ ssh $remotehost python < script
         | 
         | Wrap that in a for remotehost in ${hosts[@]}; do
         | 
         | Maybe? Has some merit on occasion.
        
         | marcosdumay wrote:
         | > DWIM principle
         | 
         | This is an instance of the problems caused by throwing
         | everything into a simple plain stream, instead of having a
         | modal stream that can tell what kind of thing is going there.
         | 
         | Try writing a python script that always handles variables so
         | perfectly that you can access any pathname in the system. It's
         | funny to even write that, because it's trivial (barring UTF-8
         | issues that require using a non-obvious API). But you can only
         | do that because of those annoying quotes that create a new
         | idiom inside them. Sh saves you from that annoyance.
        
       | jwilk wrote:
       | Upstream bug report:
       | 
       | https://bugzilla.mindrot.org/show_bug.cgi?id=2283
        
       | comex wrote:
       | Here's a wrapper script I wrote that has the same syntax as the
       | ssh command but automatically shell-quotes arguments, ensuring
       | they're passed through 1:1 to the remote command:
       | 
       | https://gist.github.com/comex/2faf21ebee93b0bd64841c30c7d280...
       | 
       | The quoting is done with Python's shlex.quote(), whose output
       | should parse properly in any POSIX-compatible shell. (Before
       | editing this comment I had linked a similar script from Stack
       | Overflow, but it uses bash's printf %q to quote, which produces
       | output that uses ANSI-C quoting, which isn't supported by dash.)
        
       | ndsipa_pomu wrote:
       | Also worth remembering that SSH will eat all your STDIN unless
       | you use the "-n" or "-f" options.
        
       | blueflow wrote:
       | man 8 sshd:
       | 
       | > After this, the client either requests an interactive shell or
       | execution or a non-interactive command, which sshd will execute
       | via the user's shell using its -c option
       | 
       | Its not sshd doing the argument splitting, its the shell on the
       | server side.
        
       | janosdebugs wrote:
       | The root of the problem isn't OpenSSH, it's the SSH protocol
       | itself. When a command execution is desired, the command travels
       | over the wire as a string, not as an array of strings as execve
       | would expect. See RFC 4254 section 6.5. [1]
       | 
       | The SSH server has to figure out how to deal with it, most SSH
       | servers to my knowledge invoke a shell. Parsing the passed
       | command and trying to invoke it without a shell can be unsafe as
       | escaping is shell-specific. (Imagine, for example, SSH'ing into a
       | Windows machine, which the protocol entirely supports.)
       | 
       | [1] https://datatracker.ietf.org/doc/html/rfc4254#section-6.5
        
         | qsantos wrote:
         | But you _could_ expect                   ssh localhost sh -c
         | 'cd /tmp && pwd'
         | 
         | to pass "sh -c 'cd /tmp && pwd'" over the network, which would
         | trigger the expected behavior. But the arguments are not quoted
         | so you end up with just: "sh -c cd /tmp && pwd".
        
           | janosdebugs wrote:
           | The problem is that the quote signs are already eaten by your
           | local shell. The SSH binary gets the parameters like this:
           | ['sh', '-c', 'cd /tmp && pwd']
           | 
           | How should SSH know what shell is running on the other end
           | and escape it properly? Will it escape for Sh? Bash? Fish?
           | Powershell? Python shell? Custom script? The SSH client would
           | need to know what shell is running on the other end, which it
           | can't. The server, which knows what will run, cannot fix this
           | because it only gets the already mangled string.
           | 
           | This is not solvable the way the protocol currently works
           | because you can't support all possible constellations of
           | client/server applications and shells.
        
             | btilly wrote:
             | This. Exactly this.
             | 
             | Rule 5 of http://cr.yp.to/qmail/guarantee.html remains
             | completely true. Don't parse. When you try to use a user
             | interface as a command interface, you have to parse. And
             | some day it will bite you.
             | 
             | The solution is to remotely invoke programs designed for
             | the purpose, and then pass data to them in a structured
             | format.
        
           | vbernat wrote:
           | It's because SSH sees "localhost" "sh" "-c" "cd /tmp && pwd"
           | and will send "sh -c cd /tmp && pwd", which the remote shell
           | will interpret as executing "sh" "-c" "cd" "/tmp" and
           | executing on success "pwd".
        
           | mst wrote:
           | Remembering to write                   ssh localhost sh -c
           | "'cd /tmp && pwd'"
           | 
           | works but (a) _ICK_ (b) I usually only remember this on about
           | attempt three at the earliest.
        
             | rascul wrote:
             | ssh will read commands from stdin so this will work:
             | echo "cd /tmp && pwd" | ssh localhost
             | 
             | Or with a here string that bash and a few others support:
             | ssh localhost <<<"cd /tmp && pwd"
             | 
             | I often use the here string method. Maybe one of those
             | would be easier to remember.
        
       | cosmin800 wrote:
       | It has always been like that, at least since I know it..20
       | years?!So, the author thinks it should be "fixed"?. Oh yeah,
       | sure, we should add new openssh client parameter -yaml and send
       | the remote command nicely structured in a yaml config.
        
       | kunley wrote:
       | Wat? (to quote the author)
       | 
       | It has nothing to do with OpenSSH....
        
       | tbronchain wrote:
       | I've had some pretty good success doing something like:
       | 
       | ssh user@host << EOF
       | 
       | Your script
       | 
       | EOF
       | 
       | Beware of local variables interpretation though!
        
         | oxygen_crisis wrote:
         | Bash lets you suppress local expansions by using <<'EOF'
         | instead of <<EOF.
         | 
         | But if you need a mix of local and remote expansion, you're
         | back to doing a lot of escaping.
        
       | rollcat wrote:
       | Oh yes, I've run into this when creating judo[1] (which runs
       | commands/scripts across a fleet of hosts via SSH).
       | 
       | It's not even a problem with OpenSSH, the SSH protocol itself
       | (RFC4254[2], Section 6.5) specifies that the command is sent as a
       | string, and there's just no sane way to convert between an array
       | of strings and a string that's also human-friendly to the command
       | line.
       | 
       | My work-around was to allow only a single argument in "judo -c
       | CMD HOST", where CMD is to be interpreted by the target's shell.
       | If you need to do anything more complex, the "judo -s SCRIPT
       | HOST" form is more-or-less equivalent to "chmod +x SCRIPT &&
       | ./SCRIPT".
       | 
       | [1]: http://github.com/rollcat/judo
       | 
       | [2]: https://www.rfc-editor.org/rfc/rfc4254
        
         | mst wrote:
         | OpenSSH has already added plenty of extensions and the world
         | seems to be fairly comfortable with using quite a few of them -
         | for backwards compatibility reasons they probably can't change
         | the current behaviour by default for -everybody- but a protocol
         | extension and a flag to use it would improve the world
         | substantially.
        
           | rollcat wrote:
           | The current behaviour is already a UX trap, and such an
           | extension would be another one on top of it. What's the sane
           | thing to do if either end does not support it? Any kind of
           | fallback would just make things even worse; detecting and
           | supporting both variants is a road to hell. I still have
           | CentOS 7 systems in production, with the last CentOS 6 box
           | only decommissioned a few years ago. Judo mostly works
           | because it sticks with the lowest common denominator, the
           | value comes from things you can do on top of it.
        
         | blincoln wrote:
         | I had a similar experience when I was testing A Black Path
         | Toward the Sun[1], specifically using SSH and SCP over the
         | HTTP/HTTPS tunnel, as well as when I was writing unreleased SSH
         | pen testing tools at two different organizations.
         | 
         | I found that SSH is more complicated than I would have expected
         | at a low level, and far more barebones than I would have
         | expected at a higher level. It's incredibly sensitive to
         | certain types of small delay for its packets. On the other
         | hand, the actual terminal traffic is basically just a pair of
         | streams. For an interactive SSH session, there's no useful
         | concept of "send a command and wait for it to finish on the
         | remote system", because the remote system is just piping the
         | output of the shell back to the SSH client. You can hack in
         | something like generating a random tag and appending "; echo '
         | ---complete:{random_tag}---'" to every command and assuming the
         | command is complete when that string appears, but it's not
         | built in, and of course every SSH server OS can have different
         | syntax requirements, so now the client has to detect and handle
         | each of those.
         | 
         | It makes sense given that SSH is a general-purpose protocol
         | that's supposed to work for just about any CLI, so I was
         | surprised for the same reason as the author of the article.
         | Their issue makes sense in context as well - SSH has to support
         | server shells like the Windows command prompt that don't even
         | have the concept of an array of arguments at a low level, just
         | the command string.
         | 
         | [1] https://github.com/nccgroup/ABPTTS
        
       ___________________________________________________________________
       (page generated 2023-07-14 23:01 UTC)