我们在前面提到system()有一个参数表,并且将第一个元素看作命令来执行,而将其余的元素作为参数来传递。所以我们可以稍微改变一下我们的脚本,使只有我们想让执行的程序能够被执行:
system ("cat", "/usr/stats/$username");
既然我们分开来指定程序的参数,那么shell就永远也不会被调用了。所以发送";rm -rf /*"也就不会起作用了,因为攻击字符串将只会被解释成一个文件名而已。
这种方法比单个参数的版本要好多了,因为它避免了使用shell命令,但是仍然有潜在的缺陷。特别的,我们要考虑到$username的值会不会被利用产生程序中能被执行的弱点。举例来说,一个攻击者仍然可以利用我们重写的代码版本,通过把$username设置成字符串"../../etc/passwd"来获得系统的密码文件。
使用那样的程序的时候很多地方会出错,举例来说,一些应用程序将特殊的字符序列解释成执行一条shell命令的请求。一个普遍的问题是有些版本的Unix邮件工具当它们在一定的上下文背景下看到有”~!…”等字符序列的时候将会执行一个shell命令。所以在一个消息体中的空白行中包含"~!rm -rf *"的用户输入将会在某种情形下产生问题。
只要是谈及安全的,上面论及system()函数的任何内容也适用于exec().
Open()函数
在Perl中open()函数被用来打开文件。在最为常见的形式中,它是这样使用的:
open (FILEHANDLE, "filename");
这样使用的时候,’filename”是以只读方式打开的。如果”filename”是含有”>”标志的前缀,那么它是为输出而打开的,并且在文件已经存在的时候覆盖原文件;如果含有”>>”前缀,那么是为追加打开的;前缀”<”打开文件来进行输入操作,但这也是不含前缀的时候的默认方式。用未经确认的用户输入作为文件名的一部分所产生的一些问题应该总是比较明显的。举例来说,向后回溯浏览目录的骗招在这里仍然能用。还有其他值得担忧的问题。现在我们使用open()替换”cat”来修改我们的脚本文件。我们象这样的命令:
open (STATFILE, "/usr/stats/$username");
然后我们从文件中读取代码并显示它。Perl文档告许我们:如果文件名是以”|”开始的,文件名将会被解释成一个输出管道命令;反之,如果文件名以”|”结束的话,文件名将会被解释成将让我们进行输出的管道。
于是,只要加上一个”|”前缀,用户就可以在/usr/stats目录下运行任何命令了。向后回溯目录的操作能够让用户在这个系统里执行任何程序。
一种解决这个问题打方法是:对于你想要打开并向其中输入的文件总是要求通过加”<”标识显式的指明.
有时我们确实要调用一个外部的程序,比如,我们想要改表我们的脚本文件以让他能够读取旧的纯文本文件/usr/stats/username,但是在显示给用户之前要先通过一个HTML过滤器。我们有一个马上就可以使用的便利的方法来实现这个意图。一种方法可以这样做:
open (HTML, "/usr/bin/txt2html /usr/stats/$username|");
print while <HTML>;
不幸的是,这依然要通过shell层。然而我们可以采用open()调用的另一个形式来避免牵涉到shell:
open (HTML, "-|")
or exec ("/usr/bin/txt2html", "/usr/stats/$username");
print while <HTML>;