Hiding secret from command line parameter on Unix
Asked Answered
V

13

46

I've a script that launches inside of itself a command with a parameter that is a secret. For example:

#!/bin/bash
command-name secret

While running the command I can read through ps -ef | grep command-name which is the secret.

Is there any way of hiding the secret in a way that through ps -ef, the command line parameter is obfuscated?

Vender answered 30/9, 2010 at 13:13 Comment(7)
Does the command accept the secret argument through an input file or streams?Menashem
Note that the script itself is readable, so the secret is visible to anyone interested in finding it. For a user to be able to run a shell script, the script must be readable — or you have to use a SUID program to run a protected copy of the script, or other similar contortions.Meilhac
Here is a good answer: modify /proc/PID/cmdline using a trick from Scott James Remnant netsplit.com/hiding-arguments-from-psSoutheastwardly
unfortunately the page from Scott James Remnant is not available any moreHeartburn
The internez does not forget... web.archive.org/web/20181221075231/http://netsplit.com/…Unsearchable
Someone out there has been working on this for Linux. …can't vouch for its quality.: github.com/hackerschoice/zapperGrigri
@Martin, the problem with that is that it only works after your program has finished starting; there's a race condition built in. It's better than nothing, but it's much worse than passing your secrets in a way that doesn't involve the command line at all.Ironworks
N
28

First, you can NOT hide command line arguments. They will still be visible to other users via ps aux and cat /proc/$YOUR_PROCESS_PID/cmdline at the time of launching the program (before the program has a chance to do run-time changes to arguments). Good news is that you can still have a secret by using alternatives:

  1. Use standard input:

     mySecret='hello-neo' printenv mySecret | myCommand
    
  2. Use a dedicated file if you want to keep the secret detached from the main script (note that you'd be recommended to use full disc encryption and make sure the file has correct chmod permissions):

     cat /my/secret | myCommand
    
  3. Use environment variables (with caveats). If your program can read them, do this:

     mySecret='hello-neo' myCommand
    
  4. Use temporary file descriptor:

     myCommand <( mySecret='hello-neo' printenv mySecret )
    

In the last case your program will be launched like myCommand /dev/fd/67, where the contents of /dev/fd/67 is your secret (hello-neo in this example).


In all of the above approaches, be wary of leaving the command in bash command history (~/.bash_history). You can avoid this by either running the command from a script (file), or by interactively prompting yourself for password each time:

read -s secret
s=$secret printenv s | myCommand  # approach 2
myCommand <( s=$secret printenv s )  # approach 3
secret=$secret myCommand  # approach 4
export secret && myCommand  # another variation of approach 4
Nabala answered 21/1, 2015 at 11:4 Comment(8)
Yes you can, I've seen programs mask their command line arguments with '*' all the time. It's like this in the output of ps -ef: /xware/lib/EmbedThunderManager ******************************************Tax
But those args can likely be dug out of /proc somewhere. In your case sounds like ps may jsut be truncating it because they're entering the "secrets" after a long string of *.Hedger
@VasyaNovikov I have verified that Meow is correct. mysql -u root -p mypassword shows up as mysql -u root -px xxxxxxxxxx in both top and ps -ef. This is not related to scrolling or truncation.Pannonia
@thatotherguy what you observe in top is not the only test you can make. For example, mysql could have forked it's own process with a different command line arguments (passing password in environment instead). In this case, any foreign process was still able to intercept the password while mysql started running but before it forked itself. Same with answer from @JorgeFuentesGonzálezNabala
Env vars may be better than args, but not that much better. cat /proc/1234/environ | tr '\0\033' '\n~' will show env vars as long as you have privs (root or owner). It may be easier to hide env vars (setenv) once the process is running, but there's still some time for an attacker to read the file before the process is able to hide the data. The sniffing process won't necessarily have too few perms to read a sensitive process's environment.Apocalypse
@Apocalypse Thanks for the remark on same-user permissions. Indeed, env doesn't protect you against your own user. But it does protect against everyone else. That is a HUGE difference to me.Nabala
There is no reason to store the secret in an insecure env var before using it. Maintain the secret in a secured file and pass the file path to commands which need the secret: my-command /my/secret (replacing alternative 4). Or pass it via STDIN: cat /my/secret | my-command - (replacing alternative 3). Alternatives for temporary secrets: my-command <(generate-password); generate-password | my-command -Dincolo
@Dincolo thanks for your comment, very insightful! I've added your link to the solution with env variables. Note that the concern doesn't apply to any other solutions except the 2-nd one because only in the second solution the env variables are actually passed to the myCommand command.Nabala
C
12

If the secret doesn't change between executions, use a special configuration file, ".appsecrets". Set the permissions of the file to be read-only by owner. Inside the file set an environment variable to the secret. The file needs to be in the home directory of the user running the command.

#!/bin/bash  
#filename: .appsecrets
set SECRET=polkalover  

Load the config file so the environment variable gets set.

. ~/.appsecrets

What I've seen done:

1)
echo $SECRET | command

works if the command prompts for the password from stdin AND if 'echo' is a builtin of your shell. We were using Korn.

2)
password=$ENV{"SECRET"};

works if you have control of the code (e.g. in perl or C++)

3)
. ./.app.config #sets the environment variables
isql -host [host] -user [user] -password <<SECRET
${SQLPASSWORD}
SECRET

works if the command can accept the secret from std-in. One limitation is that the <<string has to be the last argument given to the command. This might be troublesome if there is a non-optional arg that has to appear after -password

The benefit of this approach is you can arrange it so the secret can be hidden in production. Use the same filename in production but it will be in the home directory of the account that runs the command in production. You can then lock down access to the secret like you would access to the root account. Only certain people can 'su' to the prod account to view or maintain the secret while developers can still run the program because they use their own '.appsecret' file in their home directory.

You can use this approach to store secured information for any number of applications, as long as they use different environment variable names for their secrets.

(WRONG WAY)
One old method I saw the DBAs use was to set SYBASE to "/opt/././././././././././././././././././././././././././././././././././sybase/bin". So their commandlines were so long the ps truncated it. But in linux I think you might be able to sniff out the full commandline from /proc.

Cimino answered 30/9, 2010 at 14:18 Comment(3)
'The command line will only show the given environment variable, not its value' -- These comments are WRONG on two counts. First, dotting the ~/.appsecrets file has reset the command line arguments of the shell to have just one, the value of $1 is now 'SECRET=polkalover'; there is no variable, let alone environment variable, called SECRET. Secondly, if you get a variable SECRET created (not hard), running 'command $SECRET' expands $SECRET before executing the command, and ps shows the expanded version of the information.Meilhac
@jonathan - you are right. I was thinking about the C++ approach mainly. I've updated my answer to show the script approach.Cimino
"... So their commandlines were so long the ps truncated it"------ With ps ww it's can show full command lineStilted
S
11

I saw it on another post. This is the easiest way under Linux.

This modifies the memory part of command line that all other programs see.

strncpy(argv[1], "randomtrash", strlen(argv[1]));

You can also change the name of the process, but only when read from the command line. Programs like top will show the real process name:

strncpy(argv[0], "New process name", strlen(argv[0]));

Don't forget to copy maximum strlen(argv[0]) bytes because probably there's no more space allocated.

I think that arguments can only be found in the portion of the memory that we modify so I think that this works like a charm. If someone knows something accurate about this, please comment.

VasyaNovikov note: The password can still be intercepted after the program has invoked but before it started doing the changes you described.

Statocyst answered 25/6, 2014 at 11:21 Comment(2)
Note that the password can still be intercepted after the program has invoked but before it started doing the changes you described. This includes start-up time, so it's not zero.Nabala
@VasyaNovikov nice tip. Going to add it.Homozygous
M
8

The only way to conceal your secret argument from ps is not to provide the secret as an argument. One way of doing that is to place the secret in a file, and to redirect file descriptor 3 to read the file, and then remove the file:

echo secret > x.$$
command 3<x.$$
rm -f x.$$

It isn't entirely clear that this is a safe way to save the secret; the echo command is a shell built-in, so it shouldn't appear in the 'ps' output (and any appearance would be fleeting). Once upon a very long time ago, echo was not a built-in - indeed, on MacOS X, there is still a /bin/echo even though it is a built-in to all shells.

Of course, this assumes you have the source to command and can modify it to read the secret from a pre-opened file descriptor instead of from the command line argument. If you can't modify the command, you are completely stuck - the 'ps' listing will show the information.

Another trick you could pull if you're the command owner: you could capture the argument (secret), write it to a pipe or file (which is immediately unlinked) for yourself, and then re-exec the command without the secret argument; the second invocation knows that since the secret is absent, it should look wherever the first invocation hid the secret. The second invocation (minus secret) is what appears in the 'ps' output after the minuscule interval it takes to deal with hiding the secret. Not as good as having the secret channel set up from the beginning. But these are indicative of the lengths to which you have to go.

Zapping an argument from inside the program - overwriting with zeroes, for example - does not hide the argument from 'ps'.

Meilhac answered 30/9, 2010 at 13:41 Comment(5)
I'd add a umask 077 before the echo to ensure that only the user can read the file.Cushat
If you want ultra security, simply removing a file won't delete it's contents. Saving a password in a plain file is insecure.Homozygous
@JorgeFuentesGonzález: And if it is a journalled file system, overwriting the file won't erase the old contents, either.Meilhac
you can always securely erase a file using utilities like wipeJacquelynejacquelynn
I got this solution working (if I understood it correctly) with this: echo foo > bar; exec 3<bar; read line <&3; echo $line. Output: foo. Because type read shows read is a shell builtin ps should not see it.Duodenitis
C
7

The expect library was created partially for these kind of things, so you can still provide a password / other sensitive information to a process without having to pass it as an argument. Assuming that when 'secret' isn't given the program asks for it of course.

Cardioid answered 30/9, 2010 at 13:44 Comment(2)
I use expect quite often to keep passwords among other things off the command line. Can be used to log into remote systems, respond to password prompts, etc...Hedger
expect is a poor substitute for using any of the available secure interfaces explicitly designed for programmatic use. --password-file <(printf '%s' "$password") is saner and less fragile than trying to wrap an interface designed for humans (when programs write prompts, they expect there to be a human reading the prompt, so they can change what the prompts asks for and in what order between versions; whereas things designed to be programmatic interfaces are generally designed to be stable across versions, amenable to backwards-compatible updates)Ironworks
A
2

Per the following article: https://www.cyberciti.biz/faq/linux-hide-processes-from-other-users/ you can configure the OS to hide / separate the processes from each other with the hidepid mount option for the /proc, requires Linux kernel 3.2+.

Asleep answered 2/1, 2023 at 8:59 Comment(1)
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From ReviewAtomism
A
1

There's no easy way. Take a look at this question I asked a while ago:

Hide arguments from ps

Is command your own program? You could try encrypting the secret and have the command decrypt it before use.

Angst answered 30/9, 2010 at 13:18 Comment(3)
in your case the problem was overcome by configuring ad-hoc the conf for reading the private key by default. In this case the problem is differente.Vender
@Kerby82: no - the situation is the same; there is no way to hide command line options from 'ps'.Meilhac
This is a different question. In your question you wanted to protect yourself against other people with the same rights (unix user). In this question it is asked how to hide a secret from other unix users.Nabala
V
1

You can use LD_PRELOAD to have a library manipulate the command line arguments of some binary within the process of that binary itself, where ps does not pick it up. See this answer of mine on Server Fault for details.

Verner answered 19/5, 2018 at 22:44 Comment(0)
P
1

You could hide password parameter in environment variable and re-fork script with masked value of old password. Here is an example:

#!/bin/bash

#check if it is the first running or re-forking by env vars 'my_password' and 'null_password' 
[[ -z "$my_password" ]] && [[ -z "$null_password" ]] && {
    #copy input arguments to new array 'args[]'
    args=("$@")
    #for every array element do
    for ((i=0; i<${#args[@]}; i++)); do
        [[ "${args[$i]}" == "--password" ]] && {
             #if we found --password element, switch to the next element
             let i++
             #and save it to 'my_password' variable
             my_password="${args[$i]}"
             #replace array element by mask symbols 'top_secret'
             args[$i]="top_secret"
        }
    done
    #check if 'my_password' var is null (it is a special case, it is permited), set 'null_password' flag
    [[ -z "$my_password" ]] && null_password=true
    export my_password
    export null_password
    #create new fork in place of current process (with new args)
    exec $0 "${args[@]}"
    #current process has finished, this string will newer run
}

#Insert your code here
echo -e "\n\nYour password var is '$my_password'"
for arg in "${@}"; do let i++; echo "Argument${i}  is '$arg'"; done
echo "Look at proc info:"
ps -ef | pgrep -fa $0
sleep 180

How it looks in cmd line:

home@stick:~$ ./get_password.sh --user "t e s t" --password "my strong password" --dir "/opt/my dir" &
[1] 5121
home@stick:~$ 

Your password var is 'my strong password'
Argument1  is '--user'
Argument2  is 't e s t'
Argument3  is '--password'
Argument4  is 'top_secret'
Argument5  is '--dir'
Argument6  is '/opt/my dir'
Look at proc info:
5121 /bin/bash /home/home/get_password.sh --user t e s t --password top_secret --dir /opt/my dir

home@stick:~$ ps -ef | grep get_password.sh
home        5121    1790  0 01:20 pts/0    00:00:00 /bin/bash /home/home/get_password.sh --user t e s t --password top_secret --dir /opt/my dir
home        5129    1790  0 01:21 pts/0    00:00:00 grep --color=auto get_password.sh
Phia answered 30/11, 2023 at 11:33 Comment(2)
Quintessence from your post is: if you can hand the password over to a program that can hide its command line (most database frontends do that) by exec, your shell script exposes the password only for a short time (while it is running, before the exec).Heartburn
Yes, that's right.Phia
T
0

may be you can do like this:

#include <boost/algorithm/string/predicate.hpp>
void hide(int argc, char** argv, std::string const & arg){
    for(char** current = argv; current != argv+ argc ;++current){
        if(boost::algorithm::starts_with(*current, "--"+arg)){
            bzero(*current, strlen(*current));
        }
    }
}
int main(int argc, char** argv){
   hide(argc,  argv, "password");
}
Tasty answered 20/12, 2017 at 6:4 Comment(1)
Some explanation would have been great.Divertissement
A
-1

Here is one way to hide a secret in an environment variable from ps:

#!/bin/bash
read -s -p "Enter your secret: " secret

umask 077 # nobody but the user can read the file x.$$ 
echo "export ES_PASSWORD=$secret" > x.$$
. x.$$ && your_awesome_command
rm -f x.$$ # Use shred, wipe or srm to securely delete the file


In the ps output you will see something like this:

$ps -ef | grep your_awesome_command
root     23134     1  0 20:55 pts/1    00:00:00  . x.$$ && your_awesome_command

Elastalert and Logstash are examples of services that can access passwords via environment variables.

Alphonse answered 28/1, 2019 at 5:2 Comment(2)
Why write to a file at all? Why not just ES_PASSWORD=$secret your_awesome_command ? Writing secrets to a file if you didn't want that seems like a very strange idea. What if the computer crashes? The cleanup hook won't be run. Why add complexity?Nabala
Also the echo "export ES_PASSWORD=$secret" > x.$$ command will briefly show up to psGlazing
I
-2

If the script is intended to run manually, the best way is to read it in from STDIN

#!/bin/bash
read -s -p "Enter your secret: " secret

command "$secret"
Iona answered 30/9, 2010 at 13:47 Comment(4)
Bash 2.04 and greater have read -s which doesn't echo the input characters so you only need to use stty with shells that don't have that (zsh also has it).Flocculent
@Dennis Williamson: Nice, I didn't know that! ThanksIona
The trouble with this is that the secret is exposed on the command line and will show up in ps output - but the question is asking how to avoid that exposure.Meilhac
@Johnathan: Aha, I see what you mean. If you combine this with your answer below it's a way to do it though.Iona
L
-2

I always store sensitive data in files that I don't put in git and use the secrets like this:

$(cat path/to/secret)
Lanate answered 31/5, 2021 at 8:14 Comment(2)
Better use $/gpg -qd path/to/secret)!Trimming
This is off topic. The question isn't how to store the password, it's how to hide the password if it's used on a command line.Glazing

© 2022 - 2024 — McMap. All rights reserved.