1

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 invokes pass 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.

  • 1
    What did you expect the shell to read its input from after pass root_password | sudo --stdin --shell? – Michael Homer Apr 16 '17 at 3:40
  • From the output of pass root_password which outputs the decrypted root password to its stdout, piped to sudo – Lucien Haurat Apr 16 '17 at 3:48
  • Right, so there you go then. – Michael Homer Apr 16 '17 at 3:51
  • Sorry, I misunderstood. Well sudo --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. – Lucien Haurat Apr 16 '17 at 3:56
4

pass root_password | sudo --stdin --shell succeeded. pass printed the password and sudo read it and started a shell; the output of pass ended, meaning that the stream used for output by pass and input by the shell was closed; the shell exited successfully on EOF.

Subsequent executions within the sudo timeout window didn't require authentication, and so the shell received the password itself and tried to execute it as a command, giving that error.


Your alias can't possibly work if you want a shell or to run some command reading from stdin (but it does for commands that don't). You could set an alias up that ran pass root_password | sudo --stdin true if you wanted, and then rely on the timeout to run commands without a password.

You could also build some sort of askpass helper that would give the behaviour you want. It would provide the password via pass when asked for it by sudo.

  • OK, understood, that's clear now, thanks! So the "askpass helper" would just write the output of pass to the sudo process stdin, like a kind of wrapper, right ? – Lucien Haurat Apr 16 '17 at 4:11
  • It would need to be a drop-in replacement for e.g. x11-ssh-askpass, which sudo -A will invoke if configured. It writes the password to its own output, having obtained it however it liked. – Michael Homer Apr 16 '17 at 4:22
  • Thanks a lot, problem solved! I'm going to edit my post so that it includes my solution, for future readers. – Lucien Haurat Apr 16 '17 at 9:17

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.