如何隐藏作为命令行参数传递的密码?

Afr*_*Afr 43 security linux password command-line-interface

我正在运行一个软件守护程序,它需要某些操作来输入密码来解锁某些功能,例如:

$ darkcoind masternode start <mypassphrase>
Run Code Online (Sandbox Code Playgroud)

现在我在我的无头 debian 服务器上遇到了一些安全问题。

例如,每当我搜索我的 bash 历史记录时,Ctrl+R我都可以看到这个超强密码。现在我想我的服务器被入侵了,一些入侵者可以访问 shell,并且可以简单地Ctrl+R在历史记录中找到我的密码。

有没有办法输入密码而不显示在 bash 历史记录ps/proc或其他任何地方?


更新 1:不向守护程序传递密码会引发错误。这是没有办法的。


更新 2:不要告诉我删除软件或其他有用的提示,例如挂掉开发人员。我知道这不是最佳实践示例,但该软件基于比特币,并且所有基于比特币的客户端都是某种 json rpc 服务器,它侦听这些命令,并且它的已知安全问题仍在讨论中(abc) .


更新 3:守护进程已经启动并使用命令运行

$ darkcoind -daemon
Run Code Online (Sandbox Code Playgroud)

ps只显示启动命令。

$ 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
Run Code Online (Sandbox Code Playgroud)

因此,使用密码短语传递命令不会显示ps或根本不会显示/proc

$ 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
Run Code Online (Sandbox Code Playgroud)

这留下了历史出现在哪里的问题?只有在.bash_history

MvG*_*MvG 73

真的,这应该在应用程序本身中修复。此类应用程序应该是开源的,因此可以选择解决应用程序本身的问题。犯这种错误的安全相关应用程序也可能犯其他错误,所以我不会相信它。

简单的中介层

但是你要求的是一种不同的方式,所以这里有一个:

#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);
}
Run Code Online (Sandbox Code Playgroud)

编译这个

gcc -O2 -fPIC -shared -o injectpassword.so injectpassword.c -ldl
Run Code Online (Sandbox Code Playgroud)

然后运行你的过程

LD_PRELOAD=$PWD/injectpassword.so darkcoind masternode start fakepasshrase
Run Code Online (Sandbox Code Playgroud)

中介层库将在main您的应用程序中的函数被执行之前运行此代码。它将用调用 main 中的实际密码替换最后一个命令行参数。但是,打印出来的命令行/proc/*/cmdline(因此可以被诸如 之类的工具看到ps)仍将包含假参数。显然,您必须使源代码和从中编译的库仅对您自己可读,因此最好在chmod 0700目录中进行操作。而且由于密码不是命令调用的一部分,因此您的 bash 历史记录也是安全的。

更高级的中介层

如果你想做任何更复杂的事情,你应该记住它__libc_start_main在运行时库被正确初始化之前被执行。所以我建议避免任何函数调用,除非它们是绝对必要的。如果您希望能够随心所欲地调用函数,请确保main在所有初始化完成之后调用它之前调用函数。对于以下示例,我必须感谢 Grubermensch,他指出如何隐藏作为命令行参数传递的密码,这引起getpass了我的注意。

#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);
}
Run Code Online (Sandbox Code Playgroud)

这会提示您输入密码,因此您不再需要对内插器库保密。占位符参数被重用为密码提示,所以像这样调用

LD_PRELOAD=$PWD/injectpassword.so darkcoind masternode start "Password: "
Run Code Online (Sandbox Code Playgroud)

另一种选择是从文件描述符(例如gpg --passphrase-fddo)或 fromx11-ssh-askpass或其他任何地方读取密码。

  • 虽然我不明白也无法测试代码,但我明白了它的要点,*this* 看起来像一个实际的答案,应该是最佳答案。 (4认同)

Ton*_*nny 28

这不仅仅是历史。它也会出现在ps输出中。

编写该软件的人应该被悬挂、绘制和分割。无论是什么软件,都绝对不能在命令行上提供密码。
对于守护进程,它甚至更不可原谅......

除了软件本身的rm -f之外,我不知道任何解决方案。老实说:寻找其他软件来完成工作。不要用这种垃圾。

  • 事实上,他非常乐于助人。如果您将密码作为参数传递,它将显示在 `ps` 中。所以在开发人员可以解决这个问题之前,他建议使用其他东西。 (17认同)
  • 感谢您根本没有帮助。这是一个长期讨论的 [安全问题](https://github.com/bitcoin/bitcoin/issues/2318),仍未解决,我现在需要比 `rm -f` 更好的解决方法。 (9认同)
  • vertoe,不要吝啬。你可以要求一种在小纸条上传递它的方法,但这并不意味着任何这样的方法自动存在。read_x 很好,但仍然通过例如`ps` 暴露密码,所以它并不比`rm` 解决方案好。 (8认同)
  • 在你们对这个不是真的答案再投一个 +1 并抱怨这是不可能的之前,我建议你回顾一下 [MvG 在下面的回答](http://serverfault.com/a/592941/7709) (7认同)
  • 然后你最好开始编写另一个操作系统。我知道目前没有其他可用的解决方案。天哪,我希望有一个。您不是唯一遇到此问题的人。 (3认同)
  • @voretaq7:这就是重点。守护程序要求将密码作为命令行参数传递,当每个健全的 unix 守护程序通过使用密钥文件来处理它时。守护进程写得很疯狂。 (3认同)
  • @Tonny - 这不是操作系统;在我知道的每个操作系统中都有解决这个问题的方法。这是特定的守护进程 OP 正在运行 (2认同)
  • @voretaq7 , mpez0 这不仅仅是类 Unix 系统。窗户也是。事实上,我知道没有任何操作系统不会以某种方式公开任何正在运行的进程的命令行参数。 (2认同)

Mat*_*Ife 20

这将清除ps输出。

非常注意:这可能会破坏应用程序。你被适当地警告,这里有龙。

  • 外部进程不应该在进程内存中摆弄。
  • 如果该过程依赖此区域作为密码,您可能会破坏您的应用程序。
  • 这样做可能会破坏您在该过程中拥有的任何工作数据。
  • 这是一个疯狂的黑客。

现在,您会收到有关这些可怕警告的正式通知。这将清除 中显示的输出ps。它不会清除您的历史记录,也不会清除 bash 作业历史记录(例如运行类似 的进程myprocess myargs &)。但ps将不再显示参数。

#!/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)
Run Code Online (Sandbox Code Playgroud)

通过保存它来调用程序chmod +x。然后做./whatever <pidoftarget> 如果这有效,它将不会产生任何输出。如果失败,它会抱怨某些事情并退出。

  • . . . 这既富有创意又令人恐惧。 (18认同)

vn.*_*vn. 11

您可以从文件传递参数,只能由 root 或所需用户访问吗?

在控制台中输入密码是一个巨大的禁忌,但最后的求助...以空格开头,这样它就不会出现在历史记录中。


Dan*_*sta 7

也许这有效(?):

darkcoind masternode start `cat password.txt`
Run Code Online (Sandbox Code Playgroud)

  • 密码短语仍然可以通过 `ps` 和类似的实用程序获得。 (14认同)
  • 甚至`darkcoind masternode start \`head -1\``,如果你想手动输入密码。 (3认同)
  • @MikeyB:有一个小小的胜利:当有人在你背后监视时,你不会在搜索你的历史记录时意外地暴露它。 (2认同)

200*_*ess 5

不幸的是,如果您的darkcoind命令需要密码作为命令行参数,那么它将通过诸如ps. 唯一真正的解决办法是教育开发人员

\n\n

虽然ps暴露可能是不可避免的,但您至少可以防止密码被写入 shell 历史文件中。

\n\n
\n

$ xargs darkcoind masternode start

\n\n

password\xe2\x8f\x8e

\n\n

CtrlD

\n
\n\n

历史文件应该只记录xargs darkcoind masternode start,而不是密码。

\n

  • 或者,如果您使用的是 bash,请将“ignorespace”放入“$HISTCONTROL”中,然后您可以通过在命令前添加空格来防止*任何*命令进入 shell 历史记录。 (2认同)