缺少 CGI.pm 和其他谜团
作为专栏作家,我的职业生涯已经到了无法忽视大量邮件的地步。我并没有被电子邮件的洪流淹没,但收到读者的回复仍然是一个令人愉快的惊喜。不过,在过去一两个月里我收到的一些邮件值得进一步回复。除了回答一些关于 CGI.pm 和 1 月刊中出现的留言簿程序的简短问题外,我还将讨论与 CGI 程序员和 Web 管理员相关的安全问题。
我在一月刊专栏中收到的第一个问题之一是:“CGI.pm 在哪里?” 这些读者感到惊讶的是,我的专栏附带的本应可以工作的程序在他们那里却失败了。特别是,他们收到了这样的消息
Can't locate CGI.pm in @INC at - line 1. BEGIN failed—compilation aborted at - line 1.
这里出了什么问题?为什么他们的系统上没有 CGI.pm?
这个问题的简单答案是,CGI.pm 尽管具有许多有用的和令人惊叹的功能,但它只是许多出色的 Perl 5 模块之一,这些模块未包含在标准 Perl 发行版中。Perl 附带了许多基本模块,但这只是冰山一角。您可能想要使用的大多数模块都可从 CPAN(综合 Perl 存档网络)获得——例如用于数据库服务器访问的模块(无需单独的 Perl 可执行文件,例如 oraperl 和 sybperl)、时间和日期的操作、电子邮件文件夹的处理等等。
CPAN 是一组 FTP 站点,这些站点定期相互镜像,并允许程序员下载程序员慷慨捐赠给 Perl 社区的各种模块的最新版本。这些模块之一,也是我在职业生涯和我的 LJ 专栏中经常使用的模块之一,是 CGI.pm——正如您可能猜到的那样,它是一个使我们能够相对容易地编写 CGI 程序的模块。
访问 CPAN 最简单的方法是通过 perl.com 上的 reflector,该站点由 Perl 社区的杰出人物之一 Tom Christiansen 运行和维护。如果您访问 https://perldotcom.perl5.cn/CPAN,请确保删除最后的斜杠,您将能够从附近的站点中选择一个站点,从中您可以下载各种 Perl 模块。或者,您可以包含最后的斜杠,以及相对于 CPAN 的路径名称的其余部分,并从完整的 CPAN 网络中输入一个随机站点,如下所示
https://perldotcom.perl5.cn/CPAN/modules/by-module/
此 URL 将生成可供下载的各种模块类别的列表。每个类别包含一个或多个模块;对于 CGI.pm,我们需要进入 CGI 类别,在那里我们可以找到(截至撰写本文时)文件 CGI.pm-2.30.tar.gz。
下载此文件后,使用 gunzip 程序解压缩该文件,然后使用 tar 程序展开它。使用以下命令执行此操作
gunzip --verbose CGI.pm-2.30.tar.gz tar -xvvf CGI.pm-2.30.tar.gz
双 v 选项在展开文件时指定额外的“verbosity”(详细程度);虽然您当然可以在不使用任何详细程度的情况下解压 CGI.pm,但我更喜欢查看我正在展开的内容,而不是简单地让命令自行运行。
如果您使用的是带有 GNU tar 的系统(几乎所有 Linux 系统都是这种情况),则可以使用 tar 的 z 选项来组合这两个操作
tar -zxvvf CGI.pm-2.30.tar.gz
以这种方式解压 CGI.pm 后,您应该能够进入新创建的目录(在上面的示例中名为 CGI.pm-2.30),使用标准的 Perl 模块安装命令配置和编译文件。以下是该过程在我的系统上的外观
[1008] /downloads% cd CGI.pm-2.30 [1009] /downloads/CGI.pm-2.30% perl Makefile.PL Checking if your kit is complete... Looks good Writing Makefile for CGI [1010] /downloads/CGI.pm-2.30% make cp CGI/Carp.pm ./blib/lib/CGI/Carp.pm cp CGI/Fast.pm ./blib/lib/CGI/Fast.pm cp CGI/Push.pm ./blib/lib/CGI/Push.pm cp CGI.pm ./blib/lib/CGI.pm Magnifying ./blib/man3/CGI::Fast.3 Magnifying ./blib/man3/CGI::Carp.3 Magnifying ./blib/man3/CGI::Push.3 Magnifying ./blib/man3/CGI.3现在您已经配置并编译了 CGI.pm,使用命令 make install 将其安装到您的系统中。为了做到这一点,您需要以 root 用户身份登录,如下所示
[1001] /downloads/CGI.pm-2.30# make install Skipping /usr/lib/perl5/site_perl/./CGI/Carp.pm (unchanged) Skipping /usr/lib/perl5/site_perl/./CGI/Fast.pm (unchanged) Skipping /usr/lib/perl5/site_perl/./CGI/Push.pm (unchanged) Installing /usr/lib/perl5/site_perl/./CGI.pm Skipping /usr/lib/perl5/man/man3/./CGI::Fast.3 (unchanged) Skipping /usr/lib/perl5/man/man3/./CGI::Carp.3 (unchanged) Skipping /usr/lib/perl5/man/man3/./CGI::Push.3 (unchanged) Installing /usr/lib/perl5/man/man3/./CGI.3 Writing /usr/lib/perl5/site_perl/i586-linux/auto/CGI/.packlist Appending installation info to /usr/lib/perl5/i386-linux/5.003/perllocal.pod就这样。现在,@INC(Perl 变量,用于了解在哪里查找 Perl 模块)将包含 CGI.pm,您将不再收到那些抱怨 Perl 找不到文件的讨厌的错误消息。
请注意,Red Hat Linux 用户可能希望使用 RPM(Red Hat Package Manager)版本的 CGI.pm(和其他 Perl 模块),而不是标准发行版。这样做的好处是安装会更新 RPM 数据库,并以优雅的方式跟踪系统上的文件。缺点是,CGI.pm 的最新和最棒版本通常需要几天或几周才能出现在 Red Hat 服务器上——而其他不太流行的模块有时根本无法作为 RPM 使用。您可以在 Red Hat 站点(及其镜像站点)ftp.redhat.com 上找到各种 RPM。
我还收到了几位读者的来信,提醒我们注意一月刊中留言簿程序中的两个错误。众所周知,留言簿通常包含来自网站用户的多个问候语。因此,如果我们使用 Perl 命令打开文件
open (FILE, ">$filename") || &error_opening_file($filename);
我们就会自找麻烦,因为单个 > 运算符不仅会打开文件进行写入,还会破坏文件之前可能包含的任何信息。代码实际上应该读取
open (FILE, ">>$filename") || &error_opening_file($filename);这意味着我们想要打开 $filename 中命名的文件进行写入,并将我们的新数据附加到之前可能存在的内容之后。请注意,如果之前不存在文件,>> 运算符会创建一个文件,因此您可以随意使用 >> 进行文件创建和附加。
该程序中的另一个问题,由读者 Bill Holloway 注意到,与以下代码段有关
@names = $query->param; # Iterate through each element from the form, # writing each element to $filename. Separate # elements with $separation_character defined # above. foreach $index (0 .. $#fields) { # Get the input from the appropriate HTML # form element $input = $query->param($fields[$index]); # Remove any instances of # $separation_character $input =~ s/$separation_character//g; # Now add the input to the file print FILE $input; # Don't print the separation character after # the final element print FILE $separation_character if ($index }
当然,由于我们已将 HTML 表单元素导入到 @names 数组中,因此我们必须从 @names 中读取它们,而不是从上面的代码所做的 @fields 中读取。因此,行
$input = $query->param($fields[$index]);应替换为
$input = $query->param($names[$index]);正如您在程序的更正版本中看到的那样,该版本出现在列表 1中。
另一位读者 Maro Shim(来自韩国的信件)注意到我在二月刊中关于每次需要添加新的 CGI 目录时都必须向 HTTP 服务器的配置文件添加 ScriptAlias 或 Exec 指令的内容。Maro 指出,这意味着管理员必须为每个单独的用户修改文件。
让我们以 Apache 为例,深入探讨允许个人用户拥有自己的 CGI 目录的优缺点。然后我们将讨论为什么这可能不是最好的做法。最后,我们将讨论为每个用户提供 CGI 访问权限,但又不让他们控制整个系统。
Maro 的建议是,管理员可以在 cgi-bin 目录(对于在我 Red Hat Linux 机器上运行的 Apache 副本,默认情况下为 /home/httpd/cgi-bin)中创建一个符号链接,并且该链接可以指向每个用户的 public_html 目录中的目录,该目录通常包含用户的 HTML 文件。
例如,以下是我撰写本文时个人主目录的列表
[1068] ~% ls -F 800omni.pdf News/ public_html/ Consulting/ Text/ response1.txt Development/ cgicyrcode.pl test.dgs Mail/ chap4de.doc
因为我使用了 ls 的 -F 选项,所以目录名称以斜杠结尾,这使得它们更容易识别。如果您使用 --color 选项,您也可以通过颜色或粗体文本来识别目录,但我太老式了,不喜欢那样。public_html 目录是我个人 HTML 文件所在的目录,这些文件可以通过以 ~reuven/ 结尾的 URL 访问,因为我的用户名是 reuven,并且 Web 服务器配置为在用户的 public_html 目录中查找。因此,如果有一个文件 index.html,它将可以通过 URL 访问
http://localhost/~reuven/index.html(当然,用适当的主机名替换 localhost)。
个人 HTML 文件很好,并且大大减少了系统管理员在运行 Web 服务器时必须做的工作量,在 Web 服务器上,数十甚至数百个用户可能想要放置自己的主页。但是 CGI 程序呢?这就是 Maro 的信件的重点:在 public_html 目录中,我们可以创建一个名为 cgi-bin 的子目录,如下所示
[1071] ~% cd public_html/ [1072] ~/public_html% mkdir cgi-bin [1073] ~/public_html% ls -F cgi-bin/ test.html
现在,个人 HTML 目录包含两个项目——一个文件 test.html,它(在本例中)可以访问 ~reuven/test.html,以及一个名为 cgi-bin 的目录,我可以将其内容访问为 ~reuven/cgi-bin/。请记住,cgi-bin 这个名称没有任何神奇之处——此时,它的作用就像任何其他子目录一样。实际上,如果我要将 CGI 程序 elephant.pl 放在 ~reuven/public_html/cgi-bin 中,我可以通过转到
http://localhost/~reuven/cgi-bin/elephant.pl来访问它。但是,我们看到的不是执行 elephant.pl 的结果,而是它的源代码。这是真的,因为我们没有告诉我们的服务器它应该执行该程序;我们需要显式地将 ~reuven/cgi-bin 安装为 CGI 目录。这是创建个人 CGI 目录的最常用方法。通过在文件 srm.conf 中包含(在 Apache 下)ScriptAlias 指令,我们可以为系统上的每个用户创建新的 CGI 目录。因此,如果我们有兴趣将 ~reuven/cgi-bin 变成 CGI 目录,我们可以使用以下行
ScriptAlias /~reuven/cgi-bin/ \ /home/reuven/public_html/cgi-bin这将产生预期的效果。但是,这意味着每次我们希望为用户提供 CGI 目录时,我们都需要修改 srm.conf 并重新启动我们的 HTTP 服务器。
Maro 的替代方案通过采用不同的方法为我们节省了这项工作:我们没有向 srm.conf 添加新的 ScriptAlias 指令,而是简单地告诉我们的 HTTP 服务器,它应该遵循已经存在的 CGI 目录中的符号链接,使用以下命令
<Directory /home/httpd/cgi-bin> AllowOverride None Options FollowSymlinks </Directory>
完成此操作后,我们可以为我们想要变成 CGI 目录的任何目录创建符号链接。例如,要将 /home/reuven/public_html/cgi-bin/ 变成 CGI 目录,我们(作为 root 用户,或具有适当权限的其他用户)只需要创建符号链接
ln -s /home/httpd/cgi-bin/reuven \ /home/reuven/public_html/cgi-bin这将允许我们使用
http://localhost/cgi-bin/reuven/elephant.pl它物理上存在于我自己的个人目录中,但逻辑上存在于 /cgi-bin 目录中(就 HTTP 服务器而言),这迫使服务器执行它。
在为个人用户启用 CGI 目录之前,请考虑其后果:CGI 程序可能是从外部世界进入您服务器的入口。如果即使只有一个 CGI 程序是恶意编写的,攻击者也可能获得对您系统的访问权限——例如,收集有关您用户的信息,或使用该信息来更改或损坏文件。为所有用户提供对 CGI 程序的访问权限似乎很方便,并且在短期内肯定会节省您的时间,但安全隐患太严重而不能忽视。
如果您无法将 CGI 限制为系统上一小部分用户,那么您应该考虑安装一个 CGI wrapper 程序,该程序在执行这些程序之前执行安全检查。CGI wrapper 是一个程序,它将 CGI 程序作为其参数。在 wrapper 执行了几项安全检查后,它会执行 CGI 程序——在所有者的 ID 下,而不是通常为 Web 程序保留的 ID 下。这可以防止一个 CGI 程序读取或更改另一个程序的数据——当大量不相关的站点托管在同一系统上时,这是一个越来越可能出现的问题。
一个这样的 wrapper,称为 suEXEC,随 Apache 1.2 一起提供。该程序的配置和编译相对容易,并在 Apache 文档中详细描述。简而言之,您编译 suEXEC 并将其设置为 SUID root,以便它可以更改为用户的用户 ID,无论该所有者可能是谁。最后,您必须将 suexec 程序安装在 Apache 源代码的 httpd.h 文件中定义的位置的普通 CGI 目录之外。
另一个流行的 CGI wrapper 是 CGIwrap,它的工作方式类似,但不受特定 HTTP 服务器的限制。您可以在以下位置阅读有关 CGIwrap 的更多信息
http://www.umr.edu/~cgiwrap/
对于这些 wrapper 来说,在 HTTP 服务器的默认用户 ID 以外的用户 ID 下运行 CGI 程序是一个好主意,让个人用户编写和安装他们选择的各种程序,发送可能导致缓冲区溢出的程序数据的可能性,或者可能将恶意参数传递给使用 Unix shell 的程序的可能性太大了,不容忽视,特别是对于 Unix 以其闻名的安全漏洞而言。您可能需要坚持要求服务器上用 Perl 编写的任何 CGI 程序都使用 -T 参数,这将启用 Perl 的 taint 系统,该系统可防止用户数据在未经过某种过滤器的情况下传递到 shell——但当然,此类检查可能会被忽略,并且并非所有 CGI 程序都是用 Perl 编写的。
简而言之,没有任何完美的解决方案,这意味着在某个时候您将不得不决定是使您的系统更安全(但用户会生气),还是更容易受到可能的损害(但用户对他们运行自己选择的 CGI 程序的能力感到满意)。
当我们在讨论安全问题时,现在可能是我公开擦去 2 月份专栏后脸上残留的一些尴尬的好时机,在我的专栏中,我建议您应该使用 777 权限安装 CGI 程序,数字类型的人称之为“a+rwx”,或者允许系统上的所有用户读取、写入和执行该程序的权限。
正如几位读者注意到的那样,这绝对是一个严重的错误。计算机安全取决于尽可能多地堵住漏洞。在运行来自各种来源的程序的联网多用户系统上,安装一个具有允许系统上的任何人修改该程序内容的权限的程序几乎肯定是一个坏主意,特别是当一两个简单的(并且可能难以察觉的)修改可以将看似无害的程序变成贪婪的 bug-blatter 野兽时。在未运行此处提到的 wrapper 之一的系统上,所有 CGI 程序都以相同的权限运行,这意味着有人可以编写一个程序来干扰另一个程序的代码或数据。
如果您是唯一一个在特定 CGI 程序或网站上工作的程序员,那么您可以将您的程序安装为 755 权限 (u=rwx,ga+rx),以便系统上的其他人(包括通常负责运行 CGI 程序的 HTTP 服务器)可以读取和执行您的代码,但不能修改它。
如果您与其他人一起在一个站点或 CGI 程序上工作,您可以将权限设置为 775 (ug=rwx,a+rx),这使每个人都可以读取和执行该程序,但只允许所有者和文件组的成员编辑它。
可能在某些时候,使用 777 (a+rwx) 权限安装 CGI 程序是合适的,但这些情况很少见。
这次的邮件就到此为止。下个月,我们将回到讨论如何通过编写一些小型 CGI 程序来简化非程序员的生活,他们可能希望修改磁盘上表格中的条目,这些程序可以高效且轻松地读取和写入文件。
Reuven M. Lerner 自 1993 年初以来一直在玩 Web,当时它看起来更像是一个有趣的玩具,而不是世界下一个伟大的媒介。他目前在以色列海法的公寓里担任独立的互联网和 Web 顾问。在不从事 Web 工作或在非正式教育项目中做志愿者时,他喜欢阅读几乎所有主题的书籍,特别是政治和哲学、烹饪、解纵横字谜和远足。您可以通过 reuven@the-tech.mit.edu 或 reuven@netvision.net.il 与他联系。