Actually su itself doesn’t do anything special with its arguments. It just invokes a user’s login shell with the specified arguments. Even -c is part of the shell’s arguments. Therefore su $USER -c '...' just executes $SHELL -c '...' as $USER. Anything weird going on with '...' is the fault of $SHELL.
su $USER -c '...'
$SHELL -c '...'
If you want to trick $SHELL into just executing a command without interpreting it as a shell script you can use the following:
$SHELL -c 'exec "$0" "$@"' touch 'filename with spaces'
Any additional arguments to the (Bourne) $SHELL are available as $0 and $@. Combining that with su gives us:
su $USER -c 'exec "$0" "$@"' touch 'filename with spaces'
This way no Python script is needed in order to avoid unintentional interpretation of su’s arguments.
Good point, thanks! I will add a link to this post from my article.
I’m not really administering multi-user systems so I just use “sudo”.
Note that the SSH protocol (as by RFC4254) itself only supports passing a single string when executing a commmand:
uint32 recipient channel
boolean want reply
It’s strange that the article doesn’t mention that Bernstein himself has a replacement for su to solve this exact issue.
I don’t think daemontools‘ setuidgid was intended as a replacement for su. It isn’t even installed with the setuid bit set. So you can’t use it to gain privileges, only to drop them. In contrast, su is intended to gain, but not drop privileges and accordingly is installed with the setuid bit set. The author of nosh (belonging to the daemontools family) goes into detail about this: Don’t abuse su for dropping user privileges