Unix & Linux
command-line security password read linux-audit
Updated Fri, 20 May 2022 07:33:53 GMT

Sniff password entered with read and passed as a command line argument


I'd like to show that entering passwords via read is insecure.

To embed this into a half-way realistic scenario, let's say I use the following command to prompt the user for a password and have 7z create an encrypted archive from it:

read -s -p "Enter password: " pass && 7z a test_file.zip test_file -p"$pass"; unset pass

My first attempt at revealing the password was by setting up an audit rule:

auditctl -a always,exit -F path=/bin/7z -F perm=x

Sure enough, when I execute the command involving read and 7z, there's a log entry when running ausearch -f /bin/7z:

time->Thu Jan 23 18:37:06 2020
type=PROCTITLE msg=audit(1579801026.734:2688): proctitle=2F62696E2F7368002F7573722F62696E2F377A006100746573745F66696C652E7A697000746573745F66696C65002D7074686973206973207665727920736563726574
type=PATH msg=audit(1579801026.734:2688): item=2 name="/lib64/ld-linux-x86-64.so.2" inode=1969104 dev=08:03 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL cap_fp=0 cap_fi=0 cap_fe=0 cap_fver=0 cap_frootid=0
type=PATH msg=audit(1579801026.734:2688): item=1 name="/bin/sh" inode=1972625 dev=08:03 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL cap_fp=0 cap_fi=0 cap_fe=0 cap_fver=0 cap_frootid=0
type=PATH msg=audit(1579801026.734:2688): item=0 name="/usr/bin/7z" inode=1998961 dev=08:03 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL cap_fp=0 cap_fi=0 cap_fe=0 cap_fver=0 cap_frootid=0
type=CWD msg=audit(1579801026.734:2688): cwd="/home/mb/experiments"
type=EXECVE msg=audit(1579801026.734:2688): argc=6 a0="/bin/sh" a1="/usr/bin/7z" a2="a" a3="test_file.zip" a4="test_file" a5=2D7074686973206973207665727920736563726574
type=SYSCALL msg=audit(1579801026.734:2688): arch=c000003e syscall=59 success=yes exit=0 a0=563aa2479290 a1=563aa247d040 a2=563aa247fe10 a3=8 items=3 ppid=2690563 pid=2690868 auid=1000 uid=1000 gid=1000 euid=1000 suid=1000 fsuid=1000 egid=1000 sgid=1000 fsgid=1000 tty=pts17 ses=1 comm="7z" exe="/usr/bin/bash" key=(null)

This line seemed the most promising:

type=EXECVE msg=audit(1579801026.734:2688): argc=6 a0="/bin/sh" a1="/usr/bin/7z" a2="a" a3="test_file.zip" a4="test_file" a5=2D7074686973206973207665727920736563726574

But the string 2D7074686973206973207665727920736563726574 is not the password I entered.

My question is twofold:

  • Is audit the right tool to get at the password? If so, is there something I have to change about the audit rule?
  • Is there an easier way, apart from audit, to get at the password?

I'm aware that 7z can prompt for passwords by itself.




Solution

What's insecure is not read(2) (the system call to read data from a file). It isn't even read(1) (the shell builtin to read a line from standard input). What's insecure is passing the password on the command line.

When the user enters something that the shell reads with read, that thing is visible to the terminal and to the shell. It isn't visible to other users. With read -s, it isn't visible to shoulder surfers.

The string passed on the command line is visible in the audit logs. (The string may be truncated, I'm not sure about that, but if it is it would be only for much longer strings than a password.) It's just encoded in hexadecimal when it contains characters such as spaces that would make the log ambiguous to parse.

$ echo 2D7074686973206973207665727920736563726574 | xxd -r -p; echo
-pthis is very secret
$ perl -l -e 'print pack "H*", @ARGV' 2D7074686973206973207665727920736563726574
-pthis is very secret

That's not the main reason why you shouldn't pass a secret on the command line. After all, only the administrator should be able to see audit logs, and the administrator can see everything if they want. It is worse to have the secret in the logs, though, because they may be accessible to more people later (for example through an improperly secured backup).

The main reason why you shouldn't pass a secret on the command line is that on most systems the command line is also visible to other users. (There are hardened systems where this isn't the case, but that's typically not the default.) Anyone running ps, top, cat /proc/*/cmdline or any similar utility at the right time can see the password. The 7z program overwrites the password soon after it starts (as soon as it's been able to make an internal copy), but that only reduces the window of danger, it doesn't remove the vulnerability.

Passing a secret in an environment variable is safe. The environment is not visible to other users. But I don't think 7z supports that. To pass the password without making it visible through the command line, you need to pass it as input, and 7z reads from the terminal, not from stdin. You can use expect to do that (or pexpect if you prefer Python to TCL, or Expect.pm in Perl, or expect in Ruby, etc.). Untested:

read -s -p "Enter password: " pass
pass=$pass expect \
    -c 'spawn 7z a -p test_file.zip test_file' \
    -c 'expect "assword:" {send $::env(pass)}' \
    -c 'expect eof' -c 'catch wait result'
unset pass




Comments (2)

  • +0 – Thanks for your answer! In your example, isn't the password visible when running read -s -p "Enter password: " pass to other users by running ps, top, cat /proc/*/cmdline? — May 04, 2020 at 06:19  
  • +0 – @MatthiasBraun No. It doesn't appear on any command line. The data inside a process's memory and the data that a process reads or writes are never visible to other users (and not even to most other processes of the same user on a typical modern Linux due to restrictions on debugging). — May 04, 2020 at 07:29