当前位置:主页>web开发> CGI的安全(二)
CGI的安全(二)
来源:作者:
缺省情况下,下面的全局选项这样设置:

Options Indexes FollowSymLinks

当URL指定的目录里没有要查找的文件时,Indexes允许你指定一个文件。缺省情况下,这个变量为index.html,通过srm.conf中的DirectoryIndex来指定,很符合我们的意图。FollowSymLinks意指服务器会返回符号连接指向的数据。我没看到这个特性的必要性,所以我禁止了它。现在,这一行看起来象这样:

Options Indexes

如果我想在任何目录中使CGI程序有效,我可以通过包含ExecCGI选项来设置:

Options Indexes ExecCGI

这一行,结合在srm.conf中的AddType指令,可以允许我通过在任何目录中给所有的CGI程序添加.cgi的扩展名来执行一个CGI。

缺省情况下NCSA httpd的配置,通过在一个具有适当的属性和访问限制的特定目录中创建.htAccess文件使access.conf中的所有设置都可以被超越。在这种情况下,我不介意用户改变它们的访问限制。然而,我不想赋予用户在他们自己的目录里执行CGI和.htaccess文件的能力。

AddType application/x-httpd-cgi .cgi
Options Indexes ExecCGI

因此,我编辑access.conf来允许用户超越除了选项外所有的设置:

AllowOverride FileInfo AuthConfig Limit

现在,我的服务器安全的配置了。我只允许在cgi-bin目录中运行CGI,并且使服务器嵌入指令完全无效。服务器以nobody用户运行,一个我的系统中不存在的用户。我禁止了所有我不需要的特性,并且用户不能超越这些年特殊的限制。想了解很多的其他的配置信息,包括详尽的访问限制,请参照NCSA服务器说明文件。

2.写出安全的CGI程序

假设你已经使的你的计算机和Web服务器很安全了,那么你后面就应该学会怎样写出一个安全性很好的CGI程序。编写安全的CGI的原则和前面提到的相似:

A.你的程序只能实现你指定的功能。
B.不要给客户额外的它不需要知道的信息。
C.不要相信客户给你正确的信息。

关于第一条可能存在的安全隐患我在guestbook的例子中已经说明了。我提到了几个可以揭露漏洞的常见的错误,但是,你同样应该记住:你应当考虑你所应用的每一个函数的所有含义。

第二条是一般安全性原则的简单扩展:系统之外的人对你的系统了解的越少,你的系统就越没有可能被攻破。

最后一条原则只是一条很好的很重要的编程原则,但同样也是安全性很好的一个。CGI程序应该是安全可靠、健壮的。一个hacker可能做的第一件事是想尽一切办法通过在你的CGI程序中不断调整输入来搞乱程序,进而达到攻入计算机的目的。如果你的程序并不健壮,那么这时,它或者会崩溃,或者会实现其它的功能(当然这些功能是你不允许的)。这两种可能性都是令人不快的。为了杜绝这种可能性,不要对你的客户可能发送的信息格式或值作任何的假定。

大多数CGI程序的本质是简单的输入/输出程序。它提取客户端的说明并返回 一些响应。这种程序几乎没有风险(当然也会出现漏洞,后面你会看到)。因为CGI程序并不对输入感兴趣,没有什么错误可能发生。然而,一旦你的程序利用输入启动,可能回调用其他的程序,写文件,或者做一些功能更强大的而非简单返回输出的事情,那么你就会冒引入安全漏洞的风险。通常,功能是直接和安全风险成比例的。

2-1.语言的风险性

不同的语言有其与生俱来的安全风险。任何语言都可以编写安全的CGI程序,但是你必须注意每个语言的怪癖(急转)。这里,我只讨论C和Perl,但是它们的有些特性并不适用于其它语言。想得到其他语言的指定信息,请参照适当的文件。

在前面的章节我们学到,一般来说,编译CGI程序比解释脚本更可取。编译程序有两个优势:首先,你不需要有服务器可理解的解释器;其次,程序的源文件是不可访问的。注意,像Perl一样的传统的解释型语言可以被编译成二进制形式。(关于如何在Perl中实现,请参阅Larry WaRandall Schwartz 的《Perl编程》)从安全立场来说,编译的Perl程序和编译的C程序一样好用。

像C这样比较低级的语言会出现被称为buffer overflow的问题。C语言并没有处理字符串的好的内置的方法。通常的方法或者是声明一个字符数组或者指向字符的指针。很多人倾向于前一种方法,因为它编程比较简单。思考一下下面两个功能等价的程序代码。

程序1. 在C语言中使用数组定义字符串.
#include
#include
#define message "Hello, world!"
int main()
{
char buffer[80];
strcpy(buffer,message);
printf("%sn",buffer);
return 0;
}

程序2. 在C语言中使用指针定义字符串.
#include
#include
#include
#define message "Hello, world!"
int main()
{
char *buffer = malloc(sizeof(char) * (strlen(message) + 1));
strcpy(buffer,message);
printf("%sn",buffer);
return 0;
}

程序1比程序2简单得多,而且在这个特定的例子里,两者都可以很好的工作。我们假设有这样一个例子:我已经知道了我处理的字符串的长度,因此,我可以定义一个适当的数组长度。但是,在CGI程序里,你不知道输入的字符串会有多长。举个例子,如果信息的长度大于80 char,那么程序1会崩溃(即我们通常说的"溢出")。

这被称为buffer overflow,聪明的hacker就会利用这个来远程执行命令。这个缓冲溢出的bug存在于NCSA httpd v1.3中。这是为什么一个网络(或CGI)程序员需要更细心地编程的很好的例子。在一个单用户的机器里,缓冲溢出只能造成系统崩溃。在崩溃的单用户计算机中没有必要利用缓冲溢出来执行程序,因为大概你已经执行了你需要的任何程序(除了公共终端)。然而,在网络系统中,一个崩溃的CGI程序远不是这么简单,它会成为未经授权的用户进入的后门。

程序2中的代码解决了两个问题。首先,它动态的分配了存储字符串的足够的空间。其次,注意我将信息的长度加了1。这样,我实际上分配了比字符串长度多1字节的内存。这就保证字符串不会是0。因为目标字符总是会为额外的字符留有空间,strcpy()函数在目标字符串的最后添加了空字符,strcpy()放置了空字符。没有理由认为传送给CGI脚本的字符串会是空字符,因此,为了以防万一,我在最后留了1字节的空间。

倘若你的C程序避免了像缓冲溢出这样的问题,那么你就可以写出安全的CGI程序。然而,这是艰苦的工作,特别是当你的CGI很大更复杂的时候。这些问题将迫使你花费比一般的CGI任务更多的时间来思索低级语言的设计工作。基于这个原因,你可能更喜欢高级一点的编程语言(如Perl)。

然而,具有高级特点的Perl有着冒失的一面。尽管你能假设Perl会正确地处理字符串的存储,但当Perl使用你并不注意的高级一点的语法做一些事情时,很可能会有危险。在下一节中你会更清楚的了解到。

2-2.shell危险性

很多的CGI任务都可以使用其他的程序很容易的实现。例如,你要写一个CGI的邮件网关,完全使用CGI程序来完成执行邮件的发送代理是很愚蠢的行为。更实用的方法是将数据通过管道传送到一个存在的邮件传送代理程序,比如sendmail,然后让sendmail来完成剩下的工作。这种习惯很好并值得鼓励。

安全风险依赖于你怎样调用这些外部的程序。完成这项工作在Perl和C中有很多函数可以实现。它们中很多函数通过调用shell,然后让shell来执行这个命令。这些命令被列在表1中,如果你使用了它们中的一个,那么你就使得Unix hells在攻击下显得很脆弱。

表1. C和Perl中可以调用shell的函数.
 Perl 函数 C 函数
 system(’...’) system()
 open(’| ...’) popen()
 exec(’...’)

 eval(’...’)

`...`

为什么shell很危险呢?有很多的非数字的字符可以通过shell转换成特殊的字符。这些字符被称为元字符(译者注:这里我将metacharacter译为元字符),见表2。

表2. Shell metacharacters.
; < > * | ` & $
! # ( ) [ ] : {
} ’ "


每一个这种字符在shell中都起着特殊的作用。例如,假如你想利用finger来查询一台计算机并将结果存储到一个文件中,你可以在命令行中如下输入:

finger @fake.machine.org > results

这会使用finger查询主机fake.machine.org并将查询结果保存到一个文本文 件results中。这个>字符在这里是一个重定向符。如果你要实际地使用>字符——例如,你想将它回显到屏幕上——你将需要在这个字符前加一个反斜杠。举个例子,下面将向屏幕输出一个符号>:

echo >

这被称为转义字符(escaping or sanitizing the character string)。

hacker是怎样利用这个作为他(她)的优势的?观察以下程序3中用perl编写的finger程序。这个程序所做的是允许用户查询一个用户和一台主机的详细信息,并且,这个CGI可以查询用户并显示结果。