Take the 2-minute tour ×
Server Fault is a question and answer site for professional system and network administrators. It's 100% free, no registration required.

I'm running a software daemon that requires for certain actions to enter a passphrase to unlock some features which looks for example like that:

$ darkcoind masternode start <mypassphrase>

Now I got some security concerns on my headless debian server.

Whenever I search my bash history for example with Ctrl+R I can see this super strong password. Now I imagine my server is compromized and some intruder has shell access and can simply Ctrl+R to find my passphrase in the history.

Is there a way to enter the passphrase without it to be shown in bash history, ps, /proc or anywhere else?


Update 1: Passing no password to the daemon throws an error. This is no option.


Update 2: Don't tell me to delete the software or other helpful hints like hanging the developers. I know this is not a best-practice example but this software is based on bitcoin and all bitcoin based clients are some kind of json rpc server which listens to these commands and its a known security issue still being discussed (a, b, c).


Update 3: The daemon is already started and running with the command

$ darkcoind -daemon

Doing ps shows only the startup command.

$ ps aux | grep darkcoin
user     12337  0.0  0.0  10916  1084 pts/4    S+   09:19   0:00 grep darkcoin
user     21626  0.6  0.3 1849716 130292 ?      SLl  May02   6:48 darkcoind -daemon

So passing the commands with the passphrase does not show up in ps or /proc at all.

$ darkcoind masternode start <mypassphrase>
$ ps aux | grep darkcoin
user     12929  0.0  0.0  10916  1088 pts/4    S+   09:23   0:00 grep darkcoin
user     21626  0.6  0.3 1849716 130292 ?      SLl  May02   6:49 darkcoind -daemon

This leaves the question where does the history show up? Only in .bash_history?

share|improve this question

We're looking for long answers that provide some explanation and context. Don't just give a one-line answer; explain why your answer is right, ideally with citations. Answers that don't include explanations may be removed.

1  
The first question has to be: what happens if you start the daemon without the passphrase argument. Does it just prompt for it? –  MadHatter May 2 at 15:32
29  
I don't think there's an answer that will work. The inability to prompt for a passphrase is is a major shortcoming in the daemon. If it's free software, get a programmer in and fix it; don't forget to publish your changes. If it's proprietary software, ring up the vendor and shout at them (that won't fix anything, but it'll make you feel better). –  MadHatter May 2 at 15:55
3  
Check your documentation, it may support reading that password from a system environment variable. –  Elliott Frisch May 2 at 18:11
3  
Even if the password is not given on the command line to the daemon, it is still problematic to give it on the command line of any other command. It is only visible in the ps output for a very short time, but a process running in the background could still pick it up. But it is of course still worthwhile making it harder to pick up the password. –  kasperd May 3 at 8:05
2  
Look at the answers to this question, they deal with exactly this issue. –  dotancohen May 4 at 16:05

8 Answers 8

up vote 39 down vote accepted

Really, this should be fixed in the application itself. And such applications should be open source, so that fixing the issue in the app itself should be an option. A security related application which makes this kind of mistake might make other mistakes as well, so I wouldn't trust it.

Simple interposer

But you were asking for a different way, so here is one:

#define _GNU_SOURCE
#include <dlfcn.h>

int __libc_start_main(
    int (*main) (int, char * *, char * *),
    int argc, char * * ubp_av,
    void (*init) (void),
    void (*fini) (void),
    void (*rtld_fini) (void),
    void (* stack_end)
  )
{
  int (*next)(
    int (*main) (int, char * *, char * *),
    int argc, char * * ubp_av,
    void (*init) (void),
    void (*fini) (void),
    void (*rtld_fini) (void),
    void (* stack_end)
  ) = dlsym(RTLD_NEXT, "__libc_start_main");
  ubp_av[argc - 1] = "secret password";
  return next(main, argc, ubp_av, init, fini, rtld_fini, stack_end);
}

Compile this with

gcc -O2 -fPIC -shared -o injectpassword.so injectpassword.c -ldl

then run your process with

LD_PRELOAD=$PWD/injectpassword.so darkcoind masternode start fakepasshrase

The interposer library will run this code before the main function from your application gets executed. It will replace the last command line argument by the actual password in the call to main. The command line as printed in /proc/*/cmdline (and therefore seen by tools such as ps) will still contain the fake argument, though. Obviously you'd have to make the source code and the library you compile from it readable only to yourself, so best operate in a chmod 0700 directory. And since the password isn't part of the command invocation, your bash history is safe as well.

More advanced interposer

If you want to do anything more elaborate, you should keep in mind that __libc_start_main gets executed before the runtime library has been properly initialized. So I'd suggest avoiding any function calls unless they are absolutely essential. If you want to be able to call functions to your heart's content, make sure you do so just before main itself gets invoked, after all initialization is done. For the following example I have to thank Grubermensch who pointed out how to hide a password passed as command line argument which brought getpass to my attention.

#define _GNU_SOURCE
#include <dlfcn.h>
#include <unistd.h>

static int (*real_main) (int, char * *, char * *);

static int my_main(int argc, char * * argv, char * * env) {
  char *pass = getpass(argv[argc - 1]);
  if (pass == NULL) return 1;
  argv[argc - 1] = pass;
  return real_main(argc, argv, env);
}

int __libc_start_main(
    int (*main) (int, char * *, char * *),
    int argc, char * * ubp_av,
    void (*init) (void),
    void (*fini) (void),
    void (*rtld_fini) (void),
    void (* stack_end)
  )
{
  int (*next)(
    int (*main) (int, char * *, char * *),
    int argc, char * * ubp_av,
    void (*init) (void),
    void (*fini) (void),
    void (*rtld_fini) (void),
    void (* stack_end)
  ) = dlsym(RTLD_NEXT, "__libc_start_main");
  real_main = main;
  return next(my_main, argc, ubp_av, init, fini, rtld_fini, stack_end);
}

This prompts for the password, so you no longer have to keep the interposer library a secret. The placeholder argument is reused as password prompt, so invoke this like

LD_PRELOAD=$PWD/injectpassword.so darkcoind masternode start "Password: "

Another alternative would read the password from a file descriptor (like e.g. gpg --passphrase-fd does), or from x11-ssh-askpass, or whatever.

share|improve this answer
3  
Although I don't understand and can't test the code, I get the gist of it, and this looks like an actual answer and should be the top answer. –  Mark Henderson May 3 at 22:40
    
This is indeed awesome. –  vertoe May 4 at 11:19
    
Awesome. As far as I can tell this ought to work. Of course you need access to the source and be able to recompile. The password is readable in the source and the compiled file(s) if you use "strings" or something similar so better make sure that nobody else can read those. –  Tonny May 4 at 12:03
1  
It should be possible to take the password on STDIN and still have this work, which removes the strings vulnerability. See SO: Hide password input on terminal. –  Grubermensch May 4 at 17:32
    
@Grubermensch You are right. –  Tonny May 5 at 21:32

It's not just the history. It is going to show up in ps output as well.

Whoever wrote that piece of software should be hung, drawn and quartered. It is an absolute NO to have to supply a password on the command-line regardless whatever software it is.
For a daemon process it is even MORE unforgivable...

Besides rm -f on the software itself I don't know any solution for this. Honestly: Find other software to get the job done. Don't use such junk.

share|improve this answer
6  
Thanks for not being helpful at all. This is a long discussed security issue, still unsolved and I need a better workaround than rm -f now. –  vertoe May 2 at 15:47
17  
Actually, he's being very helpful. If you're passing the passphrase as an argument, it WILL show up in ps. So until the dev can fix that, he's suggesting using something else. –  Safado May 2 at 15:49
3  
Then you better start writing another Operating System. There is NO other solution currently available that I am aware of. By God I wish there was one. You are not the only one with this problem. –  Tonny May 2 at 15:51
8  
vertoe, don't get snippy. You can ask for a way to pass it on little slips of paper, but that doesn't mean any such way automatically exists. read_x is fine, but still exposes the passphrase via eg ps, so it's no better than the rm solution. –  MadHatter May 2 at 15:57
4  
Before y'all go and throw another +1 on this not-really-an-answer and complain that this is impossible, I suggest you review MvG's answer below –  Mark Henderson May 3 at 22:45

This will clear the ps output.

BE VERY AWARE: This could break the application. You are duly warned that here be dragons.

  • Foreign processes shouldn't be fiddling around in a processes memory.
  • If the process relies on this region for the password, you may break your application.
  • Doing this could corrupt any working data you have in that process.
  • This is an insane hack.

Now you are duly notified of these dire warnings. This will clear the output displayed in ps. It will not clear your history, nor will it clear the bash job history (such as running the process like myprocess myargs &). But ps will no longer show the arguments.

#!/usr/bin/python
import os, sys
import re

PAGESIZE=4096

if __name__ == "__main__":
  if len(sys.argv) < 2:
    sys.stderr.write("Must provide a pid\n")
    sys.exit(1)

  pid = sys.argv[1]

  try:
    cmdline = open("/proc/{0}/cmdline".format(pid)).read(8192)

    ## On linux, at least, argv is located in the stack. This is likely o/s
    ## independent.
    ## Open the maps file and obtain the stack address.
    maps = open("/proc/{0}/maps".format(pid)).read(65536)
    m = re.search('([0-9a-f]+)-([0-9a-f]+)\s+rw.+\[stack\]\n', maps)
    if not m:
      sys.stderr.write("Could not find stack in process\n");
      sys.exit(1)

    start = int("0x"+m.group(1), 0)
    end = int("0x"+m.group(2), 0)

    ## Open the mem file
    mem = open('/proc/{0}/mem'.format(pid), 'r+')
    ## As the stack grows downwards, start at the end. It is expected
    ## that the value we are looking for will be at the top of the stack
    ## somewhere
    ## Seek to the end of the stack minus a couple of pages.
    mem.seek(end-(2*PAGESIZE))

    ## Read this buffer to the end of the stack
    stackportion = mem.read(8192)
    ## look for a string matching cmdline. This is pretty dangerous.
    ## HERE BE DRAGONS
    m = re.search(cmdline, stackportion)
    if not m:
      ## cause this is an example dont try to search exhaustively, just give up
      sys.stderr.write("Could not find command line in the stack. Giving up.")
      sys.exit(1)

    ## Else, we got a hit. Rewind our file descriptor, plus where we found the first argument.
    mem.seek(end-(2*PAGESIZE)+m.start())
    ## Additionally, we'll keep arg0, as thats the program name.
    arg0len = len(cmdline.split("\x00")[0]) + 1
    mem.seek(arg0len, 1)

    ## lastly overwrite the remaining region with nulls.
    writeover = "\x00" * (len(cmdline)-arg0len)
    mem.write(writeover)

    ## cleanup
    mem.close()

  except OSError, IOError:
    sys.stderr.write("Cannot find pid\n")
    sys.exit(1)

Invoke the program by saving it, chmod +x it. Then doing ./whatever <pidoftarget> If this works, it will produce no output. If it fails, it will complain about something and quit.

share|improve this answer
15  
. . . this is both creative and frightening. –  voretaq7 May 2 at 22:06
    
EEK! Now I'm scared. –  Janne Pikkarainen May 3 at 8:59
    
Yikkes, that could work... I am not sure it something like AppArmor would catch this ? Also the virusscanner could potentially catch this and cause havoc by blocking the offending account which would be 'root'. There be dragons indeed.... –  Tonny May 3 at 10:49
    
@Tonny For protected domains, SELinux would prevent this. Your basic Unix permissions (DAC) lacks enough subject granularity to offer any protections from this behaviour (permits modification of processes memory within the same UID). Anyways, its not a bug -- its a feature. I believe this is how gdb can modify the memory of running processes (with much more surgical precision than this I might add). –  Matthew Ife May 3 at 13:17

Can you pass the argument from a file, accessible only by root or the required user?

It's a HUGE no-no to type passwords in the console, but last recourse...begin your line with a space so it doesn't appear in the history.

share|improve this answer
    
There was a shell option which enables it, but I think it wasn't enabled by default. –  heinrich5991 May 2 at 19:24

Maybe this works (?):

darkcoind masternode start `cat password.txt`
share|improve this answer
1  
Or even darkcoind masternode start `head -1`, if you want to enter the password manually. –  kasperd May 2 at 19:58
9  
The passphrase is still available via ps and similar utilities. –  voretaq7 May 2 at 21:17
1  
Moving from a plaintext password in .bash_history to a plaintext password in password.txt gains you what, exactly? –  MikeyB May 3 at 14:24
1  
@MikeyB: There is a small win: you won't accidentially expose it while searching through your history while someone is looking over your shoulder. –  MvG May 3 at 20:49
1  
@MikeyB, you may create and remove that file each time. –  RiaD May 4 at 8:11

Unfortunately, if your darkcoind command expects the password as a command-line argument, then it will be exposed through utilities such as ps. The only real solution is to educate the developers.

While the ps exposure might be unavoidable, you could at least keep the password from being written out in the shell history file.

$ xargs darkcoind masternode start

password

CtrlD

The history file should only record xargs darkcoind masternode start, not the password.

share|improve this answer
    
Or if you're using bash, put ignorespace in $HISTCONTROL, and then you can prevent any command from going into the shell history by prefixing the command with a space. –  derobert May 5 at 17:48

You can keep the password out of your shell's history by executing the command from a new shell process, which you then immediately terminate. For example:

bash$ sh
sh$ darkcoind masternode start 'correct horse battery staple'
sh$ exit
bash$

Make sure sh is configured not to save its history in a file.

Of course this doesn't address the other problems, such as the password being visible in ps. There are, I believe, ways for the darkcoind program itself to hide the information from ps, but that only shortens the window of vulnerability.

share|improve this answer
1  
the passphrase is still available via ps and similar utilities. –  voretaq7 May 2 at 21:17
2  
@voretaq7: Yes, as I explicitly acknowleged in the last paragraph of my answer. –  Keith Thompson May 2 at 21:36
2  
Indeed - you were the victim of wanton copypasta on my part:) –  voretaq7 May 2 at 22:12

As others have stated, look into your shell history control for hiding the information from history.

But one thing nobody seems to have suggested yet is to mount /proc with the hidepid parameter. Try modifying your /proc line in /etc/fstab to include hidepid, like this:

# <file system> <mount point>   <type>  <options>       <dump>  <pass>
proc            /proc           proc    defaults,hidepid=2        0       0
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.