受污染的信息
讓信息不受污染聽上去似乎是不可能的事情,要保證腳本徹底安全,只能既不使用外部數(shù)據(jù),也不使用來自外部連接或接口的數(shù)據(jù)(例如運行一個命令或連接到外部數(shù)據(jù)庫)。對于一些簡單的腳本,這是完全有可能做到的。
但對于一般的 Web 應(yīng)用程序,會從一個表單或其他位置接收數(shù)據(jù),然后在其他表單中使用這些數(shù)據(jù),在這種情況下,使用腳本解決方案才是首選。
使用這樣的數(shù)據(jù)可能造成嚴重后果。例如,請考慮一下 清單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" |
假設(shè)我們能一直獲得完全有效的 email 地址,那么這段腳本看上去沒有任何問題。但我們使用了 system() 函數(shù)來發(fā)送 email,因此 email 地址的內(nèi)容可能會影響到系統(tǒng)。例如,假設(shè)所提供的 email 地址是:
現(xiàn)在 email 地址不僅(可能)包含有效的 email 地址,而且還通過 email 告訴其他人密碼文件。system() 函數(shù)打開一個子 shell 并執(zhí)行相關(guān)內(nèi)容。這就是一個重大安全問題。
如果 Perl 沒有提供稱為污染模式的內(nèi)置模式,那么追蹤不同信息的來源就會變得很困難。如果 Perl 確定實際有效的用戶 ID 是其他 ID,或通過在命令行或腳本開頭處使用 -T 選項,那么系統(tǒng)會自動啟用污染模式。
啟用污染模式后,Perl 會檢查不同數(shù)據(jù)和變量的來源與使用情況,確保使用的信息未執(zhí)行帶有不安全或危險操作的腳本,未包含不受信任的信息。顧名思意,這些數(shù)據(jù)被劃分為受污染的 數(shù)據(jù)。
對于源自命令行參數(shù)、環(huán)境變量、本地信息和某些系統(tǒng)調(diào)用(包括訪問共享目錄、共享內(nèi)存和系統(tǒng)數(shù)據(jù))的所有信息,Perl 會識別受污染的數(shù)據(jù)。另外,所有從外部文件讀取的數(shù)據(jù)都會受污染。
不能在調(diào)用子 shell 的命令(包括管道輸入/輸出和 system() 調(diào)用或 exec() 調(diào)用)或修改文件的目錄的命令(如寫入、刪除或重命名)或進程中直接或間接使用受污染的數(shù)據(jù)。
此規(guī)則還有一個特例,print()(及其衍生物)和 syswrite() 不會觸發(fā)污染錯誤或子方法、子引用或哈希鍵。
污染功能還會自動擴展,監(jiān)視可疑的值,即使您未直接使用它。例如,無論何時調(diào)用 system() 或 exec(),都會檢查 PATH 環(huán)境變量的值,無論是否在命令行中使用了受污染的變量,因為所執(zhí)行的命令是由 PATH 決定的。檢查 PATH 是為了確保路徑中列出的所有目錄都是絕對路徑,而且所有者及其所在組以外的人無法執(zhí)行寫入操作。這樣做可防止所運行的命令產(chǎn)生更大的問題。
如果啟用了污染模式,Perl 會產(chǎn)生錯誤并停止執(zhí)行,它還會識別出正在使用的受污染值。例如,使用不安全的 PATH 會產(chǎn)生以下錯誤:
Insecure $ENV{PATH} while running with -T switch at t.pl line 11 |
而使用不安全的變量會產(chǎn)生以下錯誤:
Insecure dependency in system while running with -T switch at t2.pl line 2 |
在典型的 Web 應(yīng)用程序中,無論用來收集信息的方法是什么,用戶從表單提供的數(shù)據(jù)都會受污染。源自 CGI 腳本的數(shù)據(jù)可以從標準輸入或環(huán)境變量中獲得(這取決與所使用的 HTTP 方法和環(huán)境),而且這兩者都被劃分為污染源。
為了保護腳本執(zhí)行,并確保未使用不安全數(shù)據(jù),您需要識別信息并去除污染,以便可以安全使用數(shù)據(jù)。
使用 CGI::Carp
在 Perl 腳本中,報告錯誤的常用方法是使用 warn() 或 die() 函數(shù)來報告或產(chǎn)生錯誤。而對于 Carp 模塊,它可以對產(chǎn)生的消息提供額外級別的控制,尤其是在模塊內(nèi)部。
另外一個模塊 CGI::Carp 提供了很多與 Carp 模塊一樣的功能。它專門設(shè)計用于 Web 腳本中,可將錯誤信息寫入指定的日志,而不是寫入默認 Web 服務(wù)器日志中(例如,由 Apache 生成的日志),或者您可以在某種受控方式下將信息寫入 Web 頁面。
標準 Carp 模塊提供了 warn() 和 die() 函數(shù)的替代方法,它們在提供錯誤定位方面提供更多信息,而且更加友好。當在模塊中使用時,錯誤消息中包含模塊名稱和行號。
在 Carp 模塊內(nèi)部,有 4 個主要函數(shù),carp() 是警告消息的同義詞,croak() 與 die() 一樣,可以結(jié)束腳本。cluck() 和 confess() 分別與 warn() 和 die() 類似,但提供了從產(chǎn)生錯誤處的;厮葑粉。
如果同時使用 Carp 和 CGI::Carp 模塊,那么標準函數(shù),例如 warn()、die() 和 Carp 模塊函數(shù)、croak()、confess() 和 carp() 將會將錯誤信息寫入已配置好的 HTTP 服務(wù)器日志,并附帶日期/時間戳和腳本來源。
使用 HTTP 服務(wù)器錯誤日志的一個替代方法是使用 CGI::Carp 并利用 carpout() 函數(shù)。該函數(shù)只有一個參數(shù),即您用來寫入錯誤信息的文件的文件句柄(通常會將該信息發(fā)送到 STDERR)。您需要顯式導(dǎo)入 carpout() 函數(shù)。如 清單 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"; |
日志中產(chǎn)生的信息是通過日期和產(chǎn)生輸出的腳本名稱進行區(qū)分的:
[Thu Sep 2 11:35:56 2010] carpout.cgi:Some error must have occurred |
所有的標準方法都假設(shè)您想要將錯誤信息寫入日志文件中。但是您可能并不始終具有訪問日志的權(quán)限,或者并不總是能夠登錄到瀏覽器來獲取信息。
因此 CGI::Carp 函數(shù)提供了一個 fatalsToBrowser 選項將致命錯誤消息(die()、confess())重新指向瀏覽器和 Web 服務(wù)器日志。這樣可以確保您的用戶能夠看到腳本所產(chǎn)生的錯誤。非致命錯誤(warn() 和 carp())將會按照常規(guī)繼續(xù)發(fā)送至錯誤日志。
要使用 CGI::Carp 模塊,必須在加載該模塊時將它指定為一個選項,請使用 CGI::Carp qw/fatalsToBrowser/;。我們可以將它添加到文件瀏覽腳本中,以確保錯誤被正確報告和識別。
使用 Plack
信息污染以及使用 CGI::Carp 都是低級的問題,但仍然會引起人們的重視。但是,可以通過使用一些 Web 應(yīng)用程序框架(例如 Catalyst 或 Dancer)來簡化 CGI 應(yīng)用程序的低層次方面,比如處理查詢參數(shù)和輸出頭部材料。Plack 可以同框架一起使用,也可以單獨使用,如下所示。
Plack 是 Web 框架和 Web 服務(wù)器的 Perl 紐帶。Plack 位于代碼(無論是否使用框架)和 Web 服務(wù)器(例如,Apache、Starman 和 FCGI)之間。這意味著您(以及您所用的框架)無需擔心 Web 服務(wù)器的設(shè)置,反之亦然。
設(shè)置 Plack
我們現(xiàn)在將開始設(shè)置 Plack。我們將會使用 cpanm(來自 App::cpanminus)下載模塊并將它安裝到 local::lib(無需具有 root 訪問權(quán)限)。如 清單 3 所示。
清單 3. 初始設(shè)置
# archive of any existing cpan configurationmv ~/.cpan ~/.cpan_original # Then one of the following:# if you can run wgetwget -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 curlcurl -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 |
以上步驟就是快速方便地構(gòu)建 Web 應(yīng)用程序所需的核心 Plack 模塊(參見下方 清單 4)。
清單 4. 使用 cpanminus 安裝 Plack
cpanm Task: lack # Please also run this as we will use it later cpanm Plack::Middleware::TemplateToolkit |
主目錄中的 perl5 文件夾下已經(jīng)包含了所需的所有模塊。下一步是創(chuàng)建一個 .psgi 配置文件,用它返回一個 Web 頁面(參見下方 清單 5)。
清單 5. 創(chuàng)建一個 .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 服務(wù)器,如下所示:
plackup 1.psgi。您會看到:
HTTP::Server:
SGI:Accepting connections at http://SERVER_IP:5000/。
使用 Web 瀏覽器轉(zhuǎn)至 http://SERVER_IP:5000/。如果您是在自己的臺式機上開發(fā)程序,則可以使用 http://localhost:5000/ 。您會看到頁面顯示 “All is good”。實際上,轉(zhuǎn)到任何頁面都會看到 http://localhost:5000/any_page.html,因為不管請求的是什么,都會返回此內(nèi)容。
您可能會注意到,在命令行上可以看到 Web 服務(wù)器的訪問日志。這是因為 Plack 默認情況下被設(shè)置為開發(fā)模式,并且打開一些額外的中間件層,其中包括 AccessLog、StackTrace 和 Lint。
如果要在運行時看到 StackTrace,那么請在清單 4 中注釋掉第 27 行,只需在行首加上一個井號 (#) 即可:# ["All is good"] # Content。
重新啟動 plackup 命令(輸入 Ctrl+C 停止進程,然后運行 plackup 1.psgi 啟動),F(xiàn)在,在 Web 瀏覽器中再次轉(zhuǎn)至http://localhost:5000/,您將會看到錯誤的 StackTrace。注意頁面頂部的主要錯誤消息 “response needs to be 3 element array, or 2 element in streaming”。然后,您可以按照追蹤的每一步驟,單擊每一段追蹤信息下方的 Show function arguments 和 Show lexical variables 鏈接來幫助調(diào)試。
去掉 # 并重新啟動,那么您就再次擁有了一個有效的 .psgi 文件。
開發(fā)
plackup 命令有好幾個命令行參數(shù),運行 perldoc plackup 命令會顯示相關(guān)文檔。最常用的參數(shù)是 -r 或 --reload;這會讓 plackup 監(jiān)控 .psgi 文件(如果 psgi 文件有相應(yīng)的 lib 目錄,也會受到監(jiān)控):plackup -r 1.psgi。
擴展應(yīng)用程序
Plack 中有很多有用的應(yīng)用程序,您肯定想將它們集成到您的 Web 門戶中。例如,在
清單 6 中,我們使用
Plack::App:
irectory來列出目錄,并將其內(nèi)容用作靜態(tài)文件。我們將使用
Plack::App::URLMap 來選擇將應(yīng)用程序加載到哪個 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: irectory; 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: irectory->new( { root => "/tmp/" } )->to_app; # Create a mapper objectmy $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 )。
更多的中間件和應(yīng)用程序
有很多的 Plack::Apps 和 Plack::Middleware 模塊可以幫助我們完成常見任務(wù)。我們將看一看Plack::Middleware::TemplateToolkit,它通過模板化引擎 Template-Toolkit (TT) 來解析文件。圖像和其他靜態(tài)內(nèi)容不會通過 TT,因此我們會配置 Plack::Middleware::Static,通過特定的擴展名直接提供文件。在此之前,我們想在出現(xiàn)錯誤 404(文件未找到)時,會顯示一個好看的頁面;我們使用 Plack::Middleware::ErrorDocument 來完成這項任務(wù)。我們所需加入的代碼如 清單 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 " lack::Middleware::ErrorDocument", 404 => "$root/page_not_found.html"; # These files can be served directly enable " lack::Middleware::Static", path => qr{[gif|png|jpg|swf|ico|mov|mp3|pdf|js|css]$}, root => $root; # Our application $default_app; } |
到此為止,深入研究一下提供 PSGI 支持并能使用 Plack 的眾多 Web 框架之一也許是值得的。這些框架提供了執(zhí)行更復(fù)雜的任務(wù)的結(jié)構(gòu)和支持。讓我們來看一下 Catalyst、Mojolicious 或 Dancer。Perl.org Web 框架白皮書討論了使用框架的諸多優(yōu)勢。
結(jié)束語
由于要保證從用戶處接收的信息的安全,因此在 Perl Web 門戶腳本中解析并使用 Web 數(shù)據(jù)變得非常復(fù)雜。一旦批準了通過 Perl 腳本訪問底層文件系統(tǒng),就必須確保 CGI 腳本不能訪問您不想讓外部人員訪問的文件。
Plack 不能消除您對這些因素的擔憂,但它能讓構(gòu)建先進的 Web 應(yīng)用程序系統(tǒng)的過程變得輕松很多。Plack 能夠解決這些問題,而且提供了一個簡化的環(huán)境來構(gòu)建 Web 應(yīng)用程序。Plack 可以處理 Web 服務(wù)器與 Perl 應(yīng)用程序之間的復(fù)雜性,簡化并保護您的應(yīng)用程序和服務(wù)器。
作者簡介
![]()
Leo Lapworth 專注于快速開發(fā)和尋找問題的解決方案。他通常不關(guān)心內(nèi)容;他關(guān)心的是如何獲取以及如何為公司、客戶和用戶提供更好的服務(wù)。他主要研究 Perl 的開源系統(tǒng) (LAMP) ,他還是 London Perl 社區(qū)的活躍成員。
![]()
Martin C. Brown,馬丁.布朗,是Studio B 工作室的作者,一個早期的IT主管,在跨平臺集成方面經(jīng)驗豐富。作為一名熱心的開發(fā)工程師,他曾經(jīng)為一些特殊用戶制作了動態(tài)站點,包括HP和Oracle,并且現(xiàn)在是Foodware.net的技術(shù)主管。目前他是一名自由撰稿人和咨詢顧問,馬丁是比較知名的,作為SME時與微軟有過工作協(xié)作,他是LinuxWorld雜志的LAMP技術(shù)編輯,AnswerSquad.com團隊的核心成員,并已經(jīng)撰寫了大量的不同主題的書籍,如微軟認證,iMacs以及開放源碼編程。除了這些努力外,馬丁在很多平臺和眾多環(huán)境中依然保持是一名普通并且喜愛編程的程序員。
http://www.ibm.com/developerworks/cn/aix/library/au-perlweb_revision/index.html