格式良好的printf
通常有一个可以使用的格式:
$ var="Hello"
$ printf '%s\n' "$var"
Hello
但是,不提供格式可能会产生什么安全隐患?
$ printf "$var"
Hello
由于变量扩展被引用,应该不会有任何问题,不是吗?
答案1
在:
printf "$var"
有两个问题:
- 作为格式传递的变量数据。如果
$var
处于攻击者的控制之下可能会出现问题 - 缺少选项分隔符 (
--
),因此$var
如果它以 开头,则可以将其视为选项-
。
情况会更糟:
printf $var
大多数类似 Bourne 的 shell 中的 split+glob 都是在$var
扩展时执行的,导致了上面提到的那种安全漏洞忘记在 bash/POSIX shell 中引用变量的安全隐患。
这里可以执行任意命令:
$ export var1='-va[1$(uname>&2)] x' var2='%d a[1$(uname>&2)]'
$ bash -c 'printf $var1'
Linux
$ ksh -c 'printf $var2'
Linux
0
任意uname
命令(幸运的是这里无害)由printf
.
为了
printf "$var"
就其本身而言,我能想到的问题就更少了。
最明显的一个是 DoS,var=%1000000000s
它会在输出中发送大量空格字符,或者更糟糕的是,%.1000000000f
还会占用大量内存和 CPU 时间:
$ var=%.1000000000f command time -f 'max mem: %MK, elapsed: %E' bash -c 'printf "$var"' | wc -c
max mem: 4885344K, elapsed: 0:12.33
1000000002
其他 DoS 可能是$var
由于格式不正确或选项不正确而触发语法错误的值,导致printf
失败以及在errexit
启用该选项时调用的脚本。
printf "$var"
with对于 、和var='-va[1$(uname>&2)]'
来说似乎不是问题bash
,这是我知道的唯一三个支持该选项的 shell,将其视为格式,而另外两个则视为语法错误(因为缺少格式)。ksh93
zsh
-v varname
zsh
ksh93 和 bash 有一些小信息泄露,export var='%(%Z %z)T\n'
揭示了脚本的时区。
$ bash -c 'printf "$var"'
BST +0100
在 中yash
,如果是一个包含多个元素的数组,printf "$var"
则将printf
使用多个参数进行调用,但是的$var
yash
printf
不进行算术求值,而且无论如何,它的算术求值不受同类类型的影响影响 ksh、bash 或 zsh 的命令注入漏洞。
ksh93printf
是扩展最多的一个(所有日期格式化、正则表达式格式转换、基于字形宽度的填充、URI/HTML 编码...),并且它仍然处于实验性阶段。printf "$data"
那里暴露了数千行代码数据。如果其中存在任意命令执行的路径(可能通过某些算术表达式求值或通过触发其自己的代码中的某些错误),我不会感到惊讶。当然,任何printf
实施都可能发生这种情况。
C 函数中可变外部数据的问题printf()
是当它们包含%
最终取消引用堆栈上的随机内存区域的序列时。printf(var)
当 var%12$s
尝试打印存储在传递给 的第 12 个参数处的字节值时printf
。由于printf
没有传递任何其他参数,因此堆栈上恰好有其他东西,并且可能是指向保存敏感信息的内存某些区域的指针。与%n
,printf()
最终会写作那里有一些数字。
$ tcc -run -w -xc - $'%6$s\n' <<<'f(char*f){char*s="secret";printf(f);}main(int c,char**v){f(v[1]);}'
secret
$ 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]);}'
0x7fff1182db380x7fff1182db500x7900000x80x562b5ec0ba6a
secret
printf
实用程序可能最终会调用printf()
或可能自己实现所有这些(它们至少必须%b
在某种程度上printf()
,而对于数字格式,它们需要将参数转换为数字)。
如果他们确实调用printf()
,他们将防止在没有足够参数来覆盖格式规范的情况下调用它。这是 POSIX 要求,例如printf "%s"
不输出任何内容或输出 0,因此实现应将足够的空字符串或 0 个数字参数传递给.printf %d
printf
printf()
您可以想象编写得不好的printf
实现无法正确执行此操作。我不知道有什么,但我awk
过去见过他们自己的实现printf()
受到影响(也通过OFMT
或CONVFMT
那里涉及printf()
处理)。
然而,print "$var"
是通过该向量的任意命令注入漏洞zsh
。在那里使用它很重要print -- $var
,甚至一般来说print -r -- "$var"
都是你想要的。
² 举个例子,我得到了一个var='%(%.999999999999s)T'
带有 Ubuntu 20.04 附带的 ksh93 的SEGV
³ 即使在今天,使用我当前版本的busybox
、busybox awk -v OFMT='%#x %#x %#x %#x %g' 'BEGIN {print 1.1}'
输出0x1 0x4 0x4 0x4624bb30 1.1
和busybox awk -v OFMT='%n %g' 'BEGIN {print 1.1}'
段错误。
答案2
由于您谈论的是 shellprintf
命令而不是 Cprintf(3)
函数,因此通过正确引用"$var"
. shell 命令不允许进行可以在 C 中完成的传统堆栈转储。但正如 Stéphane 的回答所示,即使如此,也存在一些危险,并且使用不带引号的扩展进行命令注入,如下例所示。
如何您使用的输出也可能会对以后的处理造成影响。
创建一个愚蠢的例子:
tst()
{
while [ "$x" == "" ]
do
read x
done
printf $x
}
条件“$x 非空”将通过,但如果用户输入 ,则 printf 的输出将为空%s
。因此,假设 的输出tst
不为空的任何例程都可能会失败。那可以导致采用意外的代码路径。
那可以导致安全问题,具体取决于代码的其余部分。
请注意,这里有很多“如果”和“可以”的警告。这非常依赖于应用程序。这就是为什么我说不会立即产生影响。
因此,如果输入不可信,标准防御编码风格会建议指定格式字符串。如果输入是可信的,那么就没有必要。
从非安全角度来看,您通常希望输出与输入匹配;如果用户输入,hello%sthere
您希望看到该内容,而不是hellothere
.