不给 printf 使用格式会带来安全后果吗?

15 shell security printf

格式良好的printf通常有一个可以使用的格式:

$ var="Hello"
$ printf '%s\n' "$var"
Hello
Run Code Online (Sandbox Code Playgroud)

但是,不提供格式可能会产生什么安全隐患?

$ printf "$var"
Hello
Run Code Online (Sandbox Code Playgroud)

由于变量扩展被引用,应该不会有任何问题,不是吗?

Sté*_*las 31

在:

\n
printf "$var"\n
Run Code Online (Sandbox Code Playgroud)\n

有两个问题:

\n
    \n
  • 作为格式传递的变量数据。$var如果处于攻击者的控制之下可能会出现问题
  • \n
  • 缺少选项分隔符 ( --),因此$var如果它以 开头,则可以将其视为选项-
  • \n
\n

情况会更糟:

\n
printf $var\n
Run Code Online (Sandbox Code Playgroud)\n

大多数类似 Bourne 的 shell 中的 split+glob 都是在$var扩展时执行的,这会导致安全隐患中提到的安全隐患:忘记在 bash/POSIX shell 中引用变量

\n

这里可以执行任意命令:

\n
$ export var1=\'-va[1$(uname>&2)] x\' var2=\'%d a[1$(uname>&2)]\'\n$ bash -c \'printf $var1\'\nLinux\n$ ksh -c \'printf $var2\'\nLinux\n0\n
Run Code Online (Sandbox Code Playgroud)\n

任意uname命令(幸运的是这里无害)由printf.

\n

为了

\n
printf "$var"\n
Run Code Online (Sandbox Code Playgroud)\n

就其本身而言,我能想到的问题就更少了。

\n

最明显的一个是 DoS,var=%1000000000s它会在输出中发送大量空格字符,或者更糟糕的是%.1000000000f,还会占用大量内存和 CPU 时间:

\n
$ var=%.1000000000f command time -f \'max mem: %MK, elapsed: %E\' bash -c \'printf "$var"\' | wc -c\nmax mem: 4885344K, elapsed: 0:12.33\n1000000002\n
Run Code Online (Sandbox Code Playgroud)\n

其他 DoS 可能是$var由于格式不正确或选项不正确而触发语法错误的值,导致printf失败以及在启用该errexit选项时调用的脚本。

\n

printf "$var"with似乎对 、和var=\'-va[1$(uname>&2)]\'来说不是问题bash,这是我知道的唯一三个支持该选项的 shell,将其视为格式,而另外两个则视为语法错误(因为缺少格式)\xc2\ xb9.ksh93zsh-v varnamezsh

\n

ksh93 和 bash 有一些小信息泄露,export var=\'%(%Z %z)T\\n\'揭示了脚本的时区。

\n
$ bash -c \'printf "$var"\'\nBST +0100\n
Run Code Online (Sandbox Code Playgroud)\n

在 中yash,如果是一个具有多个元素的数组,则将使用多个参数printf "$var"进行调用,但's不会进行算术评估,并且无论如何,其算术评估不会受到影响 ksh 的同类命令注入漏洞的影响' s、bash 或 zshprintf$varyashprintf

\n

ksh93 是printf扩展最多的一个(所有日期格式、正则表达式格式转换、基于字形宽度的填充、URI/HTML 编码...),并且它仍然处于实验性阶段。该数据printf "$data"公开了数千行代码。如果其中存在任意命令执行的路径(可能通过某些算术表达式求值或通过在其自己的代码\xc2\xb2 中触发某些错误),我不会感到惊讶。当然,任何实施都可能发生这种情况。printf

\n

C 函数中可变外部数据的问题printf()是当它们包含%最终取消引用堆栈上的随机内存区域的序列时。printf(var)当 var%12$s尝试打印存储在传递给 的第 12 个参数处的字节值时printf。由于printf没有传递任何其他参数,因此堆栈上恰好有其他东西,并且可能是指向保存敏感信息的内存某些区域的指针。使用%n,printf()最终会在那里一些数字。

\n
$ tcc -run -w -xc - $\'%6$s\\n\' <<<\'f(char*f){char*s="secret";printf(f);}main(int c,char**v){f(v[1]);}\'\nsecret\n$ tcc -run -w -xc - $\'%p%p%p%p%p\\n%s\\n\' <<<\'f(char*f){char*s="secret";printf(f);}main(int c,char**v){f(v[1]);}\'\n0x7fff1182db380x7fff1182db500x7900000x80x562b5ec0ba6a\nsecret\n
Run Code Online (Sandbox Code Playgroud)\n

printf实用程序可能最终会调用printf()或可能自己实现所有这些(它们至少必须在某种程度上%bprintf()而对于数字格式,它们需要将参数转换为数字)。

\n

如果他们确实调用printf(),他们将防止在没有足够参数来覆盖格式规范的情况下调用它。这是 POSIX 要求,例如printf "%s"不输出任何内容或输出 0,因此实现应将足够的空字符串或 0 个数字参数传递给.printf %dprintfprintf()

\n

您可以想象编写得不好的printf实现无法正确执行此操作。我不知道有什么,但我awk过去见过他们自己的实现printf()受到影响(也通过OFMTCONVFMT那里涉及printf()处理\xc2\xb3)。

\n
\n

\xc2\xb9print "$var"是通过该向量的任意命令注入漏洞zsh。在那里使用很重要print -- $var,甚至一般来说print -r -- "$var"都是你想要的。

\n

\xc2\xb2 作为一个例子,我得到了一个var=\'%(%.999999999999s)T\'带有 Ubuntu 20.04 附带的 ksh93 的SEGV

\n

\xc2\xb3 即使在今天,使用我当前版本的busyboxbusybox awk -v OFMT=\'%#x %#x %#x %#x %g\' \'BEGIN {print 1.1}\'输出0x1 0x4 0x4 0x4624bb30 1.1busybox awk -v OFMT=\'%n %g\' \'BEGIN {print 1.1}\'段错误。

\n


Ste*_*ris 4

由于您谈论的是 shellprintf命令而不是 Cprintf(3)函数,因此通过正确引用"$var". shell 命令不允许进行可以在 C 中完成的传统堆栈转储。但正如\nSt\xc3\xa9phane 的答案所示,即使如此,也存在一些危险,并且使用不带引号的扩展进行命令注入,如下例所示。

\n

您如何使用输出也可能会对以后的处理产生影响。

\n

创建一个愚蠢的例子:

\n
tst()\n{\n    while [ "$x" == "" ]\n    do\n      read x\n    done\n    \n    printf $x\n}\n
Run Code Online (Sandbox Code Playgroud)\n

条件“$x 非空”将通过,但如果用户输入 ,则 printf 的输出将为空%s。因此,假设 的输出tst不为空的任何例程都可能会失败。这可能会导致采用意外的代码路径。

\n

可能会导致安全问题,具体取决于代码的其余部分。

\n

请注意,这里有很多“如果”和“可以”的警告。这非常依赖于应用程序。这就是为什么我说不会立即产生影响。

\n

因此,如果输入不可信,标准防御编码风格会建议指定格式字符串。如果输入是可信的,那么就没有必要。

\n

从非安全角度来看,您通常希望输出与输入匹配;如果用户输入,hello%sthere您希望看到该内容,而不是hellothere.

\n