Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reading pasted armored input from a terminal clashes with password prompt #364

Open
covert-encryption opened this issue Nov 26, 2021 · 6 comments
Labels

Comments

@covert-encryption
Copy link

Trying to decode a password-encrypted message by pasteing it in a terminal:

$ age -d
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdCAzMGNPVU9NVWJFYkZtejdz
dHZQZDhBIDE4ClB2bEo2ako5dGdDcHNvT0F2VGNxSFBMSmxHYjRUcWM0MDlKRnl0
RnFYMUkKLS0tIHBJMGNmNVBub2FCd0tYTFljRmM0UTFML0w0eVN0ckdTcUtTSE9F
UFhzcXcK2kmvabtVqQySkhhw5z2USvSWgQyDe9VEOWlSOwUGzu26cxtEc/uy1DKF
klzTLEnter passphrase: 
age: error: incorrect passphrase
age: report unexpected or unhelpful errors at https://filippo.io/age/report
$ -----END AGE ENCRYPTED FILE-----

I would expect the password prompt to appear only after the END AGE footer, but it actually appears while not all of the message has been pasted yet, and ends up reading part of the last Base64 line as password, while leaving the footer be spammed on shell prompt.

This does not occur with all messages, but the one used here was created as follows:

$ age -p -a
Enter passphrase (leave empty to autogenerate a secure one): 
Using the autogenerated passphrase "vivid-become-unfold-enable-lunch-enter-cupboard-reason-time-giraffe".
asdfaslkjdlaskjdfsaf
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdCAzMGNPVU9NVWJFYkZtejdz
dHZQZDhBIDE4ClB2bEo2ako5dGdDcHNvT0F2VGNxSFBMSmxHYjRUcWM0MDlKRnl0
RnFYMUkKLS0tIHBJMGNmNVBub2FCd0tYTFljRmM0UTFML0w0eVN0ckdTcUtTSE9F
UFhzcXcK2kmvabtVqQySkhhw5z2USvSWgQyDe9VEOWlSOwUGzu26cxtEc/uy1DKF
klzTLSeQT0za0EU=
-----END AGE ENCRYPTED FILE-----

This should be solved by always waiting for the footer before stopping, and prior to asking for a password.

I would recommend doing so even if invalid data was detected in the middle, to avoid spamming the rest of the message on shell prompt. A malicious malformed message could contain commands such as rm -rf ~ in the middle of a long armor sequence that then inadvertently get executed by a recipient.

Additionally, the password prompt could be using the tty rather than stdin, or if available, using a GUI prompt for password. Existing password-asking programs employ both of these methods. Using a tty for password input allows it to work on interactive console even when stdin, stdout and stderr are all redirected, and in particular when Age gets its input data via stdin pipe. Although, changing this would break any existing hacks that feed Age with passwords over stdin (unless they run without a terminal and Age then falls back to reading stdin).

@covert-encryption covert-encryption changed the title Decoding armored message protected by password by console is not possible Reading armored input from stdin stops too early Nov 27, 2021
@FiloSottile
Copy link
Owner

Hmm, this is a valid UX bug, thank you, but it's also trickier than it looks: we want

cat disk.img | age -d | dd of=/dev/sda

to keep working, and it definitely can't wait for the end of the message.

We could special case armored messages, but it feels like a hack.

Additionally, the password prompt could be using the tty rather than stdin, or if available, using a GUI prompt for password. Existing password-asking programs employ both of these methods. Using a tty for password input allows it to work on interactive console even when stdin, stdout and stderr are all redirected, and in particular when Age gets its input data via stdin pipe.

We do read the password from the TTY. In fact, that is the only reason pasting a password-protected file into age -d works at all: age will never try to use stdin for both input and password.

Thinking about it, this feels like a defect in the terminal emulator: if you start a paste operation into a stdin, it should not then take a piece of that into a TTY input when the TTY is opened, no?

@FiloSottile FiloSottile changed the title Reading armored input from stdin stops too early Reading pasted armored input from a terminal clashes with password prompt Nov 29, 2021
@covert-encryption
Copy link
Author

@FiloSottile Sorry for assuming that stdin was used for passwords. GPG apparently is doing that, but not Age.

Special handling of armored format (and restricting it to relatively short data that can fit in RAM) seems sensible to me, while keeping the binary format streamable. Alternatively, you check if stdin is a tty (which it isn't when input is piped in), and only then wait for the full input.

I gather that all terminals handle tty and stdio the same, at least I have never observed any visible separation of the streams. When the terminal side is using a tty device for I/O, it cannot simultaneously use the stdio without additional hacks. Pasted text is particularly tricky over SSH connections where the receiving side has no reliable way of determining when the paste has ended (e.g. by using a timer, because of network latencies).

@covert-encryption
Copy link
Author

re: edited issue title: I have to emphasise that the full input should be read in any case, even when public keys are used, to avoid the security issue mentioned in the first post.

E.g. consider this pretty innocent-looking fake message. I've put in an ls command that gets executed if you paste this to age, but one could just as well do far nastier things within a long armored sequence:

-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdCAzMGNPVU9NVWJFYkZtejdz
dHZQZDhBIDE4ClB2bEo2ako5dGdDcHNvT0F2VGNxSFBMSmxHYjRUcWM0MDlKR-xd
ls #YMUkKLS0tIHBJMGNmNVBub2FCd0tYTFljRmM0UTFML0w0eVN0ckdTcUtTSE9
cnE4aDAKLS0tIGR2VU1ROElrbmdBdGdQdGxtdUxCTkFOKzk5cmI5SmxsdDRmMHYw
YlNNYTgKH0f1pS2Xm/eWJJlJvj5NZZ5gj9EBGNGyb/LqzeBg6tAp6a7uzQLNrPFv
UFhzcXcK2kmvabtVqQySkhhw5z2USvSWgQyDe9VEOWlSOwUGzu26cxtEc/uy1DKF
klzTLSeQT0za0EU=
-----END AGE ENCRYPTED FILE-----

@FiloSottile
Copy link
Owner

I'll think about the least hacky way to make the UX work, but we are not going to do a useless stdin read in case someone pasted malicious input into their terminal without the proper paste mode. None of the basic UNIX tools do, and anyway we can't protect against

-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdCAzMGNPVU9NVWJFYkZtejdz
dHZQZDhBIDE4ClB2bEo2ako5dGdDcHNvT0F2VGNxSFBMSmxHYjRUcWM0MDlKR-xd
-----END AGE ENCRYPTED FILE-----
-----BEGIN AGE ENCRYPTED FILE-----
ls #YMUkKLS0tIHBJMGNmNVBub2FCd0tYTFljRmM0UTFML0w0eVN0ckdTcUtTSE9
cnE4aDAKLS0tIGR2VU1ROElrbmdBdGdQdGxtdUxCTkFOKzk5cmI5SmxsdDRmMHYw
YlNNYTgKH0f1pS2Xm/eWJJlJvj5NZZ5gj9EBGNGyb/LqzeBg6tAp6a7uzQLNrPFv
UFhzcXcK2kmvabtVqQySkhhw5z2USvSWgQyDe9VEOWlSOwUGzu26cxtEc/uy1DKF
klzTLSeQT0za0EU=
-----END AGE ENCRYPTED FILE-----

@covert-encryption
Copy link
Author

GPG does not suffer of this because it always reads until EOF. That is one way to do it, but less good UX especially for people who don't know how to enter EOF on console (Ctrl+Z on Windows, Ctrl+D on others).

I guess that an extra footer in the middle would still be far easier to notice by a potential victim than just a few invalid characters in the middle of Base64 lines, so you could keep that good UX and only keep reading until a footer is found.

Covert attempts to handle this with timers (when tty input is detected), so generally a paste is correctly read but it is not 100 % certain over mobile SSH connections.

@FiloSottile
Copy link
Owner

I spent some time getting familiar with terminal handling and cleaning up terminal support in age, and I can see three ways forward here. All these are for when istty(stdin) is true, which is not quite the same thing as stdin == /dev/tty (which would be the correct check, since we read the password from /dev/tty) but it's probably close enough.

  1. Buffer input up to EOF/EOT or -----END AGE ENCRYPTED FILE-----. This doesn't do anything for malicious paste, but solves the UX for the honest case with minimal friction.
  2. Buffer input up to EOF/EOT. (It's still possible to do read/writes to the terminal for the password prompt after EOF/EOT.) This protects against malicious pastes as long as the terminal emulator strips or blocks pasted control characters, which Terminal.app and iTerm2 at least do. It adds the friction of requiring the user to CTRL+D/CTRL+Z at the end of the paste, which we could ameliorate by detecting -----END AGE ENCRYPTED FILE----- and printing a hint.
  3. Enable paste bracketing and buffer input up to the end of paste escape code (or EOF/EOT or -----END AGE ENCRYPTED FILE----- if there is no start of paste escape). This is as smooth a UX as (1) and should be as safe as (2) but is more complex and requires terminal emulator support (which is anyway table stakes for avoiding shell paste attacks).

FiloSottile added a commit that referenced this issue Jul 12, 2022
A partial solution, still missing bracketed paste support.

Updates #364
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants