This is a post not beggining as you would expect or see everyday,
here is my root password: :4\g&8n:6_F[`9Touc8Ls+L'8)>6!3,nNmUzR&Ub~w7NTd'^Lb0]`0`."u>tP\>XAspMLTt!@}=F6CP)NsSMYY7*xm'A!7`!n'tmAaGWoBhS|u4{k$*v/o|'%)mbXMw
Hard to remember right? Well that's not the problem. And before you yell at me, yes, I changed it. I now have a dummy_root
user with that password for tests.
TL;DR
I broke zsh passing this password to sudo on stdin
(Very) long version
So here's what's wrong: I use sudo
with the targetpw
flag, meaning that the password asked by sudo is the password of the target user, not mine. I use the pass
password manager (site) for everything, including the root password of all my computers/servers. For obvious security reasons, this password is randomly generated by pass with 128 characters (because why not). As a lazy person (I'm sure you are too), I want to use the following alias:
alias sudo='pass mydomain.tld/hostname/users/root/password | sudo --stdin'
For those of you who are not familiar with pass, it basically encrypts passwords in a directory tree with GnuPG, and invoking pass directory/subdirectory/password
decrypts the password and prints it to stdout (of course, directories are optional, you can store everything in disorder at the root, however that's not encouraged). The aim of the alias is to avoid doing this tens of times a day:
$ pass --clip root_password #Copy root password to clipboard
$ sudo command
[sudo] password for root: paste
#command output (if the password is right of course)
But testing the alias command, here what happens:
$ pass root_password | sudo --stdin --shell
[sudo] password for root: %
$
Here, the %
sign is color inverted: my terminal is white text on black background, the %
is black on a white background, sort of white highlighted, and is printed after password decryption by pass. Between the printing of [sudo] password for root:
and the printing of this %
, GPG asks for my private key passphrase. I already noticed the inverted percent sign on some occasion, and from where and when I saw it, I belive it is to print EOF
(careful here, I am not sure of this at all, see at the end of the post for a method to print this%
). Notice that after this strange sign, i'm not root, but echo $?
returns 0
...
So I hexdumped the output of pass root_password
to see if I can get more info on that character. Here is the result:
$ pass root_password
:4\?g&8n:6_F[`9Touc8Ls+L'8)>6!3,nNmUzR&Ub~w7NTd'^Lb0]`0`."u>tP\>XAspMLTt!@}=F6CP)NsSMYY7*xm'A!7`!n'tmAaGWoBhS|u4{k$*v/o|'%)mbXMw
$ pass root_password | hexdump -C
00000000 3a 34 5c 3f 67 26 38 6e 3a 36 5f 46 5b 60 39 54 |:4\?g&8n:6_F[`9T|
00000010 6f 75 63 38 4c 73 2b 4c 27 38 29 3e 36 21 33 2c |ouc8Ls+L'8)>6!3,|
00000020 6e 4e 6d 55 7a 52 26 55 62 7e 77 37 4e 54 64 27 |nNmUzR&Ub~w7NTd'|
00000030 5e 4c 62 30 5d 60 30 60 2e 22 75 3e 74 50 5c 3e |^Lb0]`0`."u>tP\>|
00000040 58 41 73 70 4d 4c 54 74 21 40 7d 3d 46 36 43 50 |XAspMLTt!@}=F6CP|
00000050 29 4e 73 53 4d 59 59 37 2a 78 6d 27 41 21 37 60 |)NsSMYY7*xm'A!7`|
00000060 21 6e 27 74 6d 41 61 47 57 6f 42 68 53 7c 75 34 |!n'tmAaGWoBhS|u4|
00000070 7b 6b 24 2a 76 2f 6f 7c 27 25 29 6d 62 58 4d 77 |{k$*v/o|'%)mbXMw|
00000080 0a |.|
00000081
0a
is NL
according to man ascii
. You can notice here that the %
sign is not here, and that the newline character needed by sudo (see man sudo
) is here.
But hang on, now the fun begins: when I run the command twice on a freshly started terminator window or tty, here is what happens:
$ pass root_password | sudo --stdin --shell
[sudo] password for root: %
$ pass root_password | sudo --stdin --shell
zsh: parse error near `)'
$
And from then, zsh appears to be broken. It seems it wants to execute the output of the first command, but only sudo
is the second command. Here is some examples with this "broken" shell:
$ echo testpw | sudo --stdin --shell
zsh: command not found: testpw
$ echo testpw | cat
testpw
$ echo testpw | wc --bytes
11
$ echo anothertestpw | sudo --stdin --shell
zsh: command not found: anothertestpw
$ pass root_password | sudo --stdin --shell
zsh: parse error near `)'
$ exec zsh
$ echo testpw | sudo --stdin --shell
zsh: command not found:testpw
What is going on here? As shown evec replacing the zsh instance with another (exec zsh
) does not fix this. What is really broken here? pass, zsh or sudo ?
So let's carry on, let's test more, let's do another hexdump in a new terminal window (because obviously I don't know how to put zsh back in his "normal" state):
$ pass root_password | sudo --stdin --shell 2>&1 | hexdump -C
00000000 5b 73 75 64 6f 5d 20 70 61 73 73 77 6f 72 64 20 |[sudo] password |
00000010 66 6f 72 20 72 6f 6f 74 3a 20 |for root: |
0000001a
$
Where did this percent sign go? No idea. It's just not here and I'm still not root, but the shell is now in his broken state (I definitely don't know how to name this "broken state" properly). Running the pass root_password | sudo --stdin --shell
command after this one raises the same error message as before. It is to note that everything is the same on a tty and on terminator, even this evil %
character.
Here is all the informations I can think of that one may need to help me. I'm on Arch Linux, and all packages mentionned are up to date as of today (2017-04-16 or 15, depending where you are on the planet, currently 4AM in France) which means (pacman -Q
output):
- kernel: 4.10.9-1
- zsh: 5.3.1-2
- sudo: 1.8.19.p2-1
- pass: 1.7.1-1
- gnupg: 2.1.20-1
- terminator: 1.91-5
Here is a link to my .zshrc, it might help too.
And as promised, here is some steps to get this malicious %
sign with pass
. Simply create a multiline password, and hitting Ctrl+D to finish the password without adding a trailing new line will print this character. I notice here that hitting Ctrl+D is not sufficient at the end of a line with text, you have to do it twice. But if you add a trailing line to this multiline password, hitting it once will end the password input without showing this.
$ pass insert --multiline test_evil_percent
Enter contents of test_evil_percent and press Ctrl+D when finished:
fooReturn
barCtrl+DCtrl+D%
There it is !
But with a trailing line, it does not appears, and we get our prompt back when hitting Ctrl+D just once.
$ pass insert --multiline test_evil_percent
Enter contents of test_evil_percent and press Ctrl+D when finished:
fooReturn
barReturn
Ctrl+D
$
So the primordial question is: how to get my alias to work? For those wondering, I just wrote a one line script in my custom scripts directory invoking pass root_password
and set the environment variable SUDO_ASKPASS
to the pass of this script in my .zshrc
file
Another question may be: what the hell is going on with zsh and sudo here?
Edit 2017-04-16: Solution
Thanks to Michael Homer I chose this solution for my problem:
- Write a one line script named
askpass-sudo
which invokespass root_password
- Set the
SUDO_ASKPASS
environment variable in my.zprofile
to the path of this script - Added the line
alias sudo='sudo -A'
to my.zshrc
And voilà! Invoking sudo command
now asks, as wanted, my GnuPG key passphrase to decrypt the root password, and with sudo --shell
I properly get a root shell.
pass root_password | sudo --stdin --shell
?pass root_password
which outputs the decrypted root password to its stdout, piped to sudosudo --shell
is supposed to ask for a password, and open a root shell right ? So with--stdin
flag I expect it to read this password from pass and then open a root shell, which it does not.