显然,运行:
perl -n -e 'some perl code' *
或者
find . ... -exec perl -n -e '...' {} +
(与-p
代替相同-n
)
或者
perl -e 'some code using <>' *
经常出现在本网站上发布的俏皮话中,具有安全隐患。这是怎么回事?如何避免呢?
答案1
有什么问题
首先,与许多实用程序一样,您会遇到以-
.而在:
sh -c 'inline sh script here' other args
其他参数传递给inline sh script
;与perl
等价的,
perl -e 'inline perl script here' other args
扫描其他参数以获取更多选项珀尔首先,不是内联脚本。因此,例如,如果-eBEGIN{do something evil}
当前目录中有一个被调用的文件,
perl -ne 'inline perl script here;' *
(有或没有-n
)都会做坏事。
与其他实用程序一样,解决方法是使用选项结束标记 ( --
):
perl -ne 'inline perl script here;' -- *
但即便如此,它仍然是危险的,这取决于<>
每当使用-n
( sed -n
mode)、-p
( sed
mode)、-a
/ -F
( awk
mode) 中的任何一个而不使用-i
(in-place) 时所使用的运算符。
该问题在perldoc perlop
文档中进行了解释。
该特殊运算符用于读取一行(一条记录,默认情况下记录为行)输入,其中该输入来自依次传入的每个参数@ARGV
。
在:
perl -pe '' a b
-p
意味着while (<>)
代码周围有一个循环(此处为空)。
<>
将首先打开a
,一次读取一行记录,直到文件耗尽,然后打开b
...
问题是,要打开文件,它使用第一种不安全的形式open
:
open ARGV, "the file as provided"
使用这种形式,如果参数是
"> afile"
,它afile
以书写模式打开,"cmd|"
,它运行cmd
并读取它的输出。"|cmd"
,您打开了一个流以写入 的输入cmd
。
例如:
perl -pe '' 'uname|'
不输出所调用的文件的内容uname|
(顺便说一句,一个完全有效的文件名),而是命令的输出uname
。
如果你正在跑步:
perl -ne 'something' -- *
有人在当前目录中创建了一个名为rm -rf "$HOME"|
(又是一个完全有效的文件名)的文件(例如,因为该目录曾经被其他人写入,或者您提取了一个不可靠的存档,或者您运行了一些不可靠的命令,或者其他软件中的另一个漏洞被利用),那么你就有大麻烦了。重要的是要意识到该问题的领域是自动处理文件的工具民众类似的区域/tmp
(或此类工具可能调用的工具)。
> foo
名为, foo|
,的文件|foo
是一个问题。但在较小程度上< foo
,foo
如果带有前导或尾随 ASCII 空格字符(包括空格、制表符、换行符、cr...),则意味着这些文件将不会被处理,或者会出现错误的文件。
另请注意,某些多字节字符集中(如ǖ
BIG5-HKSCS)中的某些字符以字节 0x7c 结尾,即|
.
$ printf ǖ | iconv -t BIG5-HKSCS | od -tx1 -tc
0000000 88 7c
210 |
0000002
因此,在使用该字符集的语言环境中,
perl -pe '' ./nǖ
会尝试运行./n\x88
命令perl
不是尝试在用户的区域设置中解释该文件名!
如何修复/解决
AFAIK,您无法一劳永逸地改变perl
系统范围内不安全的默认行为。
首先,问题仅出现在文件名开头和结尾的字符上。所以,当perl -ne '' *
或者perl -ne '' *.txt
是一个问题时,
perl -ne 'some code' ./*.txt
不是因为现在所有参数都以(所以不是, , , , 空格...)开始./
和结束。更一般地说,最好添加前缀.txt
-
<
>
|
球体和./
。这还可以避免使用许多其他实用程序调用-
或开头的文件出现问题-
(在这里,这意味着您不再需要选项结束 ( --
) 标记)。
使用-T
开启taint
模式有一定帮助。如果遇到此类恶意文件,它将中止命令(仅适用于>
和|
情况,但不适用于<
或 空格)。
当以交互方式使用此类命令时,这非常有用,因为它会提醒您发生了一些危险的事情。不过,在进行一些自动处理时,这可能并不理想,因为这意味着有人可以做到这一点加工仅创建一个文件就会失败。
如果您确实想处理每个文件,无论其名称如何,您可以使用ARGV::readonly
perl
CPAN 上的模块(不幸的是通常默认情况下不安装)。这是一个非常短的模块,它的作用是:
sub import{ # Tom Christiansen in Message-ID: <24692.1217339882@chthon> # reccomends essentially the following: for (@ARGV){ s/^(\s+)/.\/$1/; # leading whitespace preserved s/^/< /; # force open for input $_.=qq/\0/; # trailing whitespace preserved & pipes forbidden }; };
基本上,它通过将" foo|"
例如转换为 来清理@ARGV "< ./ foo|\0"
。
BEGIN
您可以在命令的语句中执行相同的操作perl -n/-p
:
perl -pe 'BEGIN{$_.="\0" for @ARGV} your code here' ./*
在这里,我们根据正在使用的假设对其进行简化./
。
不过,该(和)的副作用ARGV::readonly
是$ARGV
inyour code here
显示尾随 NUL 字符。
更新2015-06-03
perl
v5.21.5 及更高版本有一个新的<<>>
运算符,其行为类似<>
,但它会不是进行特殊处理。参数仅被视为文件名。因此,使用这些版本,您现在可以编写:
perl -e 'while(<<>>){ ...;}' -- *
(不要忘记--
或使用./*
)而不用担心它会覆盖文件或运行意外的命令。
-n
/但-p
仍然使用危险的<>
形式。并注意符号链接仍在被遵循,因此这并不一定意味着在不受信任的目录中使用它是安全的。
答案2
此外@Stéphane Chazelas 的回答-i
,如果我们使用命令行选项,我们就不必担心这个问题:
$ perl -pe '' 'uname|'
Linux
$ perl -i -pe '' 'uname|'
Can't open uname|: No such file or directory.
因为在使用-i
选项时,perl
使用了统计数据在处理文件之前检查文件状态:
$ strace -fe trace=stat perl -pe '' 'uname|'
stat("/home/cuonglm/perl5/lib/perl5/5.20.1/x86_64-linux", 0x7fffd44dff90) = -1 ENOENT (No such file or directory)
stat("/home/cuonglm/perl5/lib/perl5/5.20.1", 0x7fffd44dff90) = -1 ENOENT (No such file or directory)
stat("/home/cuonglm/perl5/lib/perl5/x86_64-linux", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
Process 6106 attached
Linux
Process 6105 suspended
Process 6105 resumed
Process 6106 detached
--- SIGCHLD (Child exited) @ 0 (0) ---
$ strace -fe trace=stat perl -i -pe '' 'uname|'
stat("/home/cuonglm/perl5/lib/perl5/5.20.1/x86_64-linux", 0x7fffdbaf2e50) = -1 ENOENT (No such file or directory)
stat("/home/cuonglm/perl5/lib/perl5/5.20.1", 0x7fffdbaf2e50) = -1 ENOENT (No such file or directory)
stat("/home/cuonglm/perl5/lib/perl5/x86_64-linux", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
stat("uname|", 0x785f40) = -1 ENOENT (No such file or directory)
Can't open uname|: No such file or directory.