44

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?

q9f
  • 640
  • 3
  • 10
  • 21
  • 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 02 '14 at 15:32
  • @MadHatter it throws an error. there is no other way to pass the password to the daemon. – q9f May 02 '14 at 15:36
  • 31
    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 02 '14 at 15:55
  • I believe this might have been asked/answered on Unix/Linux. http://unix.stackexchange.com/questions/29111/safe-way-to-pass-password-for-1-programs-in-bash – Colyn1337 May 02 '14 at 16:15
  • 4
    Check your documentation, it may support reading that password from a system environment variable. – Elliott Frisch May 02 '14 at 18:11
  • @ElliottFrisch is it possible to create an environment variable that can only be read by root? – smcg May 02 '14 at 19:35
  • @smcg Yes. But it will only work if the daemon supports it. – Elliott Frisch May 02 '14 at 19:54
  • 1
    `darkcoin` as in the crypto coin? Is this your worker user/pass? If so then there's no downside to somebody seeing that passphrase (as long as it's not a re-used passphrase from anywhere else). In Bitcoin and Dogecoin pools, those passphrases are just to verify your worker. The worst that anybody can do with that passphrase is mine under your name and earn you some shares. I'll gladly tell you my worker name and passphrase! Wait... Just looked into it, it's the daemon to the wallet isn't it? – Corey Ogburn May 02 '14 at 20:04
  • @CoreyOgburn yes, its the wallet and the passphrase is very sensitive as anyone knowing it has the ability to access my funds. its not comparable to worker passwords. – q9f May 02 '14 at 22:41
  • 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 03 '14 at 08:05
  • What about wrapping in a script that reads the password from the commandline and passes it into the program? – Boris the Spider May 03 '14 at 22:26
  • 2
    Look at the answers to [this question](http://unix.stackexchange.com/questions/88665/how-does-ps-know-to-hide-passwords), they deal with exactly this issue. – dotancohen May 04 '14 at 16:05

9 Answers9

76

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.

MvG
  • 1,813
  • 15
  • 18
  • 4
    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 03 '14 at 22:40
  • This is indeed awesome. – q9f May 04 '14 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 04 '14 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](http://stackoverflow.com/questions/6856635/hide-password-input-on-terminal). – Grubermensch May 04 '14 at 17:32
  • @Grubermensch You are right. – Tonny May 05 '14 at 21:32
  • After some search I've found the [source code of the Darkcoin client](https://github.com/darkcoinproject/darkcoin) as it moved from where the commenter originally found. Someone should fix this and do a pull request. In my opinion, however, after looking at the project description and the white paper it doesn't seem very convincing security-wise. I wouldn't trust my money in this without an external security audit. – josh May 12 '14 at 01:28
  • Could someone explain this for newbies too? I can't understand a thing. I'm trying to get a password in a Qt application without running into this security issue. – Iulian Onofrei Sep 19 '16 at 14:52
  • @IulianOnofrei: If you literally can't understand a thing, I suggest you start by learning English. Seriously, though, since I assume you can understand English and therefore make sense of most of the sentences on a language level, you have to be more specific as to what higher level concepts you are having problems with. Writing code? Compiling code? Basic C programming? Dynamic linking? `LD_PRELOAD`? Interposers? Function pointers? On the whole, this might be better of as a separate question, with links going both ways. And probably not on Server Fault, more likely Stack Exchange I guess. – MvG Sep 19 '16 at 15:48
  • I don't know C that well. I don't know about dynamic linking that well. I don't know what `LD_PRELOAD` is. I don't know what interposers are. I don't know what function pointers are. I'll make some more research until opening a separate question. Thanks for the help. – Iulian Onofrei Sep 19 '16 at 16:16
  • Could you post, please, how to compile it with g++? I find some problems due to g++ mangled names to let overriding and so. I have some code compiled with g++ that I'd like to link with this function to do some stuff. – Alejandro Galera Apr 16 '19 at 10:56
  • 1
    @mulg0r: Standard [extern "C"](https://en.cppreference.com/w/cpp/language/language_linkage) should do the trick of suppressing the name mangling for the relevant function, namely `__libc_start_main`. – MvG Apr 16 '19 at 13:06
  • Pretty cool stuff, unfortunately I haven't found a way around an error like "node: ../deps/uv/src/unix/proctitle.c:65: uv_setup_args: Assertion `process_title.len + 1 == size' failed." that I guess comes from the process name changing – polpetti Feb 25 '20 at 18:47
  • 1
    @polpetti: If you read https://github.com/nodejs/node/blob/1f0f58cb81503215df69dbaffc53e286e661e1c3/deps/uv/src/unix/proctitle.c#L65 you can see that this assertion checks that memory is adjacent. So instead of replacing a single argument, you would have to allocate a block of memory for all the arguments you want to pass to main, then place the arguments into that one after the other. On the other hand I'm at a loss why you would want to use this hack for node. As I wrote, if you have the source code, fixing there is better by far. So edit the JavaScript or patch node. – MvG Feb 27 '20 at 01:50
  • I'm a bit late to the party but... I tried looking up the concept of *interposers*, but it doesn't seem to be a standard software name. This piece of code is cool AF, so I'd like to learn more. What is the word I ought to be looking up? – André Chalella Aug 06 '20 at 19:32
  • @AndréChalella: I imagine using `LD_PRELOAD` as a search term should turn up many useful resources. Adding `__libc_start_main` should allow you to focus on how to tweak `main` using that approach. – MvG Aug 07 '20 at 08:44
  • @MvG I did that, the problem was, I needed something more general, because I'm on Cygwin right now, which has `LD_PRELOAD` but, I found later, not `RTLD_NEXT` or `__libc_start_main`. Eventually I was able to find this: https://pdfs.semanticscholar.org/7b76/fdeea9513cc3c9d3f13035ec31b5ed04456b.pdf. In short, for Windows, seems like Microsoft Detours is the way to go. I couldn't find anything more suited to Cygwin. – André Chalella Aug 08 '20 at 10:30
28

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.

MadHatter
  • 79,770
  • 20
  • 184
  • 232
Tonny
  • 6,332
  • 1
  • 18
  • 31
  • 9
    Thanks for not being helpful at all. This is a long discussed [security issue](https://github.com/bitcoin/bitcoin/issues/2318), still unsolved and I need a better workaround than `rm -f` now. – q9f May 02 '14 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 02 '14 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 02 '14 at 15:51
  • 1
    @Safado That's why I ask how to to pass it as an argument *rollseyes*. – q9f May 02 '14 at 15:55
  • @Tonny have you ever tried [read x](http://bitcoin.stackexchange.com/a/2858/6441)? – q9f May 02 '14 at 15:55
  • 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 02 '14 at 15:57
  • 2
    @Tonny - it's not the Operating System; there are ways to solve this in every OS I know of. It's the particular daemon OP is running – mpez0 May 02 '14 at 15:57
  • @mpez0 It *is* the operating system - I know of no way to accomplish what is being asked for (in any meaningful way) on a Unix (POSIX) system if the password is given as a command-line argument. Solving the history part is easy (you could even just delete your shell history) but the full command line (and its arguments) would still be available with very little effort using `ps` or the `/proc` filesystem. If you really know a way to fix that, please enlighten me. – voretaq7 May 02 '14 at 21:26
  • 3
    @voretaq7: and that's the point. The daemon requires that the password be passed as a command line argument, when every sane unix daemon handles this by using a key file. The daemon is insanely written. – nomen May 02 '14 at 22:36
  • 2
    @voretaq7 , mpez0 It's not just unix-like systems. Windows too. In fact I know of no OS that doesn't expose the command-line parameters of any running process in some way. – Tonny May 03 '14 at 10:43
  • 7
    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](http://serverfault.com/a/592941/7709) – Mark Henderson May 03 '14 at 22:45
  • 1
    @tonny All Unix-like systems, and Windows, too, and all other OSes that I know of have ways to pass information to running processes besides the command line. It isn't the OS, its the way that daemon was written – mpez0 May 04 '14 at 19:32
  • @Mark Does that work? Yeah. Is writing a `LD_PRELOAD` module for someone else's poorly written junk *professional*, and the type of advice we should be giving on here? Debatable. It's really more of a StackOverflow problem at that point, because the best approaches are all written in C and that's not really our audience. – Andrew B May 05 '14 at 13:54
  • The question's __Update 3__ says that `ps` is not showing the arguments, but this answer says that it does. Who is right and who is correct?! – Iulian Onofrei Sep 19 '16 at 14:45
  • @IulianOnofrei: `ps` shows the arguments unless and until the application changes them in memory. So it's a race condition. If the Daemon has been running long enough you probably won't see it, but during startup there is a time window (duration unknown) where the arguments would be visible. – MvG Feb 27 '20 at 02:01
20

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.

Matthew Ife
  • 23,357
  • 3
  • 55
  • 72
  • 18
    . . . this is both creative and frightening. – voretaq7 May 02 '14 at 22:06
  • EEK! Now I'm scared. – Janne Pikkarainen May 03 '14 at 08: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 03 '14 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 03 '14 at 13:17
11

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.

vn.
  • 375
  • 2
  • 10
  • There was a shell option which enables it, but I think it wasn't enabled by default. – heinrich5991 May 02 '14 at 19:24
  • 1
    `export HISTCONTROL=ignoreboth` ignores both duplicates and lines with a leading space for entry into the history. Add it to your .bashrc or .bash_profile. – Andreas Feb 07 '17 at 20:40
7

Maybe this works (?):

darkcoind masternode start `cat password.txt`
Daniele Testa
  • 661
  • 4
  • 10
  • 18
  • 4
    Or even `darkcoind masternode start \`head -1\``, if you want to enter the password manually. – kasperd May 02 '14 at 19:58
  • 16
    The passphrase is still available via `ps` and similar utilities. – voretaq7 May 02 '14 at 21:17
  • This would change *nothing*. It's the shell's job to do the variable substitution, fork and exec the child with the right arguments. The exact same thing happens in both cases. – MikeyB May 02 '14 at 22:12
  • It does solve the part of the requirement, which is to not store the password in `.bash_history`. Additionally with the command already stored in the history, it is simple to use it from there each time, such that you don't accidentally do it wrong, the next time you need to run it. – kasperd May 03 '14 at 07:58
  • 1
    Moving from a plaintext password in `.bash_history` to a plaintext password in `password.txt` gains you what, exactly? – MikeyB May 03 '14 at 14:24
  • 2
    @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 03 '14 at 20:49
  • 1
    @MikeyB, you may create and remove that file each time. – RiaD May 04 '14 at 08:11
  • Thanks, simple and nice. Command history shows `cat password.txt` (not the actually password. – vikingsteve Apr 26 '16 at 08:53
5

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.

200_success
  • 4,771
  • 1
  • 25
  • 42
  • 2
    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 05 '14 at 17:48
3

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
ptman
  • 28,394
  • 2
  • 30
  • 45
3

For Bitcoin, the official developer answer is to use the provided python wrapper in contrib/bitrpc/bitrpc.py (github):

It asks for a password in a secure way if you use the command walletpassphrase, for example. There are no plans to add interactive functionality to bitcoin-cli.

and:

bitcoin-cli will remain as-is and not gain interactive functionality.

Source: #2318

Unlock wallet:

$ python bitrpc.py walletpassphrase

Change passphrase:

$ python bitrpc.py walletpassphrasechange

https://github.com/bitcoin/bitcoin/tree/master/contrib/bitrpc

For darkcoin it works anlogue:

https://github.com/darkcoin/darkcoin/tree/master/contrib/bitrpc

q9f
  • 640
  • 3
  • 10
  • 21
2

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.

Keith Thompson
  • 520
  • 2
  • 9