过程替换中的敏感数据

las*_*ash 3 security permissions encryption process-substitution

假设我这样做:

#!/bin/bash
#content=$(cat -)
content="foo"
pass=$1
echo $content | ccrypt -f -k <(echo -n $pass)  
Run Code Online (Sandbox Code Playgroud)

是否可以/dev/fd/*信任在进程替换文件上设置的权限以在 期间保持密码安全ccrypt

编辑:从脚本中删除了愚蠢的违规行,无论如何都会显示通行证

Sté*_*las 6

让我们分解一下:

content=$(cat -)
Run Code Online (Sandbox Code Playgroud)

这与content=$(cat). 这是使用命令替换这在bash使用管道。在管道的一端,cat正在写入从其标准输入读取的内容。而bash在另一端读取存储到$content

但是,在此之前,它会去除尾随的换行符,并且还会阻塞 NUL 字符。为了能够在变量中存储任意数据,您不能使用bash; 您需要改为使用zsh并执行以下操作:

content=$(cat; echo .); content=${content%.}
Run Code Online (Sandbox Code Playgroud)

解决换行剥离问题。

请注意,要存储的数据$content是通过脚本的标准输入输入的。这将可用(在 Linux 上),因为/proc/pid-of-your-script/fd/0它与/proc/pid-of-cat/fd/0. 并且由于将其cat复制到管道中bash,因此它也在/proc/pid-of-cat/fd/1和 中/proc/pid-of-script/fd/fd-to-the-other-end-of-the-pipe

在任何情况下,您$content只使用一次的内容,因此执行该中间步骤没有意义。


pass=$1
Run Code Online (Sandbox Code Playgroud)

在这里,您是说非常秘密的密钥取自脚本的第一个参数。

您不能将机密数据作为命令行参数传递。命令行参数不是秘密。它们显示在ps -efwww. 在 Linux 上,/proc/pid/cmdline包含它们是世界可读的(默认情况下;在 Linux 上,/proc/pid可以在管理上限制对具有相同 euid 的进程的访问,尽管很少这样做,因为这会影响行为或ps其他事情)。

在某些系统上,它们甚至可能通过某些进程记帐/审计机制进行记录。


echo $content 错误的原因有几个:

  • echo不能用于任意数据。它不能与诸如-n, -neneneene... 之类的参数一起正常工作,并且取决于包含反斜杠的环境。
  • 未加引号$content意味着调用您不想要的 split+glob 运算符,如果$content包含$IFS或 通配符,则会导致问题。
  • 它添加了一个额外的换行符。

你会想要:

printf %s "$content"
Run Code Online (Sandbox Code Playgroud)

并确保之前没有删除尾随的换行符。

由于这printf是管道的一部分,它将在一个子进程中运行,该子进程的 stdout 将是一个管道。在 Linux 上,/proc/pid-of-that-child-process/fd/1将允许访问该内容。也会/proc/pid-of-ccript/0


ccrypt -f -k <(echo -n $pass)
Run Code Online (Sandbox Code Playgroud)

再有问题echo和不带引号的$pass

echo(再次在子进程中运行)将密码写入管道。管道的另一端将提供对FD ñccrypt<(...)会扩展到类似/dev/fd/n/proc/self/fd/nccrypt将打开那个文件(所以在一个新的 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
Run Code Online (Sandbox Code Playgroud)

并将其称为

PASSWORD=secret-phrase the-script < data-to-encrypt 
Run Code Online (Sandbox Code Playgroud)


PSk*_*cik 5

stracingbash -c 'cat <(echo pass)'显示它<()通过调用pipe2,(在我的情况63)欺骗它到较大fd和传递,作为/dev/fd/SOMEFD代替<()

如果ccrypt不能真正快速地读取密码,理论上它可以被同一用户下运行的第三个进程拦截。

您可以通过例如在脚本语言中模拟序列然后使用独立进程在预期接收者之前读取管道的读取端来验证这一点。