2.7 处理文件名
文件名是提交给CGI脚本的简单数据,但如果不小心的话,却能导致许多麻烦。如果用户输入的名字中包含路径因素,如目录斜杠和双点,尽管期望的是输入一个简单的文件名--例如file.txt--但结果却可能是/file.txt或../../../file.txt。根据Web服务器的安装以及对提交的文件名做什么操作,系统中的所有文件就有可能都暴露给了一个聪明的黑客。
进一步,如果用户输入了一个已有文件的名字或者一个对系统的运行很重要的文件名,怎么办?对如果输入的名字是/etc/passwd或C:\WINNT\SYSTEM32\KRNL32.DLL怎么办?根据在CGI脚本中对这些文件进行什么操作,它们有可能被发送给用户或者被垃圾覆盖了。在Windows 95和Windows NT下,如果不检查反斜杠字符(\),可能会允许Web 浏览器通过UNC文件名访问甚至不在该Web机器上的文件。
如果用户在文件名中输入了不合法的字符怎么办?在UNIX下,任何以句点(.)开头的文件名都是不可见的。在Windows下斜杠(/)和反斜杠(\)都是目录分隔符。很可能不小心写了一个Perl程序,当文件名以管(pipe)(|)开头时,尽管自己以为仅仅是打开了一个文件,实际上却是执行了一个外部程序。如果用户知道怎么办的话,甚至可以把控制字符(例如Escape键或Return键)作为文件名的一部分送给脚本。
更坏的情况是,在shell脚本中,分号用于结束一条命令并开始另一条命令。如果脚本设计目的是cat用户输入的文件,用户可能输入file.txt;rm-rf/作为文件名,导致返回fi1e.txt,然后清除整个硬盘而不经任何确认。
2.8 输入合理,输出却不合理
为了避免所有这些问题,关闭由它们打开的所有安全缝隙,检查用户输入的每个文件名。必须确保输入正是程序预期的输入。
这样做的最好办法是将输入的文件名的每个字符与可接收字符的清单进行比较,如果不匹配就返回一个错误。这比维持一个所有合法字符的清单并比较它们要安全得多——要想让什么字符溜掉太容易了。
以下程序清单是用Perl如何完成这种比较的例子。它允许任何字符字母(大写或小写调)、任何数字、下划线和句点。它还进行检查以确保文件名不以句点开头。这样,该段代码就不允许可以改变目录的斜杠,不允许可以将多条命令放在一行的分号,或者破坏Perl的Open()调用的Pipes了。
程序清单 保证所有字符都是合法的
if (($file_Name =~ /[^a-zA-Z_\.]/) || ($file_Name =~ /^\./)) {
#File name contains an illegal characgter or starts with a period
}
警告
尽管上述程序清单中的代码清除了大部分不合法的文件名,但操作系可能还有一些限制,而该代码没有覆盖到。例如,文件名可以用数字开头吗?或者以下划线开头?如果文件中包含多个句点或者句点后多于三个字符怎么办?整个文件名足够短得能满足文件系统的限制吗?
必须不断向自己提出这种问题。在写CGI脚本时最危险的事是认为用户会遵守指令。其实用户是不会的。保证用户不犯错误是编程者自己的事。
2.9 处理HTML
另外一种看起来无害的但却能导致很大麻烦的输入是在请求用户输入文本信息时得到的HTML。以下的程序清单是一个Perl程序片段;它向任何在$user_Name变量中输入了一个名字的人,例如John Smith,发出问候信息。
程序清单 发出定制的问候脚本
print ("<HTML><TITLE>Greetings!<TITLE><BODY>\n");
print ("Hello,$user_Name! It's good to see you!\n");
print ("</BODY><HTL>\n");
想像一下,如果用户不是仅仅输入一个名字,而是输入了<HR><H1><P ALIGN="CENTER">John Smith</P><H1><HR>或想像一下当脚本希望得到用户名时,黑客输入了<IMG SRC="/secret/cutekid.gif">,结果是公开了本该保密的信息。允许输入HTML可能很危险。
比输入简单的HTML修改页面或访问画面更危险的是恶意的黑客可能输入一条服务器端的include指令。如果web服务器设置为服从服务器端include,用户就可以输入
<!--#include file="/secret/project/p1an.txt"-->
而不是他的名字,以便看到秘密计划的全部文本,或者用户可以输入<!--#inc1ude fi1e-"/etc/passwd"-->来获取机器的口令文件。可能最坏的情况是黑客可能输入<!--#exec cmd="rm-rf/"-->而不是他的名字。这样上述程序清单中的代码会删掉硬盘上几乎所有内容。
警告
由于经常被恶意地使用,服务器端的include经常被禁止使用以保护站点免受侵害。现在假定这些都没问题。即使关闭了服务器端的include并且不介意用户能看到自己硬盘上的任何图片或者改变页面显示的外观,也仍然有问题--不仅是针对编程者的,而且针对其他用户。
CGI脚本的一个通常用途是留名册(guestbook):访问站点的顾客可能签个名,让别人知道他们已经在那儿了。一般情况下用户简单地输入他的名字,该名字会在访问者清单中出现。但是,如果将The last signee!<FORM><SELECT>作为用户名输入怎么办?<SELECT>标记将导致Web浏览器忽略位于<SELECT>和一个不存在的</SELECT>之间的所有内容,包括以后清单中加入的任何名字。即使有10个人签了名,仅有前3个会显示出来,因为第三个名字包含一个<FORM>和一个<SELECT>标记。因为第三个签名者在他的名字中使用了HTML标记,他后面的任何名字都不会显示出来。
对于用户输入HTML而不是普通的文本的情况有两种解决办法:
1)快速但比较粗糙的办法是不允许小于号(<)和大于号(>),因为所有HTML标记必须包含在这两个字符中,所以清除它们(或者如果碰到它们就返回一个错误)是一种防止HTML被提交并返回的简单的办法。下面一行Perl代码简单地清除了这两个字符:$user_Input=~s/<>//g;
2)更精细一点的办法是将这两个字符转换成它们的HTML换码--—种特殊的代码,用于表示每个字符而不使用该字符本身。下面的代码通过全部用<替换了小于符号,用>替换了大于符号,从而完成了转换:
$user_Input=~s/</&1t;/g;
$user_Input=~s/>/>/g;
|