当我们打开一个管道命令,或者是为了读(“-|”),或者是为了写(”|-“)的时候,Perl在当前进程中产生分支,并且返回子进程的PID给父进程,返回0给子进程。”or”语句用来决定我们是在父进程还是在子进程。如果我们在父进程(返回值为非零),我们继续执行print()语句。否则我们在子进程中,就执行txt2html程序,使用多于一个参数的exec()的安全版本来避免传递任何命令到shell层。所发生的是,子进程答应txt2html产生的STDOUT输出,然后就默默的消亡了(记住:exec()从不返回),同时父进程从STDIN中读取结果。象这样的技术可以被用来通过管道将输出输到一个外部程序的技术:
open (PROGRAM, "|-")
or exec ("/usr/bin/progname", "$userinput");
print PROGRAM, "This is piped to /usr/bin/progname";
在我们需要管道的时候,open()的以上这些形式应该总是比直接的管道open()命令优先采用,因为它们不通过shell层。现在让我们设想我们要将静态文本转化成格式化很好的HTML页面,并且,基于方便考虑,要存放在显示这些页面的Perl脚本相同的目录下。那么我们的open语句看起来可能是如下形式:
open (STATFILE, "<$username.html");
当用户通过表单中传递username=jdimo的时候,脚本显示jdimov.html。这里仍然有被攻击的可能。不同于c++和c ,perl不用空字节来结束字符串,这样的话字符串jdimov/”jdimov/lo/bah在绝大数c库调用中解释为”jdimo”,但是在Perl中却是”jdimov\0blah”。当perl传递一个含空字符的字符串给用c写的程序的时候,这个问题就突出了。UNIX内核以及绝大多数UNIX 和shell 都 是纯c 语言的。Perl自身也主要是且c编写,当用户如下调用我们的脚本:
statscrit.plusername=jdimov/%00
会发生什么呢?我们的程序传递字符串”jdimov/%。html”到对应的系统调用里以打开它,但是因为那些系统调用是用c编写,接受的是空字节的字符串方式。结果怎样呢?如果有文件”jdimov”的话就会显示这个文件,可能并没有这个文件,即使有也不是很有用。但是如果用"statscript./pusername=statscript。p/%"来调用脚本,会发生什么呢?如果脚本和我们的html文件在同一个目录下的话,这样我们可以用这个输入来期骗脚本,来显示给我们所有的内容。在这种情况下或许不是什么大的安全危险,但是它肯定能被其它的程序使用,因为它允许攻击者分析其他可利用的缺陷的来源。
单引号
在perl中,另一种读取外部程序的输出的方法是把命令放在单引号里。所以如果我们想在分等级的$stats的文件中保存我们stats文件的内容的话,我们可以这样做:
$stats=’cat/user/stats/$username’;
这确实要通过shell层来实现。任何把用户输入包含在一对单引号内的脚本都有发生前面讨论的所有的安全问题的危险。有很多方法可试图使shell不要误解一席可能的转换字符。但是最安全的事就是不要用但引号。取而代之的是,打开一个通到STDIN的管道,然后分叉执行外部程序,就像我们在前一节open()所做的一样。
Eval()和/e 修饰符
函数eval()可以在运行时间执行一个Perl代码块,并返回上一次经评估语句的值。这种函数功能经常用于诸如配置文件,它可以写成perl代码,除非你绝对相信输进eval()的源代码,否则不要做诸如eval/$userinput,之类的事,这也适用于一个常规表述中的/e 修饰符,用来使perl在执行之前解释该表述。
过滤用户输入