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


43

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

$ darkcoind masternode start <mypassphrase>

现在,我的无头Debian服务器上出现了一些安全问题。

每当我使用例如搜索bash历史记录时,Ctrl+R都可以看到此超级强密码。现在,我想象我的服务器已损坏,并且某些入侵者具有外壳访问权限,并且可以简单地Ctrl+R在历史记录中找到我的密码短语。

有没有一种方法来输入密码,没有它在bash的历史显示,ps/proc或其他地方?


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


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


更新3:守护程序已经启动并通过以下命令运行

$ darkcoind -daemon

“执行” 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

因此,使用密码短语传递命令不会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

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


1
第一个问题必须是:如果在没有密码短语参数的情况下启动守护程序,会发生什么情况。它只是提示吗?
MadHatter 2014年

31
我认为没有答案会起作用。无法提示输入密码是守护程序的主要缺点。如果是免费软件,请聘请程序员并进行修复;不要忘记发布您的更改。如果是专有软件,请与供应商联系并大声疾呼(那不会解决任何问题,但这会让您感觉更好)。
MadHatter 2014年

4
检查您的文档,它可能支持从系统环境变量读取该密码。
艾略特·弗里施

3
即使未在命令行中将密码提供给守护程序,在其他任何命令的命令行中提供密码仍然存在问题。它仅在很短的时间内在ps输出中可见,但是在后台运行的进程仍然可以将其拾取。但是,当然仍然值得增加密码的获取难度。
卡巴斯德(Kasperd),2014年

2
这个问题的答案,他们正好处理这个问题。
dotancohen

Answers:


68

确实,这应该在应用程序本身中解决。而且此类应用程序应该是开源的,因此应该选择在应用程序本身中解决问题。发生这种错误的与安全相关的应用程序也可能会犯其他错误,因此我不信任它。

简单插入器

但是您要求的是另一种方式,所以这是一种:

#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);
}

用编译

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

然后使用

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

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);
}

这将提示您输入密码,因此您不必再将插入器库保密。占位符参数被重用作为密码提示,因此调用它就像

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

另一种选择是从文件描述符(例如,gpg --passphrase-fd确实如此)或从中读取密码x11-ssh-askpass


4
尽管我不理解并且无法测试代码,但是我了解了要点,看起来像是一个实际的答案,应该是最重要的答案。
马克·亨德森

这确实很棒。
Waqar Lim 2014年

太棒了 据我所知,这应该行得通。当然,您需要访问源并能够重新编译。如果您使用“字符串”或类似的东西,则密码在源代码和编译文件中都是可读的,因此最好确保没有其他人可以读取这些密码。
托尼2014年

1
应该可以在STDIN上获取密码,并且仍然可以进行这项工作,从而消除了该strings漏洞。参见SO:隐藏终端上的密码输入
Grubermensch 2014年

1
@ mulg0r:标准extern“ C”应该起到抑制相关功能(即)名称处理的技巧__libc_start_main
MvG

28

不仅仅是历史。它也将在ps输出中显示。

编写该软件的人都应该被挂起,绘制和固定。无论使用哪种软件,都绝对不必在命令行上提供密码。
对于守护进程,它甚至更不可原谅...

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


9
感谢您根本没有帮助。这是一个长期讨论的安全性问题,仍未解决,我需要比rm -f现在更好的解决方法。
Waqar Lim 2014年

17
实际上,他很有帮助。如果您将密码短语作为参数传递,它将显示在中ps。因此,在开发人员解决此问题之前,他建议使用其他方法。
萨法多2014年

3
然后,您最好开始编写另一个操作系统。我知道目前没有其他可用的解决方案。上帝希望我有一个。您不是唯一遇到此问题的人。
托尼2014年

8
vertoe,别小气。您可以要求一种在小纸条上通过它的方法,但这并不意味着任何这种方法都会自动存在。read_x很好,但是仍然通过eg公开密码短语ps,因此它并不比rm解决方案更好。
MadHatter 2014年

7
在大家对这个并非真的答案投掷+1并抱怨这是不可能的之前,我建议您在下面
Mark Henderson

19

这将清除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)

通过保存程序来调用程序chmod +x。然后执行./whatever <pidoftarget> 此操作,将不会产生任何输出。如果失败,它将抱怨并退出。


18
。。。这既有创造力又令人恐惧。
voretaq7

哎呀!我现在很害怕。
Janne Pikkarainen 2014年

Yikkes,那可能行得通...我不确定像AppArmor这样的东西能抓住这个吗?而且,病毒扫描程序可能会通过阻止有问题的帐户(“根”)来捕获并造成破坏。有怪物的确....
东铭

@Tonny对于受保护的域,SELinux将阻止这种情况。您的基本Unix权限(DAC)缺乏足够的主题粒度,无法对此行为提供任何保护(允许在同一UID内修改进程内存)。无论如何,这不是错误,而是功能。我相信这是gdb可以修改正在运行的进程的内存的方法(比我可能要增加的手术精度要高得多)。
马修·伊夫,2014年

11

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

在控制台中键入密码是一个很大的禁忌,但是最后一个办法...在您的行前加一个空格,这样它就不会出现在历史记录中。


有一个shell选项可以启用它,但我认为默认情况下未启用它。
heinrich5991 2014年

export HISTCONTROL=ignoreboth忽略重复项和行,两者之间都没有多余的行,用于输入历史记录。将其添加到您的.bashrc或.bash_profile。
Andreas

7

也许这可行(?):

darkcoind masternode start `cat password.txt`

3
甚至darkcoind masternode start `head -1`,如果您想手动输入密码。
卡巴斯德(Kasperd)

14
密码短语仍可通过ps和类似的实用程序获得。
voretaq7

1
从纯文本密码进入.bash_history到纯文本密码,password.txt您可以获得什么呢?
MikeyB 2014年

1
@MikeyB:有一个小小的胜利:当有人看着你的肩膀翻阅历史时,你不会偶然暴露它。
MvG 2014年

1
@MikeyB,您可以每次创建和删除该文件。
RiaD

4

不幸的是,如果您的darkcoind命令希望密码作为命令行参数,那么它将通过诸如的实用程序公开ps。唯一真正的解决方案是教育开发人员

尽管ps可能无法避免这种情况,但是您至少可以防止密码在外壳历史记录文件中被写出。

$ xargs darkcoind masternode start

password

CtrlD

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


2
或者,如果你正在使用bash,把ignorespace$HISTCONTROL,然后就可以防止任何命令由用空格前缀命令进入shell的历史记录。
德罗伯特2014年

3

正如其他人所述,请查看您的Shell历史记录控件以隐藏历史记录中的信息。

但是似乎没有人建议的一件事是/proc使用该hidepid参数安装。尝试将您的/proc行 修改/etc/fstab为包括hidepid,如下所示:

# <file system> <mount point>   <type>  <options>       <dump>  <pass>
proc            /proc           proc    defaults,hidepid=2        0       0

2

您可以通过执行新的Shell进程中的命令将密码保留在Shell历史之外,然后立即终止。例如:

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

确保sh配置为将其历史记录保存在文件中。

当然,这不能解决其他问题,例如密码在中可见ps。我相信,darkcoind程序本身可以通过多种方式隐藏信息ps,但这只会缩短漏洞窗口。


1
密码短语仍然可以通过ps类似的实用程序获得。
voretaq7

3
@ voretaq7:是的,正如我在回答的最后一段中明确承认的那样。
基思·汤普森

3
确实-您是我肆意抄袭的受害者:)
voretaq7

2

对于比特币,开发人员的官方答案是在contrib/bitrpc/bitrpc.pygithub)中使用提供的python包装器:

walletpassphrase例如,如果您使用命令,它将以安全的方式要求输入密码。没有计划向中添加交互式功能bitcoin-cli

和:

bitcoin-cli 将保持原样,并且不会获得交互功能。

资料来源:#2318

解锁钱包:

$ python bitrpc.py walletpassphrase

更改密码:

$ python bitrpc.py walletpassphrasechange

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

对于Darkcoin,它适用于以下情况:

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.