44

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?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Kerby82
  • 4,934
  • 14
  • 48
  • 74
  • Does the command accept the secret argument through an input file or streams? – Jeff Sep 30 '10 at 13:49
  • 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. – Jonathan Leffler Dec 27 '15 at 20:55
  • Here is a good answer: modify /proc/PID/cmdline using a trick from Scott James Remnant http://netsplit.com/hiding-arguments-from-ps – Martin Apr 08 '16 at 12:29
  • unfortunately the page from Scott James Remnant is not available any more – Tuxinose Nov 29 '22 at 11:50
  • The internez does not forget... https://web.archive.org/web/20181221075231/http://netsplit.com/hiding-arguments-from-ps – Ichthyo Jan 29 '23 at 00:03

12 Answers12

28
  1. 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:

  2. Use standard input:

     mySecret='hello-neo' printenv mySecret | myCommand
    
  3. 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
    
  4. Use environment variables (with caveats). If your program can read them, do this:

     mySecret='hello-neo' myCommand
    
  5. 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
VasiliNovikov
  • 9,681
  • 4
  • 44
  • 62
  • 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 ******************************************` – Meow Aug 12 '16 at 01:32
  • 1
    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 *. – Jason Slobotski Jun 23 '17 at 19:50
  • 5
    @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. – that other guy Jul 11 '18 at 21:50
  • 1
    @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ález – VasiliNovikov Jul 12 '18 at 06:38
  • 3
    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. – Mark Dec 13 '19 at 19:10
  • 3
    @Mark 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. – VasiliNovikov Dec 14 '19 at 23:50
  • 1
    There is no reason to store the secret in an [insecure](https://security.stackexchange.com/q/197784) 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 -` – ominug Mar 10 '21 at 12:50
  • @ominug 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. – VasiliNovikov Mar 27 '21 at 07:19
11

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.

Kelly S. French
  • 12,198
  • 10
  • 63
  • 93
  • 4
    '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. – Jonathan Leffler Oct 01 '10 at 15:16
  • @jonathan - you are right. I was thinking about the C++ approach mainly. I've updated my answer to show the script approach. – Kelly S. French Oct 29 '10 at 14:56
  • 3
    _"... So their commandlines were so long the `ps` truncated it"_------ With `ps ww` it's can show full command line – αғsнιη Jun 30 '18 at 09:32
9

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.

Jorge Fuentes González
  • 11,568
  • 4
  • 44
  • 64
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'.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
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.

Wrikken
  • 69,272
  • 8
  • 97
  • 136
  • 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... – Jason Slobotski Jun 23 '17 at 19:47
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+.

  • 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 Review](/review/late-answers/33540084) – ahuemmer Jan 02 '23 at 17:24
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.

Community
  • 1
  • 1
dogbane
  • 266,786
  • 75
  • 396
  • 414
  • 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. – Kerby82 Sep 30 '10 at 13:20
  • @Kerby82: no - the situation is the same; there is no way to hide command line options from 'ps'. – Jonathan Leffler Sep 30 '10 at 13:26
  • 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. – VasiliNovikov Jan 21 '15 at 11:38
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.

MvG
  • 57,380
  • 22
  • 148
  • 276
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");
}
xingfe123
  • 79
  • 3
-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.

rouble
  • 16,364
  • 16
  • 107
  • 102
  • 1
    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? – VasiliNovikov Feb 26 '21 at 08:13
  • Also the `echo "export ES_PASSWORD=$secret" > x.$$` command will briefly show up to ps – Ben Slade Apr 27 '22 at 16:56
-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"
Daenyth
  • 35,856
  • 13
  • 85
  • 124
  • 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). – Dennis Williamson Sep 30 '10 at 14:58
  • @Dennis Williamson: Nice, I didn't know that! Thanks – Daenyth Sep 30 '10 at 17:11
  • 14
    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. – Jonathan Leffler Oct 01 '10 at 15:23
  • @Johnathan: Aha, I see what you mean. If you combine this with your answer below it's a way to do it though. – Daenyth Oct 01 '10 at 15:26
-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)