Handling secrets (somewhat) securely in shells
16 points by polywolf
16 points by polywolf
The article advocates for using variables as a mean to "hide" secrets leaking, but it doesn't.
Both versions will leak user/password from /proc :
curl -u "alice:Hunter2" https://domain.tld
curl -u "$USER:$(cat pass.txt)" https://domain.tld
The only "reliable" way to hide a secret from other users on your machine is to have your credentials read from a file with hardened permissions.
I mean, that's not what's shown in the article tho is it? An analogous example would be something more like:
curl -u @<(echo "$USER:$(cat pass.txt)") https://domain.tld
Which, while the subprocess would still be visible in /proc for a brief period of time, is a much tighter window for a potential attacker to hit.
No window at all, actually. The shell expands <(...) into something like /dev/fd/5, and unless you're in an odd environment, echo is using the shell builtin and will not result in another fork/exec in the first place.
EDIT: oh, but the cat of course is not builtin. They're not doing that in the article either, though.
Would mounting /proc with hidepid also help to make sure credentials are hidden from other users?
Was the article modified after posting? The "Shell history leakage" section is using files to communicate the credential, and the "Process information leakage" section warns of exactly the /proc leakage you say the article ignores.
The article mentions using files, but it has a 3rd section advising to use variables because files can leak secrets through your shell history.
From my understanding of the article, it puts the "variable method" as the best way to not leak secrets, but I might be misunderstanding.
The critical point you may have missed is that the article is not using $(echo "$mysecretholdingvar") syntax which you may be more familiar with, but <(echo "$mysecretholdingvar"). That's where it's introducing files.
The latter will...
echo "$mysecretholdingvar" (with FD 6 used for its stdout)Many arguments for curl allow a @ prefix to indicate the argument's value should be read from the file path that follows it. Its actual command line ends up being something like curl -H @/dev/fd/5, which is perfectly safe to expose in /proc. curl ends up reopening the inherited FD (the read end of the the pipe hooked up to the subshell), and your secret remains secret.
A method I started using with curl specifically in scripts:
$ export token='asdfghjkl'
$ curl --variable %token --expand-header 'X-Auth-Token: {{token}}' https://example.org/auth
$ export user='alice' password='hunter2'
$ curl --variable %user --variable %pass --expand-user '{{user}}:{{password}}' https://example.org/auth
This makes sure that it can't be readable from curl's argv; the secrets will only be readable if you have access to the curl process's environment. Variables were added in v8.3.0, so depending on how old your system is, you might not have access to it...
In fact, you could lock it down even more and mitigate visibility in the environment by using curl's variable expansion more carefully:
$ umask go-rx
$ echo 'my token' > tokenfile
$ curl --variable %token@tokenfile --expand-header 'X-Auth-Token: {{token}}' https://example.org/auth
So, I researched best practices around this recently for work, and probably the best options right now are 1Password CLI (op) if you have an account: https://developer.1password.com/docs/cli/secret-references/
Or if you don't then sops: https://getsops.io/docs/#passing-secrets-to-other-processes
Both of these are secret vaults (similar to HashiCorp Vault) and the CLIs can both run child processes and securely inject secrets into them as environment variables. It mostly eliminates the need to know shell tricks and their variations in different shells.
Interesting! I didn't know that:
echo is a shell builtin, so it doesn't show up in the process table
And that:
echo <(echo secret token)
/dev/fd/63
is a pretty neat trick as well :)
Thanks for sharing!
This really should be an operating system service. On Windows it is.
In my local workstation, I mostly do read to avoid secrets in my shell history. I used to use HISTCONTROL, but then I noticed that it wasn't on by default on some systems... and not keeping history is a pain, so I switched to read.
The proper solution would be better isolation of /proc between users, but I guess that ship sailed eons ago :( (I mean, I guess no distro will change that because it might break stuff.)
In more serious environments, my usual approach is to work as hard as possible to avoid requiring secrets. I've found stuff like GitHub workflows being able to authenticate to AWS via OIDC without secrets, which is quite fancy. But in the end, normally you always require some annoying scripting to use secrets safely :(