1

I am trying to read a password from stdin into a variable in a Bash script. This variable should then be expanded into an ssh-command and transmitted to the server. There, the password should arrive in correctly escaped manner, so it can be used as input for another command. (In this case to change the login-password of my pihole).

Currently I have something like:

read -rs -p "Password: `echo $'\n> '`" newpass
ssh root@gate "pihole -a -p \"${newpass}\""

But also tried:

ssh root@gate "pihole -a -p ""'""${newpass}""'"
ssh root@gate 'pihole -a -p '"${newpass}"
ssh root@gate 'pihole -a -p '"${newpass}"''
ssh root@gate "pihole -a -p \'${newpass}\'"
ssh root@gate 'pihole -a -p ''\''"${newpass}"'\''

Even with printf:

IFS= read -rs -p "Password: `echo $'\n> '`" newpass
command=$(printf 'pihole -a -p %s\n' "$newpass")

or

IFS= read -rs -p "Password: `echo $'\n> '`" newpass
command=$(printf 'pihole -a -p '\''%s'\''\n' "$newpass")

and many more. But with this password: a$#5!6k?h'v;z' they all fail. And there isn't even a back-tick in it, yet...

My question to all you Bash Gurus out there is:

What is the correct way to parse a password into a variable, where the user may enter literally any character?

This includes:

$ 
!
` (backtick)
' (single quote)
"
;
#
@
\
/
~
\n

and so on...

I have read all the related questions here and in other forums, but couldn't find a solution for this use case.

I know, that there will be people who suggest to use Python or C or something else, but still I'm interested in Bash's capabilities.

However, if it definitely isn't possible to accomplish with Bash, then I'm also interested in ideas on how you would do this (cleanly and securely) with other tools.

Thanks in advance

4
  • 2
    Getting the password in a variable is the easy part, that's what the IFS= read -r does. The problem you have is not with that, but with safely passing an arbitrary string to another shell's command line (via SSH).
    – ilkkachu
    Jul 15, 2020 at 21:55
  • 1
    BTW, note that putting sensitive data on command-line arguments is generally not a great idea. Every user account on your system, even untrusted ones, can read command-line arguments from every other account's processes! It doesn't help for ssh, but passing content in environment variables are much safer; likewise files with well-chosen permissions. For the ssh case, passing content on stdin is the right thing. Jul 16, 2020 at 1:13
  • @CharlesDuffy Wouldn't something like mounting /proc with hidepid=2 prevent the issue of users peaking at other users' processes?
    – m4110c
    Jul 16, 2020 at 7:24
  • Yes, that is an effective mitigation. Jul 16, 2020 at 14:09

2 Answers 2

3

Several possible solutions.

  • Have pihole ask for the password directly instead of using your own prompt.

    ssh -t user@host pihole -a -p
    
  • Run your own read prompt on the target side:

    ssh -t user@host 'read -rsp "Password: " newpass && pihole -a -p "$newpass"'
    
  • Pass it through a pipe:

    echo "$newpass" | ssh user@host 'read -r newpass && pihole -a -p "$newpass"'
    
  • Quote the password you read before passing it:

    read -rsp "Password: " newpass
    qnewpass=$(printf "%q" "$newpass")
    ssh user@host pihole -a -p "$qnewpass"
    
  • Encode the password and decode it on the other side:

    newpass_base64=$(printf "%s" "$newpass" | base64)
    ssh user@host "pihole -a -p \$(echo $newpass_base64 | base64 -d)"
    
  • Don't use special characters at all, XKCD style. ;-)

6
  • Solution 1 is not feasable, because the script does more things with that password not just update pihole. So direct user interaction is not desired. Solution 4 is not up to me, it's another user who enters the password.. Solution 2. works and 3. is also smart. I would upvote but lack the needed reputation. Thank you!
    – m4110c
    Jul 15, 2020 at 21:46
  • @m4110c in that case, consider making a wrapper on the target side with a safe to interact prompt or read pass from stdin. still beats squeezing odd parameters through several layers of shell argument interpretations... Jul 15, 2020 at 21:50
  • Wouldn't be a bad idea if it were just one server but I have 3 servers in my LAN that need to get that password. Do you mean to have a wrapper on each target then? I think that would be overkill and I'd need to maintain those 3 scripts in case something changes..
    – m4110c
    Jul 15, 2020 at 21:55
  • @m4110c added another variant to option 1), basically passing and executing the whole script on the target side. anyway, many options there... Jul 15, 2020 at 21:59
  • 1
    I agree with just about everything here. Re: "don't use special characters at all", though -- leaving your code with bugs you're just not triggering right now due to carefully-chosen data still leaves your code with bugs. Next person who comes in might not remember the reasoning behind those restrictions; worse is when the data that's being restricted for bug-avoidance is something like a filename that malicious users or outsiders can influence; I once was present for a major data loss event caused by a pointer-arithmetic error dumping random garbage into a filename used by a buggy script. Jul 16, 2020 at 1:18
1
$ echo "$pw"
$!@`',

$ echo "${pw@Q}"
'$!@`'\'','

That should do it. I do not know, though, in which version of bash this was introduced. This works, too, and may have been available earlier:

$ printf %q "$pw"
\$\!@\`\'\,
# or, avoiding the command substitution
$ printf -v pw_ql1 %q "$pw"
$ echo "$pw_ql1"
2
  • It works with this solution. I need to try it in the server script. I'll report back tomorrow. Thanks anyway for the input!
    – m4110c
    Jul 15, 2020 at 21:43
  • I believe that was introduced in bash 5.0. printf %q was definitely introduced much earlier, and works even in the ancient bash 3.2 releases Apple ships. Jul 16, 2020 at 1:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.