Handling secrets (somewhat) securely in shells

(linus.schreibt.jetzt)

50 points | by todsacerdoti 4 days ago

7 comments

  • akendo 41 minutes ago
    I'm surprised to see that very little is known about the Linux kernel keyring. With keyctl[0] you can put secrets scoped to process and ensure that they stay there only for a limited period of time. The tool isn't intuitive, but it's the way I put my 2FA and secrets in shell without bothering about leaking anything.

    [0]: https://www.man7.org/linux/man-pages/man1/keyctl.1.html

    • whatevaa 17 minutes ago
      Nothing to be surprised about here. First time I'm learning of this existence.
  • rasguanabana 14 minutes ago
    An alternative to just exporting a variable is to prepend it to the command. This will keep it unexported for subsequent calls in current shell.

    var=value some_command

    This will still show up in /proc, but a lot of internal tools often rely on environment variables, so it’s kind of inevitable.

  • chasil 3 hours ago
    This article does not mention that environment variables are also visible by process in /proc/*/environ (which has restrictive permissions, but is completely visible to root).

    PuTTY has added a -pwfile option for use in ssh. If not exported, this interface is likely the best for non-key batch use. It seems much superior to sshpass.

    The old .netrc format can be adapted for storage (which appears popular for curl), but I prefer sqlite databases, with permissions removed for all but the owner.

    • yjftsjthsd-h 3 hours ago
      > This article does not mention that environment variables are also visible by process in /proc/*/environ (which has restrictive permissions, but is completely visible to root).

      What isn't visible to root? Maybe if you're willing to go down a really deep rabbit hole you can play that game, but I would generally explicitly exclude root from my threat model.

      • oefrha 1 hour ago
        Defense in depth. Malware is software programmed to do a number of things, not all possible things (well at least until the attacker gets a shell, which is rather noisy). Scanning env vars is trivial, scanning the entire file system and traversing mount points is a bit harder, traversing all memory and guessing what’s a secret is a hell lot harder even for an interactive attacker. If you happen to include some malicious library doing dragnet mining and exfilatration of secrets, you’re more likely to dodge a bullet if you don’t have secrets in env vars than if you do.
    • evgpbfhnr 3 hours ago
      > This article does not mention that environment variables are also visible by process in /proc/*/environ (which has restrictive permissions, but is completely visible to root).

      He's explicitly not using export, so they won't show up there. Plain variables are not in the environment.

      (it's good to bring up this file as well as getting inherited by child processes though)

      • chasil 3 hours ago
        I believe that unexported shell variables will be visible in /proc/*/mem, so it would be prudent to overwrite then unset them as soon as reasonably possible in their usage.
        • evgpbfhnr 3 hours ago
          mem, yes, definitely. I'm not sure how you can protect yourself from that (or root user using ptrace or equivalent debugging tool) though...

          Oh, memfd_secret?

                 The memory areas backing the file created with memfd_secret(2) are visible only to the processes that  have  ac‐
                 cess  to the file descriptor.  The memory region is removed from the kernel page tables and only the page tables
                 of the processes holding the file descriptor map the corresponding physical memory.  (Thus, the pages in the re‐
                 gion can't be accessed by the kernel itself, so that, for example, pointers to the region  can't  be  passed  to
                 system calls.)
          • CableNinja 2 hours ago
            Hm, this is interesting. What kernel version did you find this in? Im curious if this is exposed to other languages
            • sllabres 1 hour ago
              From the man page: Linux 5.14.

              Before Linux 6.5, memfd_secret() was disabled by default and only available if the system administrator turned it on using "secretmem.enable=y" kernel parameter. [...]

              "To prevent potential data leaks of memory regions backed by memfd_secret() from a hybernation image, hybernation is prevented when there are active memfd_secret() users."

      • beached_whale 1 hour ago
        I think that once root is the adversary, all bets are off. The simplest being /proc/*/mem or hooking a debugger up to the process and pausing it...
  • pamcake 39 minutes ago
    I think single-secret files and filesystem permissions are superior between the presented options.

    You don't need root to do what rootless podman does and create and work in directories that processes spawned from your normal user can't normally read using subuids. tmpfs to keep it off actual disks.

  • evgpbfhnr 3 hours ago
    > I’m also intrigued by the potential that type systems have for “tagging” secrets and preventing their propagation beyond where they’re needed

    facet (rust) allows tagging fields as sensitive so they won't show up in logs: https://facet.rs/guide/attributes/#sensitive

    I'm sure other languages have equivalents but I rarely see this.. for example I was about to say serde doesn't do it, but it looks like it's possible with a wrapper type? https://docs.rs/redactrs/latest/redactrs/

    Anyway, this kind of tagging is good, I want more!

  • bandrami 2 hours ago
    20 years of administering Linux systems and I didn't know the read -s trick.
    • wahern 35 minutes ago
      read -s in pdksh does nearly the opposite, saving the string to your history file! See https://man.openbsd.org/ksh#read pdksh is the system shell on OpenBSD, among others, and I just confirmed this is indeed what it does in OpenBSD.
  • mpyne 3 hours ago
    Another trick I've used is to use named FIFOs for commands that expect there to be files rather than stdin/stdout. The command that spits the sensitive credential outputs to the FIFO and blocks.

    The command that needs the sensitive credential to be input is pointed to the FIFO and reads it, and nothing is left over on disk or in the shell's history or memory.

    • SoftTalker 2 hours ago
      I was going to mention this too, it was a pretty common approach we used in batch files. There's a potential race condition if something else can read from the fifo after the secret is there but before the intended process consumes it, so you still need to be careful with permissions.
    • luckman212 3 hours ago
      would very much like to see a small example of how to create, consume, and destroy those FIFOs...
      • stouset 3 hours ago
        https://man7.org/linux/man-pages/man1/mkfifo.1.html

        Pretty simple. This creates a named pipe. One end of a shell command redirects to it, one end redirects from it. rm when finished.

        • akdev1l 2 hours ago
          You can just use process substitution

          cat <(secret-print my-secret)

        • SoftTalker 2 hours ago
          In a shell script situation, you'd typically trap EXIT and ERR and remove the fifo in the handler.