[摘要]目录: 1. 基本的安全问题 1-1. 操作系统 1-2. 增强服务器的安全 1-2-1.你应该在什么地方放置你的CGI程序? 1-2-2.SSI(Server-Side Includes) 1-2-3.增强你的Unix服务器的安全 1-2-4.例子:...
目录:
1. 基本的安全问题
1-1. 操作系统
1-2. 增强服务器的安全
1-2-1.你应该在什么地方放置你的CGI程序?
1-2-2.SSI(Server-Side Includes)
1-2-3.增强你的Unix服务器的安全
1-2-4.例子:安全的配置NCSA服务器
2.写出安全的CGI程序
2-1.语言的风险性
2-2.shell危险性
3.安全处理
3-1.SSL
3-2.SHTTP
4.概要
如果你以前从未编写过应用于网络的软件,那么安全问题可能是你在编程时最不注重的了。毕竟,在单机上,你没有必要担心写了不安全的程序,因为,大概也只能有一个人可以接近那台计算机。
但是,在编写应用于Internet的软件中需要非常强调安全问题。有一个挺老的计算机格言说:"使一台计算机真正安全的唯一方法是将它与世界断开连接并把计算机放到紧锁的房间里。"可见,将计算机和一个网络简单相连就会降低你的计算机的安全性。
对于越大的相连的网络这句话越适用,比如Internet,这里有成千上万的人可能会访问你的计算机。很多基于Internet的服务,特别是WWW,别设计成能使其他人很容易的从你的计算机中获取信息。这些你允许接受访问的服务(或者是有意的,或者是无意的)都有可能成为老谋深算、心怀恶意的人的攻击途径。一个很糟糕的网络服务器很容易被攻破,甚至潜在给出了可以访问你的整个计算机和重要数据的权限。
我说你提供的每一项网络就象进入你系统中另一个门,是指什么呢?什么才是安全破坏呢?不管是什么目的,安全破坏是指一个人从你的计算机中获得了未经授权的访问权。"Unauthorized access"(未经授权的访问权)也可以理解为很多事情,试图从服务器上运行一个非公共的程序,甚至是获得在Unix中获得root权限。
你过多的依赖于为网络服务器编写安全程序的程序员的知识和细心。毕竟,没有人指望你详细审查几千行的源码只为了弄清楚软件是否有安全漏洞;大多数情况,你依赖编程者的可靠性和其他审阅源码和仔细的帮助测试软件的专家。假如网虫们证明了你不能完全相信这些程序员可以写出完美的安全的代码,那么你可以采取措施最大限度的减少风险。
在后面的"保护你的Web服务器",你将学习Web服务器的安全。目前,假定你的应用于Web服务器的软件是安全的,并且正确的配置了;也就是说,没有人可以仅仅通过你的Web服务器从你的机器中获得未经授权的权限。为什么写安全的CGI脚本很重要呢?CGI是一个允许你拓展Web服务器的一般协议。通过编写CGI程序,你能够增加Web服务器的功能。这些功能很可能无意中引入新的安全漏洞。一个糟糕的CGI应用程序很可能允许任何人拥有你的机器的完全的权限。
用户提交一个表单或者是以另一种方式访问CGI脚本的时候,本质上来说,是你允许他们远程运行你的服务器中的应用程序。因为很多的CGI应用程序接受用户的表单输入(或通过填写表,或是通过命令行),从另一个角度来说,你允许用户控制CGI程序的运行。作为CGI程序的作者,你需要确定你的CGI脚本只能用来实现它指定的功能。这一章提到了相关的Web安全问题,提供了编写安全的CGI程序的深入的资料。在本章的最后,你也会学会怎样安全的编写CGI。
1. 基本的安全问题
你的Web服务器的全面的安全性取决于很多因素。如果你的Web服务器没有正确配置或者系统有其它漏洞的话,那么一个安全的CGI程序也是毫无用处的。这里,我论述一些相关的Web安全问题,并说明如何为CGI程序正确的配置你的Web服务器。
1-1. 操作系统
一个通常的问题是什么样的平台对Web服务器来说更安全?运行System 7的Macintosh,Unix的工作站,运行OS/2或Linux的PC等等。在这个问题上有过很多争论,这些反映了人们对不同的操作系统的不同的偏爱。
没有一个操作系统比另外一个明显安全。Unix被认为比单用户的平台(比如acintosh或者是运行着Windows的PC)更安全,因为曾经有人攻破过一些运行着后者(注:括号中)的机器,并拥有了所有文件的权限。然而对于Unix,有一个关于文件属主和权限的基本的理解。如果你的服务器正确的配置了,并被一个安全的用户(比如:非root用户)拥有,这时候,如果未经授权的用户闯进来,他(她)只能造成有限的破坏。然而,有限的破坏已经够糟糕了,在以后的章节的例子中你会明白。
另一方面,因为Unix经常要配置很多不同类型的网络服务,比如mail,FTP,Gopher,WWW等等,因此,有更多的潜在的“后门”。加强这些服务的安全性是一个耗时的过程,甚至对有经验的系统管理员亦如此。即使你每项配置正确无误,然而在每个单独的软件包里仍有可能出现恼人的bug。安全漏洞在各种软件包中并不是罕见的,从一些组织(比如CERT(the Computer Emergency ResponseTeam))的有关各种的Unix网络服务周期性的通知中我们可以清楚的了解到。
每一个不同的平台都有其不同的安全含意,但是不能彼此比较安全性。尽管你应该注意每个操作系统的安全性,但是这不应该成为你选择平台的主要标准。选择你的平台,纠正有关该平台相关的安全漏洞,然后安全正确的配置你的Web服务器。在你完完全全的完成这些步骤之后,你才应该将你的精力投入到编写安全的CGI脚本中去。
1-2. 保护你的服务器
编写安全的CGI脚本的第一步要确定你安全并正确的配置了你的Web服务器。如果你的Web服务器并不可靠,那即使你再仔细编写你的CGI脚本也是没有用的,人们仍然可以闯入你的计算机。而且,正确的配置你的Web服务器能够减小糟糕的CGI程序所带来的可能的危害。
More:选择一个安全的Web服务器
在不同的平台有数不清的Web服务器可供使用。如果可能的话,自我确定一个产品是否安全是很困难的,你将不得不依靠公司的信誉和口头承诺。
检查你的选择。在你拥有了一个Web服务器的列表之后,看一下每个产品的有效期以及目前有多少人使用它。越老的并且经常使用的Web服务器,有关的安全方面的bug越有可能被发现并修补。如果源码是开放的,并且你有时间和专门技术,自己从头至尾看一下源文件,看看能否找到潜在的漏洞。阅读网络中不同的新闻组对该产品以及作者和发行人的评论。著名的公司或作者会很快的通知用户其产品的任何问题。阅读各个组织(如CIAC(Computer Incident AdvisoryCapability)和CERT)有关安全方面的警告信息。
检查所有的服务器组件并确定你是否真的需要所有组件的特性。越复杂、功能越强大的服务器,越有可能存在未被发现的安全问题。确定你的服务器支持日志功能,这样你可以跟踪安全问题或其它故障的原因。
有一个对付意外事件的计划。如果发现安全漏洞,要随时准备升级或者替换你的Web服务器。关注新版本的发行和新闻组中有关你的Web服务器的信息。尽量使用Web服务器最新的非测试的版本。
不必担心免费的服务器。关于开发源码使服务器更安全或者相反有争论。如果服务器的源码不公开,安全漏洞将更难发现。如果源码公开,那么,理论上,漏洞将很快被发现,公开并得到修补。
在增强服务器的安全性时,应该有三个目的:
A.配置你的程序使它只能提供你指定的服务。
B.不到必要的时候不暴露任何信息。
C.如果系统遭到入侵,最大限度地减少损坏。
我知道的有关你的计算机的信息越多,我就越有机会闯入你的计算机。例如,如果我知道哪个目录或者文件夹存储了你的所有的敏感的、私有的信息,这样,我将进入你的系统获取全部访问权缩小至只是获得某个目录的权限(通常是更容易了)。或者,如果我可以访问你的服务器配置文件或源码或者是你的CGI脚本,那我可以很容易的浏览它们来寻找安全漏洞。如果你的系统有漏洞,你不想让别人轻易知道,你必须在别人之前发现它们。
1-2-1.你应该在什么地方放置你的CGI程序?
很多服务器允许你通过各种不同途径来运行CGI程序。例如,你可以指定一个特定的目录作为你的cgi-bin。或者,你可以允许CGI存放在任何目录下。
这两种方法都有优缺点,但是从安全的角度来说,在一个指定的目录中放置你的所有的CGI应用程序更好。把所有的程序放到同一个目录使你很容易跟踪你服务器器所有的应用程序并审查它们的安全漏洞,同时,还可以防止被恶意修改。
如果你倾向于使用描述型的语言(例如Perl)来编写你的大部分的应用程序,那么源码被包含在程序自身中。如果你不小心的话,这些代码很容易被阅读,甚至被利用。例如,很多文本编辑器存储备份的文件,通常在文件名的后面加一个扩展名(比如.bak)。
举个例子,emacs使用扩展文件名~存储备份文件。假设你使用Perl编写了一个CGI脚本——program.cgi——存储在Web的数据目录而非中心的指定的目录中。现在,假设你使用emacs对程序做了一些琐碎的修改而忘记了删除备份文件。现在,在你的目录里有了两个文件:program.cgi和program.cgi~。Web服务器知道以.cgi结尾的文件是CGI程序,它会运行这个程序而不是显示它的内容.然而,聪明的用户可能尝试访问program.cgi~.因为它不是以.cgi结尾,你的Web服务器将它以原始的文本文件发送出去,这样就允许用户查看你的源代码来搜寻可能的漏洞.这违反了避免暴露不必要信息的原则.
当然,如果你的服务器允许你指定位于某一特定的目录下的文件均为CGI,那么这个文件的扩展名是什么也就无关紧要了.这样,在前面的例子中,如果备份文件放在这样特定的目录里,当用户试图访问它时,服务器就会运行这个程序而不是发送源代码.
注意到在你的服务器中指定一个中心目录作为CGI程序的存放位置是有限定的,特别是在多用户系统中.例如,如果你是一个ISP(Internet Service Provider)并且你想让你的的用户可以编写并运行他自己的CGI程序,你可能有意允许CGI程序可以存放在任何的目录中.做这个之前,认真考虑一下可替换的选项.你的客户们打算写很多的特定的个性化的脚本吗?如果不是,最好是让你的客户将他的CGI脚本提交给你,然后由你将其添加到cgi-bin目录中,而不要允许CGI可在任何目录中有效.
关于CGI程序的位置另外一个问题是将解释器放在哪里.解释脚本时,服务器运行解释器,由它顺序装载脚本并执行.
不要将解释器放到你的cgi-bin目录中,或其他有关你的数据结构的任何目录中.给了用户访问解释器的权限本质上就是给了他们运行你的系统中任何程序或命令的权力.
如果你使用Windows或其他的非Unix操作系统,这尤其重要.在Unix系统中,你可以在脚本的第一行中指定解释器.例如:
#!/usr/local/bin/perl
# this first line says use Perl to run the following script
在Windows中,举个例子,没有类似在脚本中指定解释器的方法.一个调用Perl脚本的方法是建立一个批处理文件来调用Perl和脚本:
rem progname.bat
rem a wrapper for my perl script, progname.pl
c:\perl\perl.exe progname.pl
然而,你也许倾向于避免建立额外的程序,只是简单的将perl.exe放在你的cgi-bin
目录中,并访问如下的URL:
http://hostname/cgi-bin/perl.exe?progname.pl
[page_break]这也行,但是这样也允许了网络上的任何一个人运行你机器中的Perl命令.例如,可以访问如下的URL:
http://hostname/cgi-bin/perl.exe?-e+unlink+%3C*.*%3E%3
经过解码,其相当于调用Perl并运行下面的一行程序,这行程序将删除当前目录的所有文件.显然,这是我们不想的.
unlink <*.*>;
你永远没有理由将解释器放入你的cgi-bin目录中(或者其他可以运行CGI的目录),所以千万不要这么做.一些Windows服务器能够根据其扩展名辨别脚本的类型并运行相应的解释器.例如,Win-HTTPD认为每一个以.pl结尾的CGI脚本是Perl脚本,并自动运行Perl.如果你的Web服务器没有这个特性,就像这章第一个Windows Perl例子那样使用包装的脚本.More:我应该使用一个解释器吗?
如果你使用一个Unix或者是Macintosh的Web服务器的话,记住永远不要冒险将一个解释器放到你的cgi-bin中.前面我们提到过,Unix允许你指定特定的位置给包含脚本的解释器.为了在Macintosh中使这些脚本有效,你可以使用一个应用程序如ResEdit编辑代码将脚本与挪用的解释器结合.
1-2-2.SSI(Server-Side Includes)
在第四章中,你已经知道了应该避免服务器嵌入指令的原因。一个经常提出的一般原因是安全性。很显然,一些服务器嵌入指令(特别是NCSA和Netsape)的执行会允许用户将程序输入包含到HTML文件中。每次当这些HTML文件被访问时,在服务器端程序会运行并将输出作为HTML文件的一部分显示出来。
允许这种服务器的嵌入指令,你就很容易受到一些安全风险的影响。首先,在Unix的计算机中,程序由服务器的所有者运行,而不是程序的所有者。如果你的服务器没有正确配置,并且将重要的文件或程序交给服务器的所有者,这些文件和程序以及它们的输出有可能被你的计算机的用户所访问。
当你允许用户通过浏览器修改你系统中的HTML文件时,这种风险就增大了。一个通常的例子是留言本。在留言本中,用户填写表单并把信息提交到CGI程序中,程序一般是将未编辑的信息附加到一个HTML文件中。如果不编辑或过滤提交的信息,你就允许了用户从他或她的浏览器中提交HTML代码。如果你允许程序在服务器端嵌入执行,不怀好意的用户就可以通过提交如下的附加代码给你的机器造成破坏:
这个服务器嵌入指令将试图尽可能地删除你的机器中的所有内容。
你可以通过很多方法避免这个问题,而不需要完全关闭服务器嵌入。你可以在将提交的文本附加到你的留言本之前过滤所有的HTML附加代码。或者你可以禁止你的服务器嵌入中的exec的功能(在这章后面的"增强你的Unix服务器的安全"中我将演示在NCSA服务器中如何做)。
如果你忘记了其中的任何一条,其他的一些防护措施同样可以很大程度上减因这种附加代码造成的危害.例如,只要你的服务器以不存在的用户,非root的身份运行,这个附加代码不会删除任何重要的东西,可能什么也不会丢掉.假设不还 好意者不是试图删除你的磁盘上的所有东西,而是使用如下的代码获取你的 etc/passwd作为破解之用:
当然,如果你的系统使用的是shadow型的passwd档,那么你的/etc/passwd对潜伏的hacker来说毫无用处.
这个例子论证了通常的服务器端嵌入指令和CGI中两个很重要的问题.首先,安全漏洞可以被完全隐藏.谁会想到一个简单的使用SSI编写的留言本程序可以体现如此之大的安全风险?其次,一个安全漏洞的潜在的危害可以通过正确配置你的服务器和加强你的系统安全来降低到最小.
1-2-3.增强你的Unix服务器的安全
一个安全的Unix系统对于Web文件服务来说是个非常优秀的平台。然而,在加强服务器安全和正确配置Unix的Web服务器的过程中伴随着很多复杂的问题。你应该做的第一件事就是确定你的机器已经尽可能的安全了。
将你不需要的网络服务关掉,不管对你而言他们是多么没有害处。任何人未必能使用finger协议侵入你的系统,举个例子,它提供了一些用户的信息,然而,finger可以提供给hacker关于你的系统的有用的信息。
加强你的系统的内部安全。如果hacker设法破解了一个用户帐号,要确定这个hacker不会获得额外的权限。安全shadow型的password文件和去除设定用户权限的脚本(脚本以所有者的身份运行,即使是由其他用户调用时)是很有用的。
加强Unix机器的安全是一个复杂的课题,超出了本书的范围。我强烈建议你购买一本这方面的书,阅读Internet上这方面的资源,如果有必要的话,甚至可以雇佣一个咨询顾问。不要低估加强你的机器安全的重要性。
另外,分配隔离的空间给你的Web服务器和文件。你的文件目录的用途是将这些文件提供给其他人使用,可能是整个Internet,因此你不要将你别人知道的任何东西放到这些目录里。你的服务器目录包含重要的日志和配置信息,并且你要尽可能的不要让你的内部用户看到或修改它。
要明智的设置你的目录和服务器的所有权和使用权。为Web相关的目录建立一 新的用户和组是通常的一个方法。确定非特权用户不能更改服务器或文件目录。
你的服务器千万不要以root身份运行(running as root)。在Unix系统中,只有root能够访问小于1234的端口。因为缺省的Web服务器运行于端口80,你需要是root来启动一个Web服务器。然而,在一个Web服务器以root身份运行以后,它可以修改自身进程的所有权,或者改变它用以连接的子进程的所有权。其中任何一种方法都需要服务器以非root身份运行。确定配置你的Web服务器使其以非root身份运行,最好是以一个完全不存在的用户如nobody。这样,如果在你的Web服务器或CGI程序中有漏洞时,它可以降低潜在的危害。
禁止所有你不需要的服务器特性。如果你开始禁止了一个特性,而后来又决定使用它时,你总是可以将其改回来的。像SSI和SSL都是你可能需要禁止的。
如果你的用户不需要通过你的服务器将他们个人的Web文件用于服务,就需要使Web目录无效。这样一来,你就可以完全地控制你的机器中用于服务的所有文件,这对于通常的维护和安全是很重要的。
如果你的用户需要将他们个人文件用于服务(例如,如果你是一个IAP(InternetAccess Provider),确信他们不能超越你的主范围。认真考虑一下用户是否需要在他们的个人目录里运行CGI程序的权力。前面我们已经提到,最好将所有的CGI放到一个集中的位置。
--------------------------------------------------------------------
CGIWRAP:
在Web上一个流行的软件包是cgiwrap,由Nathan Neulinger(nneul@umr.edu)编写。这个软件包允许用户作为程序的拥有者运行他们自己的CGI程序,而不是作为服务器的所有者。
不清楚仅仅允许所有人运行他们自己的未包装的CGI程序是更否更有益。一方面,一个糟糕的CGI脚本由nobody拥有比起由一个实际存在的用户拥有来说,前者可能造成的危害更小。另一方面,如果CGI程序以nobody运行对系统造成了破坏,那么责任在于系统管理员,相反,如果只是一个特定的用户文件被破坏了,那么责任终将是用户的。
我的建议是不要赋予用户运行个人CGI的权力,如果这样不可能,那么你最终使用cgiwrap还是一个简单的程序取决于你想责任出在哪里。
---------------------------------------------------------------------
最后,你可能需要考虑你的Web文件建立一个chroot环境。在Unix系统中,你可以通过使用chroot来保护目录。当server运行在一个chroot的目录中时,它看不到这个目录之外的任何东西。在一个chroot环境中,如果有人想侵入你的Web服务器,他们只会破坏这个目录里的文件。
注意,一个chroot环境仅适用于当Web服务器提供单独的文件资源。如果你的Web服务器将用于服务的用户文件存放在多个目录中时,想建立一个有效的chroot环境几乎是不可能的。另外,解释器(例如Perl或者一个shell)的存在也会降低chroot环境的性能。在一个没有任何shell和解释器的chroot环境中,侵入系统的人最坏情况下能改变和破坏你的文件,如果存在解释器,潜在的危害会上升。
1-2-4.例子:安全的配置NCSA服务器
我将通过讨论NCSA服务器(v1.4.2)来论证怎样着手正确地配置Unix环境下的通用的Web服务器。有很多Web服务器可以运行在Unix系统下,NCSA是最早的服务器之一,被广泛使用并且属于自由软件,而且相当容易配置。我仅说明我认为对Web服务器安全方面有关的配置;想获得有关配置NCSA httpd更多详细的说明,请参照它的站点:
http://hoohoo.ncsa.uiuc.edu/
你可以将这里说明的原则应用到几乎所有的Unix Web服务器中。
首先,我需要表明我的目标。在这个方案中,我想将NCSA服务器架设在一个很小的名为MyCompany的ISP的安全的Unix机器上。这台机器的域名为www.mycompany.net。我需要我的机器中的每一个拥有帐号的人能够将他或她的Web文件用于服务并可以使用CGI或其他的特性。
我绝对应该需要什么特性呢?这里,因为我是一个很小的ISP,我不能让用户自行将其CGI用于服务。如果他们想写出并使用他们自己的CGI程序,他们必须将其提交给我来检查;如果CGI程序没问题,我就安装它。另外,我要提供一些通常需要的一般的程序,比如留言本和各类表单处理的应用程序。现在,这个方案里我不需要其他任何的特性了,包括服务器嵌入指令。
我们来看一下我将如何配置我的Web服务器。我将建立用户和www组;这些将拥有所有恰当的目录。我将建立一个目录来存放我的服务器文件
(/usr/local/etc/httpd/)和存放Web文件的目录(/usr/local/etc/httpd/htdocs/)。所有这些目录对全球是可读的对所建立的用户和组是可写的。
现在,我将要配置服务器。NCSA HTTPD有三个配置文件:access.conf,httpd.conf
和srm.conf。首先,你需要告诉httpd你的Server和HTML的目录所在。在httpd.conf
中,以如下一行来指定Server的目录:
ServerRoot /usr/local/etc/httpd
在srm.conf中,这样指定文件目录:
DocumentRoot /usr/local/etc/httpd/htdocs
因为我想指定在/usr/local/etc/httpd/cgi-bin目录中的所有文件为CGI程序,在srm.conf中包含如下一行:
ScriptAlias /cgi-bin/ /usr/local/etc/httpd/cgi-bin
注意我的cgi-bin目录的实际位置在我的服务器目录而不是文件目录。因为我想要使我的服务器目录(包括包含CGI的目录)尽量为私有,我将它放到文件目录以外。如果你在该目录中有一个名为mail.cgi的CGI,我可以通过如下的URL访问它:
http://www.mycompany.net/cgi-bin/mail.cgi
在srm.conf中需要编辑另一行;它对我们追求特定的服务器安全不是特别有关系,但是为了彻底的安全,我还是要提到它:
Alias /icons/ /usr/local/etc/httpd/icons
这个Alias指令允许我们为你的文件目录树内部或以外的目录指定一个别名。与ScriptAlias指令不同,Alias并不改变目录的含义。
因为我需要禁止服务器嵌入指令,并不允许CGI在cgi-bin以为的目录运行,在srm.conf中我通过在行首插入一个英镑符号(#)注释掉几行:
#AddType text/x-server-parsed-html.shtml
#AddType application/x-httpd-cgi.cgi
AddType可以帮助你在MIME类型和文件扩展名间建立关联。text/x-server-parsed-html对parsed HTML来说是MIME类型,application/x-httpd-cgi则是CGI应用程序类型。这里,我不需要为这种MIME类型指定扩展名,因为在配置服务器的时候,我们忽略cgi-bin中的所有文件扩展名,一律被视为CGI。
最后,我需要通过编辑全局的access.conf文件来设置某些目录的属性和访问权限。为了为所有的目录定义全局的参数,仅仅将没有任何环境标记的指令放到文件中。为了为特定的目录指定参数,在directoryname是目录的全路径时,通过来包含指令。
……