假设我这样做:
#!/bin/bash
content="foo"
pass="secret-password"
echo $content | ccrypt -f -k <(echo -n $pass)
是否可以信任在进程替换文件上设置的权限/dev/fd/*
来保证密码在 的持续时间内安全ccrypt
?
答案1
让我们来分解一下:
content=$(cat -)
这与 相同content=$(cat)
。那是用命令替换其中bash
使用管道。在管道的一端,cat
正在写入从标准输入中读取的内容。bash
另一端正在读取它以存储到$content
.
然而,在此之前,它会删除尾随的换行符,并且还会因 NUL 字符而阻塞。为了能够在变量中存储任意数据,您不能使用bash
;你需要使用zsh
并执行类似的操作:
content=$(cat; echo .); content=${content%.}
解决换行符剥离问题。
请注意,要存储的数据$content
是通过脚本的标准输入输入的。这将是可用的(在 Linux 上),/proc/pid-of-your-script/fd/0
与/proc/pid-of-cat/fd/0
.由于cat
将其复制到管道 to bash
,因此它也在/proc/pid-of-cat/fd/1
和中/proc/pid-of-script/fd/fd-to-the-other-end-of-the-pipe
。
在任何情况下,您都只使用一次内容,$content
因此执行中间步骤是没有意义的。
pass=$1
在这里,你说的是这个秘密钥匙取自脚本的第一个参数。
您不能将秘密数据作为命令行参数传递。命令行参数不是秘密。它们显示在 的输出中ps -efwww
。在 Linux 上,/proc/pid/cmdline
包含它们的文件是世界可读的(默认情况下;在 Linux 上,/proc/pid
可以通过管理方式将访问权限限制为具有相同 euid 的进程,尽管很少这样做,因为这会影响行为或ps
其他事情)。
在某些系统上,它们甚至可以通过某些进程记帐/审计机制来记录。
echo $content
错误的原因有几个:
echo
不能用于任意数据。它不能与像-n
,-neneneene
... 这样的参数正常工作,并且根据环境包含反斜杠的东西。- 不带引号
$content
意味着调用您不想要的 split+glob 运算符,如果$content
包含$IFS
或 通配符,则会导致问题。 - 它添加了一个额外的换行符。
你想要:
printf %s "$content"
并确保尾随换行符没有被提前删除。
由于它printf
是管道的一部分,因此它将在子进程中运行,其标准输出将是管道。在 Linux 上,/proc/pid-of-that-child-process/fd/1
将授予对该内容的访问权限。所以将/proc/pid-of-ccript/0
。
在
ccrypt -f -k <(echo -n $pass)
echo
又出现了和 未引用的问题$pass
。
echo
(再次在子进程中运行)会将密码写入管道。管道的另一端将在 fd 上可用ntoccrypt
和<(...)
will 扩展为类似/dev/fd/n
or 的东西/proc/self/fd/n
。ccrypt
将打开该文件(因此在新的 fd 上),该文件(在 Linux 上)将再次可用,/proc/pid-of-ccrypt/fd/that-fd
除了/proc/pid-of-ccrypt/fd/n
和/proc/pid-of-echo/fd/1
现在,代码中的主要问题不是进程替换或任何其他管道,而是密码作为命令的命令行参数给出(此处为您的脚本)这一事实。
进程替换涉及普通管道,就像$(...)
命令替换和|
./dev/fd/x
在除Linux之外的大多数系统上只对相应的进程有意义,因此不能泄漏到其他进程。但是以相同 euid(或 root)运行的其他进程无论如何都可以读取这些进程的内存(就像调试器所做的那样)并恢复该密码(或者可能从相同的源获取它)。
在 Linux 上,/dev/fd
是 的符号链接/proc/self/fd
和/proc/self
动态符号链接/proc/the-pid
。/proc/pid/fd
默认情况下,具有相同 euid 的进程可以读取(尽管可以添加进一步的限制,与限制谁可以将调试器附加到进程的限制相同)。
对于指向管道的 fd,/proc/pid/fd/that-fd
其作用类似于命名管道。因此另一个进程(再次以相同的 euid 或 root 身份运行)可能会窃取管道的内容。但无论如何,如果他们能做到这一点,他们也可以直接读取进程内存的内容,因此没有必要试图防范这种情况。
您可以通过环境变量传递密码,而不是在命令行上传递密码。环境比参数列表更加私密。在 Linux 上,/proc/pid/environ
只能被具有相同 euid(或root
)的进程读取。
所以你的脚本可能只是:
#! /bin/sh -
exec ccrypt -f -E PASSWORD
并将其称为
PASSWORD=secret-phrase the-script < data-to-encrypt