【问题标题】:Hiding secret from command line parameter on Unix在 Unix 上隐藏命令行参数的秘密
【发布时间】:2010-09-30 13:13:25
【问题描述】:

我有一个脚本,它在自身内部启动一个带有秘密参数的命令。例如:

#!/bin/bash
command-name secret

在运行命令时,我可以阅读 ps -ef | grep command-name 这是秘密。

有没有什么办法可以通过ps -ef,混淆命令行参数?

【问题讨论】:

  • 该命令是否通过输入文件或流接受秘密参数?
  • 请注意,脚本本身是可读的,因此任何有兴趣找到它的人都可以看到这个秘密。为了让用户能够运行 shell 脚本,该脚本必须是可读的——或者您必须使用 SUID 程序来运行该脚本的受保护副本,或者其他类似的变形。
  • 这是一个很好的答案:使用 Scott James Remnant netsplit.com/hiding-arguments-from-ps 的技巧修改 /proc/PID/cmdline

标签: bash unix


【解决方案1】:
  1. 首先,您不能隐藏命令行参数。在启动程序时(在程序有机会对参数进行运行时更改之前),其他用户仍然可以通过ps auxcat /proc/$YOUR_PROCESS_PID/cmdline 看到它们。好消息是,您仍然可以通过使用替代方案来获得秘密:

  2. 使用环境变量 (with caveats)。如果您的程序可以读取它们,请执行以下操作:

     mySecret='hello-neo' myCommand
    
  3. 使用标准输入:

     mySecret='hello-neo' printenv mySecret | myCommand
    
  4. 如果您想保持秘密与主脚本分离,请使用专用文件(请注意,建议您使用全盘加密并确保文件具有正确的chmod 权限):

     cat /my/secret | myCommand
    
  5. 使用临时文件描述符:

     myCommand <( mySecret='hello-neo' printenv mySecret )
    

在最后一种情况下,您的程序将像myCommand /dev/fd/67 一样启动,其中/dev/fd/67 的内容是您的秘密(本例中为hello-neo)。


在上述所有方法中,请注意不要将命令留在 bash 命令历史记录中 (~/.bash_history)。您可以通过从脚本(文件)运行命令或每次以交互方式提示自己输入密码来避免这种情况:

    read -s mySecret && export mySecret
    myCommand  # approach 2
    printenv mySecret | myCommand  # approach 3
    myCommand <( printenv mySecret )  # approach 4

【讨论】:

  • 是的,你可以,我已经看到程序一直用'*'掩盖它们的命令行参数。在ps -ef的输出中是这样的:/xware/lib/EmbedThunderManager ******************************************
  • 但是这些参数很可能是从 /proc 的某个地方挖出来的。在您的情况下,听起来 ps 可能只是截断它,因为他们在一长串 * 之后输入了“秘密”。
  • @VasyaNovikov 我已经验证喵是正确的。 mysql -u root -p mypasswordtopps -ef 中都显示为mysql -u root -px xxxxxxxxxx。这与滚动或截断无关。
  • Env vars 可能比 args 更好,但并没有那么好。 cat /proc/1234/environ | tr '\0\033' '\n~' 将显示环境变量,只要您有权限(root 或所有者)。一旦进程运行,隐藏 env vars (setenv) 可能更容易,但在进程能够隐藏数据之前,攻击者仍有一段时间读取文件。嗅探进程不一定有太少的权限来读取敏感进程的环境。
  • @Mark 感谢您对同一用户权限的评论。事实上, env 并不能保护您免受您自己的用户的侵害。但它确实可以防止其他所有人。这对我来说是一个巨大的差异。
【解决方案2】:

如果秘密在执行之间没有改变,请使用一个特殊的配置文件,".appsecrets"。将文件的权限设置为所有者只读。在文件中为密钥设置一个环境变量。该文件需要位于运行该命令的用户的主目录中。

#!/bin/bash  
#filename: .appsecrets
set SECRET=polkalover  

加载配置文件以便设置环境变量。

. ~/.appsecrets

我所看到的:

1)
echo $SECRET | command

如果命令提示输入来自 stdin 的密码并且如果 'echo' 是你的 shell 的内建函数,则可以工作。我们使用的是 Korn。

2)
password=$ENV{"SECRET"};

如果您可以控制代码(例如在 perl 或 C++ 中),则可以使用

3)
. ./.app.config #sets the environment variables
isql -host [host] -user [user] -password <<SECRET
${SQLPASSWORD}
SECRET

如果命令可以接受来自 std-in 的秘密,则有效。一个限制是&lt;&lt;string 必须是给命令的最后一个参数。如果在 -password 之后必须出现非可选参数,这可能会很麻烦

这种方法的好处是您可以安排它,以便在生产中隐藏秘密。在生产中使用相同的文件名,但它将位于在生产中运行命令的帐户的主目录中。然后,您可以像访问 root 帐户一样锁定对机密的访问。只有某些人可以“su”到 prod 帐户以查看或维护机密,而开发人员仍然可以运行该程序,因为他们在其主目录中使用自己的“.appsecret”文件。

您可以使用这种方法为任意数量的应用程序存储安全信息,只要它们使用不同的环境变量名称作为其机密。

(错误的方式)
我看到 DBA 使用的一种旧方法是将 SYBASE 设置为 "/opt/././././././././././././././././././././././././././././././././././sybase/bin"。所以他们的命令行太长了 ps 截断了它。但在 linux 中,我认为您可能能够从 /proc 中嗅出完整的命令行。

【讨论】:

  • '命令行将只显示给定的环境变量,而不是它的值'——这些 cmets 错误有两个原因。首先,打点 ~/.appsecrets 文件已将 shell 的命令行参数重置为只有一个,$1 的值现在是 'SECRET=polkalover';没有变量,更不用说环境变量,称为 SECRET。其次,如果你创建了一个变量 SECRET(不难),运行 'command $SECRET' 会在执行命令之前扩展 $SECRET,ps 会显示信息的扩展版本。
  • @jonathan - 你是对的。我主要考虑的是 C++ 方法。我已经更新了我的答案以显示脚本方法。
  • "... 所以他们的命令行太长了,ps 截断了它"------ 使用ps ww 它可以显示完整的命令行跨度>
【解决方案3】:

ps 隐藏您的秘密论点的唯一方法是不提供秘密作为论点。这样做的一种方法是将秘密放在一个文件中,并重定向文件描述符 3 以读取该文件,然后删除该文件:

echo secret > x.$$
command 3<x.$$
rm -f x.$$

尚不完全清楚这是一种保存秘密的安全方法; echo 命令是一个内置的 shell,所以它不应该出现在 'ps' 输出中(并且任何外观都会稍纵即逝)。很久以前,echo 不是内置的——事实上,在 MacOS X 上,仍然有一个 /bin/echo,尽管它是所有 shell 的内置。

当然,这假设您拥有command 的源并且可以修改它以从预先打开的文件描述符而不是从命令行参数中读取秘密。如果你不能修改命令,你就完全卡住了——“ps”列表会显示信息。

如果您是命令所有者,您可以使用另一个技巧:您可以捕获参数(秘密),为自己将其写入管道或文件(立即取消链接),然后在没有秘密论据;第二次调用知道,由于没有秘密,它应该查看第一次调用隐藏秘密的地方。第二次调用(减去秘密)是在处理隐藏秘密所需的微小间隔之后出现在“ps”输出中的内容。不如从一开始就设置秘密通道。但这些都表明你必须走的路。

从程序内部删除参数 - 例如用零覆盖 - 不会隐藏“ps”中的参数。

【讨论】:

  • 我会在回显之前添加一个umask 077 以确保只有用户可以读取文件。
  • 如果您想要超级安全,只需删除文件并不会删除其内容。将密码保存在普通文件中是不安全的。
  • @JorgeFuentesGonzález:如果是日志文件系统,覆盖文件也不会删除旧内容。
  • 您始终可以使用wipe等实用程序安全地擦除文件
  • 我得到了这个解决方案(如果我理解正确的话):echo foo &gt; bar; exec 3&lt;bar; read line &lt;&amp;3; echo $line。输出:foo。因为type read 显示read is a shell builtin ps 应该看不到它。
【解决方案4】:

我在另一个帖子上看到了。这是Linux下最简单的方法。

这会修改所有其他程序看到的命令行的内存部分。

strncpy(argv[1], "randomtrash", strlen(argv[1]));

您也可以更改进程的名称,但只能从命令行读取。像top 这样的程序会显示真实的进程名:

strncpy(argv[0], "New process name", strlen(argv[0]));

不要忘记复制最大 strlen(argv[0]) 字节,因为可能没有更多空间分配。

我认为参数只能在我们修改的内存部分中找到,所以我认为这就像一个魅力。如果有人对此有所了解,请发表评论。

VasyaNovikov 注意:在程序调用后但在开始执行您描述的更改之前,仍然可以截取密码。

【讨论】:

  • 请注意,在程序调用之后但在开始执行您描述的更改之前,仍然可以截取密码。这包括启动时间,所​​以它不为零。
  • @VasyaNovikov 不错的提示。准备添加它。
【解决方案5】:

expect 库是为此类事情部分创建的,因此您仍然可以向进程提供密码/其他敏感信息,而无需将其作为参数传递。假设当没有给出“秘密”时,程序当然会要求它。

【讨论】:

  • 我经常使用 expect 来将密码保存在命令行之外。可用于登录远程系统、响应密码提示等...
【解决方案6】:

没有简单的方法。看看我刚才问的这个问题:

Hide arguments from ps

command是你自己的程序吗?您可以尝试对密钥进行加密,并在使用前让command 对其进行解密。

【讨论】:

  • 在您的情况下,通过配置 ad-hoc conf 以默认读取私钥来解决问题。在这种情况下,问题就不同了。
  • @Kerby82:不——情况是一样的;无法从“ps”中隐藏命令行选项。
  • 这是一个不同的问题。在 your 问题中,您想保护自己免受其他具有相同权利的人(unix 用户)的侵害。在 this 问题中,询问如何对 other unix 用户隐藏秘密。
【解决方案7】:

您可以使用LD_PRELOAD 让一个库在该二进制文件本身的进程中操作该二进制文件的命令行参数,而ps 不会接收它。有关详细信息,请参阅服务器故障上的 this answer of mine

【讨论】:

    【解决方案8】:

    如果脚本打算手动运行,最好的方法是从 STDIN 读取它

    #!/bin/bash
    read -s -p "Enter your secret: " secret
    
    command "$secret"
    

    【讨论】:

    • Bash 2.04 及更高版本具有read -s,它不会回显输入字符,因此您只需将 stty 与没有它的 shell 一起使用(zsh 也有它)。
    • @Dennis Williamson:很好,我不知道!谢谢
    • 这样做的问题是秘密暴露在命令行上,并将显示在ps 输出中 - 但问题是如何避免这种暴露。
    • @Johnathan:啊哈,我明白你的意思了。如果您将此与下面的答案结合起来,那么它就是一种方法。
    【解决方案9】:

    也许你可以这样做:

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

    【讨论】:

    • 一些解释会很棒。
    【解决方案10】:

    这是一种对 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
    


    在 ps 输出中,您将看到如下内容:

    $ps -ef | grep your_awesome_command
    root     23134     1  0 20:55 pts/1    00:00:00  . x.$$ && your_awesome_command
    

    Elastalert 和 Logstash 是可以通过环境变量访问密码的服务示例。

    【讨论】:

    • 为什么要写入文件?为什么不只是 ES_PASSWORD=$secret your_awesome_command ?如果您不希望将秘密写入文件,这似乎是一个非常奇怪的想法。电脑死机了怎么办?清理挂钩不会运行。为什么要增加复杂性?
    【解决方案11】:

    我总是将敏感数据存储在我不放入 git 的文件中,并使用如下秘密:

    $(cat path/to/secret)
    

    【讨论】:

    • 更好地使用$/gpg -qd path/to/secret)
    猜你喜欢
    • 2016-08-04
    • 2013-03-30
    • 1970-01-01
    • 1970-01-01
    • 2011-09-27
    • 2019-09-10
    • 1970-01-01
    • 2011-08-28
    • 2021-03-15
    相关资源
    最近更新 更多