运行 perl -ne '...' 的安全隐患 *

Sté*_*las 32 security options perl filenames

显然,运行:

perl -n -e 'some perl code' *
Run Code Online (Sandbox Code Playgroud)

或者

find . ... -exec perl -n -e '...' {} +
Run Code Online (Sandbox Code Playgroud)

(与-p代替相同-n

或者

perl -e 'some code using <>' *
Run Code Online (Sandbox Code Playgroud)

经常在本网站上发布的 one-liners 中发现,具有安全隐患。这是怎么回事?如何避免?

Sté*_*las 38

有什么问题

首先,与许多实用程序一样,文件名以-. 而在:

sh -c 'inline sh script here' other args
Run Code Online (Sandbox Code Playgroud)

其他参数传递给inline sh script; 与perl等价物,

perl -e 'inline perl script here' other args
Run Code Online (Sandbox Code Playgroud)

首先扫描其他 args 以获得更多选项到perl,而不是内联脚本。因此,例如,如果-eBEGIN{do something evil}在当前目录中调用了一个文件,

perl -ne 'inline perl script here;' *
Run Code Online (Sandbox Code Playgroud)

(有或没有-n)会做坏事。

与其他实用程序一样,解决方法是使用选项结束标记 ( --):

perl -ne 'inline perl script here;' -- *
Run Code Online (Sandbox Code Playgroud)

但即便如此,它仍然很危险,这取决于/<>使用的运算符。-n-p

该问题在perldoc perlop文档中进行了解释。

该特殊运算符用于读取输入的一行(一条记录,默认情况下记录为行),其中该输入来自依次传入的每个参数@ARGV

在:

perl -pe '' a b
Run Code Online (Sandbox Code Playgroud)

-p意味着while (<>)围绕代码的循环(此处为空)。

<>将首先打开a,一次读取一行记录,直到文件用完,然后再打开b...

问题是,要打开文件,它使用了第一种不安全的形式open

open ARGV, "the file as provided"
Run Code Online (Sandbox Code Playgroud)

使用这种形式,如果参数是

  • "> afile",它afile以书写模式打开,
  • "cmd|",它运行cmd并读取它的输出。
  • "|cmd",您已打开一个流以写入cmd.

所以例如:

perl -pe '' 'uname|'
Run Code Online (Sandbox Code Playgroud)

不输出所调用文件的内容uname|(顺便说一句,完全有效的文件名),而是输出uname命令的输出。

如果您正在运行:

perl -ne 'something' -- *
Run Code Online (Sandbox Code Playgroud)

并且有人rm -rf "$HOME"|在当前目录中创建了一个名为(又是一个完全有效的文件名)的文件(例如,因为该目录曾经可以被其他人写入,或者您提取了一个狡猾的存档,或者您运行了一些狡猾的命令,或者其他软件中的另一个漏洞被利用了),那么你就有大麻烦了。意识到该问题很重要的领域是在公共领域/tmp(或可能被此类工具调用的工具)中自动处理文件的工具。

所谓的文件> foofoo||foo是一个问题。但在较小程度上< foo,并foo与前导或尾随ASCII间隔字符(包括空格,制表,换行,CR ...)以及该装置的那些文件将不被处理或错误之一将是。

还要注意某些多字节字符集中(如?BIG5-HKSCS)中的某些字符以字节 0x7c 结尾,即|.

$ printf ? | iconv -t BIG5-HKSCS | od -tx1 -tc
0000000  88  7c
        210   |
0000002
Run Code Online (Sandbox Code Playgroud)

所以在使用该字符集的语言环境中,

 perl -pe '' ./n?
Run Code Online (Sandbox Code Playgroud)

会尝试运行./n\x88命令perl不会尝试在用户的语言环境来解释文件名!

如何修复/解决

AFAIK,您无法改变perl系统范围内一劳永逸的不安全默认行为。

首先,问题仅出现在文件名的开头和结尾的字符上。所以,虽然perl -ne '' *还是perl -ne '' *.txt有问题,

perl -ne 'some code' ./*.txt
Run Code Online (Sandbox Code Playgroud)

不是因为现在所有的参数都./.txt(所以不是-, <, >, |, space...)开始和结束。更一般地说,在glob 前面加上./. 这也避免--使用许多其他实用程序调用或开始的文件的问题(在这里,这意味着您不再需要选项结束 ( --) 标记)。

使用-T开启taint模式在一定程度上有帮助。如果遇到此类恶意文件,它将中止命令(仅适用于>|情况,但不适用于<或空白)。

当以交互方式使用此类命令时,这很有用,因为它会提醒您发生了一些不安全的事情。但是,这在进行一些自动处理时可能并不理想,因为这意味着有人可以通过创建文件来使该处理失败。

如果你确实想处理每个文件,不管它们的名字,你可以使用CPAN 上ARGV::readonly perl模块(不幸的是,默认情况下通常没有安装)。这是一个非常短的模块,它执行以下操作:

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
   };
};
Run Code Online (Sandbox Code Playgroud)

基本上,它通过将" foo|"例如转换为"< ./ foo|\0".

您可以BEGINperl -n/-p命令中的语句中执行相同的操作:

perl -pe 'BEGIN{$_.="\0" for @ARGV} your code here' ./*
Run Code Online (Sandbox Code Playgroud)

在这里,我们根据./正在使用的假设对其进行了简化。

但是,那个(和ARGV::readonly)的副作用是$ARGVinyour code here显示了尾随的 NUL 字符。

更新 2015-06-03

perlv5.21.5 及更高版本有一个新的<<>>运算符,其行为类似于,<>只是它不会进行特殊处理。参数将仅被视为文件名。因此,对于这些版本,您现在可以编写:

perl -e 'while(<<>>){ ...;}' -- *
Run Code Online (Sandbox Code Playgroud)

(不要忘记--或使用./*)而不必担心它会覆盖文件或运行意外命令。

-n/-p仍然使用危险的<>形式。请注意符号链接仍在被遵循,因此这并不一定意味着在不受信任的目录中使用它是安全的。

  • 你一整天都在努力,我敢打赌。做得好。 (3认同)
  • perl 的不错更新,但奇怪的是 perl 开发人员没有添加 -P 和 -N 选项来使用它(无法更改现有的 -p 和 -n,因为某些脚本可能依赖于不安全的行为) (2认同)

cuo*_*glm 9

除了@Stéphane Chazelas 的回答之外,如果我们使用-i命令行选项,我们就不必担心这个问题:

$ perl -pe '' 'uname|'
Linux

$ perl -i -pe '' 'uname|'
Can't open uname|: No such file or directory.
Run Code Online (Sandbox Code Playgroud)

因为在使用-ioption 时,perl使用stat在处理之前检查文件状态:

$ 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.
Run Code Online (Sandbox Code Playgroud)