在計(jì)算機(jī)領(lǐng)域——尤其在Internet上——盡管大部分Web服務(wù)器所編的程序都盡可能保護(hù)自己的內(nèi)容不受侵害,但只要CGI腳本中有一點(diǎn)安全方面的失誤--口令文件、私有數(shù)據(jù)、以及任何東西,就能使入侵者能訪問計(jì)算機(jī)。遵循一些簡(jiǎn)單的規(guī)則并保持警惕能使自己的CGI腳本免受侵害,從而可以保護(hù)自己的權(quán)益。
1. 腳本和程序
在開始決定采用何種語言編寫CGI腳本時(shí)應(yīng)考慮幾個(gè)因素,其中之一應(yīng)是安全性。Shell 腳本,Perl程序和C可執(zhí)行程序是CGI腳本最常采用的形式,從安全性角度來說每種都備有優(yōu)缺。盡管沒有哪一種是最好的--基于其他方面的考慮,如速度和可重用性--每種都有實(shí)用的領(lǐng)域。
Shell腳本一般用于小的、快速的甚至可以用完就不要的CGI程序,因此,編寫它們時(shí)常常不考慮安全性。這種疏忽可以導(dǎo)致一些缺陷,使得僅對(duì)系統(tǒng)具有一般知識(shí)的人也能進(jìn)入系統(tǒng)任意走動(dòng)。
盡管Shell CGI 程序最容易寫,甚至只需拼湊一下即可,但控制它們卻很困難,因?yàn)樗鼈円话闶峭ㄟ^執(zhí)行外部的其他程序來完成工作的。這就導(dǎo)致一些可能的隱患,CGI 程序會(huì)繼承任何它使用的程序的安全問題。
例如,常用UNIX實(shí)用程序 awk對(duì)于它能處理的數(shù)據(jù)的數(shù)量有一些相當(dāng)嚴(yán)格限制。如果在CGI腳本中使用awk,那么該程序也就有了同樣的限制。Perl比Shell腳本更進(jìn)一步。Perl用于CGI編程有很多優(yōu)點(diǎn),并且相當(dāng)安全。但Perl能給CGI 作者提供足夠的靈活性從而導(dǎo)致對(duì)安全性的錯(cuò)誤感覺。例如,Perl是解釋型的。這意味著它實(shí)際在調(diào)用時(shí)是先編譯,然后每次執(zhí)行一步。這就很容易使得不正確的用戶數(shù)據(jù)被包括進(jìn)來作為代碼的一部分,從而錯(cuò)誤地進(jìn)行解釋,形成程序中止原因。
最后談?wù)凜。C迅速成為標(biāo)準(zhǔn)應(yīng)用開發(fā)語言,幾乎所有的UNIX和windows NT系統(tǒng)都是用它開發(fā)的。從安全性的角度來看C 似乎是很不錯(cuò),但由于它的流行性,它的好幾種安全性問題已廣為人知,而這些問題也能很容易地被人利用。
例如,C 對(duì)串處理非常差。它不做任何自動(dòng)的定位或清理而讓編程者自己處理所有事情。在處理串時(shí),大部分C 程序員都是簡(jiǎn)單地建立一個(gè)預(yù)定義的空間并希望它足夠大以便處理用戶輸入的任何內(nèi)容。
當(dāng)然,Shell腳本、Perl和C 不是僅有的編寫CGI腳本語言。實(shí)際上,任何可以按預(yù)定義的方式與Web服務(wù)器進(jìn)行交互的計(jì)算機(jī)語言都可以用于編寫CGI程序。在UNIX和Windows NT服務(wù)器上,數(shù)據(jù)是通過環(huán)境變量和標(biāo)準(zhǔn)輸入(stdin) 傳給腳本的,所以任何能從這兩種數(shù)據(jù)源讀取并寫入標(biāo)準(zhǔn)輸出(sidout)的語言都能用于創(chuàng)建CGI:awk、FORTRAN、C++、Basic和COBOL,等。windows的程序員可以使用流行的Visual Basic,這意味著有經(jīng)驗(yàn)的VB程序員不必去學(xué)一門新語言。Macintosh使用AppleEvents、和AppleScript與CGI程序進(jìn)行通信,所以任何可以讀寫這兩者的語言都可使用。
不過,Shell腳本(不管使用那種Shell)、Perl和C仍是最流行為的編寫CGI腳本的語言。這并不是說必須使用它們了;只是說大部程序的庫——即大部分經(jīng)過測(cè)試的安全的庫——都是用這三種語言編寫的。如果自己來選擇CGI編程語言,最好是借鑒前人的經(jīng)驗(yàn)。
2. 誰也不信
幾乎所有的CGI 安全問題都來自與用戶的交互。接收來自外部數(shù)據(jù)源的輸入之后一個(gè)簡(jiǎn)單的、可預(yù)見的CGI程序突然向多方向伸展,每個(gè)方面都可能有最小的縫隙使得“黑客”可以溜進(jìn)來。正是與用戶的這種交互——通過表單或文件路徑——才給予了CGI 腳本這種能力,但同時(shí)也使得它們成了運(yùn)行在Web服務(wù)器上的最潛在的危險(xiǎn)部分。
編寫安全的CGI 腳本很大程度上是創(chuàng)造性和妄想的結(jié)合。編寫者必須有足夠的創(chuàng)造性才能想到用戶使用的,不管是無意地還是別的所有的可能隱含導(dǎo)致問題的發(fā)送數(shù)據(jù)的方式。而且必須有點(diǎn)妄想,因?yàn)橛锌赡懿恢朗裁磿r(shí)候、什么地方、他們將會(huì)一一加以試驗(yàn)。
2.1 兩種導(dǎo)致問題的方式
當(dāng)用戶登錄進(jìn)入Web 站點(diǎn)并開始進(jìn)行交互訪問時(shí),他們能以兩種方式惹麻煩。一種是不遵守規(guī)則,歪曲或違反頁面中建立的每個(gè)限制或約束;另一種方式是按要求去做。
大部分CGI 腳本是作為HTML表單的后臺(tái)運(yùn)行的,負(fù)責(zé)處理由用戶輸入的信息并提供某種定制的輸出。因?yàn)樵谶@種情況下,大部分CGI 腳本編寫時(shí)都等待某種特殊格式的數(shù)據(jù)。它們期望用戶的輸入能匹配收集并發(fā)送信息的表單。不過事情并不總是這樣。用戶可以有許多種辦法繞過這些預(yù)定義的格式而給腳本發(fā)送一些看起來是隨機(jī)的數(shù)據(jù)。CGI 程序必須對(duì)此有所準(zhǔn)備。
其次,用戶可以給CGI 腳本發(fā)送所期望的數(shù)據(jù)類型,按預(yù)期的形式在表單中填入每個(gè)字段。這種類型的提交可以是想像中的來自某個(gè)與站點(diǎn)交互的無意的用戶,也可能來自某個(gè)惡意的“黑客”,憑借他有關(guān)操作系統(tǒng)和Web 服務(wù)器軟件的知識(shí)并利用常見的編程錯(cuò)誤。這些入侵,表面上一切都正常,卻是最危險(xiǎn)的、最難檢測(cè)出來。Web 站點(diǎn)安全性依賴干這種入侵的防止。
2.2 不要相信表單數(shù)據(jù)
在CGI 編程中最常見的安全失誤就是相信從表單傳到腳本的數(shù)據(jù),用戶是未知的一大堆人,他們總能找到一些編程人員從來沒想到過的發(fā)送數(shù)據(jù)的方法--而且是程序員認(rèn)為幾乎不可能的方法。
腳本必須對(duì)這些加以考慮。例如,下面這些情形都是可能的:
1)從一組單單選按鈕中選擇的結(jié)果可能不是表單中提供的選項(xiàng)之一。
2)來自某個(gè)文本字段的數(shù)據(jù)長(zhǎng)度可能大于MAXLENGTH字段允許的長(zhǎng)度。
3)字段本身的名字可能與表單中指定的不相符。
2.3 不合理數(shù)據(jù)的來源
因—些無意的或是有意的原因,導(dǎo)致自己的腳本接收到不知道如何去處理的數(shù)據(jù),有可能導(dǎo)致非預(yù)期的——同時(shí)很危險(xiǎn)的——行為。
下面的代碼實(shí)現(xiàn)了一種表單并向某個(gè)搜索yahoo!數(shù)據(jù)庫的CGI腳本送垃圾。該腳本設(shè)計(jì)得很好并且很安全,因?yàn)樗雎粤瞬徽J(rèn)識(shí)的輸入。
<FORM METHOD="POST" ACTION="http://search.yahoo.com/bin/search">
Enter your name,first then last:
<INPUT TYPE="TEXT" NAME="first">
<INPUT TYPE="TEXT" NAME="last">
</FORM
也許用戶碰巧(或者意識(shí)地)將URL編輯為這個(gè)CGI腳本。當(dāng)瀏覽器向CGI程序提交數(shù)據(jù)時(shí),要簡(jiǎn)單地將輸入表單中的數(shù)據(jù)連到CGI的URL上(用于GET METHODS),就像用戶可以很容易地將Web頁面地址輸入到他的瀏覽器一樣,用戶也可以自己修改發(fā)送給這個(gè)腳本的數(shù)據(jù)。
例如,當(dāng)單擊表單上的Submit按鈕時(shí),Netscape將一個(gè)長(zhǎng)串字符放入Location字段,該串由CGI的URL后接一串?dāng)?shù)據(jù)組成,大部分看起來像表單中定義的NAMES和VALUES。如果愿意的話,可以自由地編輯Location字段的內(nèi)容并按自己的意愿修改數(shù)據(jù):增加表單中沒有的字段,擴(kuò)展由MAXLENGTH選項(xiàng)限制的文本數(shù)據(jù),或者幾乎任何對(duì)象。以下顯示了某CGI腳本預(yù)期從表單中提交的URL。
http://www.altavista.digit.com/cgi-bin?pg=q&what=web&imt=&q=%22An+Entirely+Other%22
用戶可以修改同一URL,CGI腳本仍被調(diào)用,但現(xiàn)在接收的是非預(yù)期的數(shù)據(jù)。為了保證安全,該腳本應(yīng)該在編寫時(shí)就設(shè)計(jì)為能將這種輸入識(shí)別為不被要求的數(shù)據(jù)并加以拒絕。
最后,某個(gè)有野心的"黑客"也許會(huì)寫一個(gè)程序連到Web上的服務(wù)器并假裝是一個(gè)Web瀏覽器。該程序可能做一些任何一個(gè)真正的web瀏覽器從未做過的事,例如給CGI腳本發(fā)送成百兆字節(jié)的數(shù)據(jù)。如果CGI腳本不限制從POST METHOD讀取數(shù)據(jù),那怎么辦?它有可能會(huì)崩潰,也許允許那個(gè)崩潰了系統(tǒng)的人訪問系統(tǒng)。
2.4 拒絕不合要求的表單數(shù)據(jù)
CGI腳本可以有幾種方式拒絕接收提交給它的非預(yù)期的輸入。編寫CGI時(shí)應(yīng)該使用其中一些技巧或所有這些技巧。
首先,CGI 腳本應(yīng)設(shè)置接收多少數(shù)據(jù)的限制,不僅限制整個(gè)提交,也限制提交中的每個(gè)NAME/VALUE對(duì)。例如,CGI腳本讀取POST METHOD,檢查CONTENT-LENGTH環(huán)境變量的大小來確定某輸入是不是合理的預(yù)期輸入。如果CGI 腳本設(shè)計(jì)接收的唯一數(shù)據(jù)是某人的姓名,那么如果CONTENT-LENGTH大于100字節(jié),就應(yīng)該有理由返回一個(gè)錯(cuò)誤。沒有哪個(gè)合理的姓有那么長(zhǎng),通過設(shè)置限制,就能使腳本不再盲目地讀取發(fā)送給它的內(nèi)容。
注意
令人高興的是,不必?fù)?dān)心去限制通過POST方法提交的數(shù)據(jù)。GET是自限制的并且不會(huì)向腳本發(fā)送多于1KB的數(shù)據(jù)。服務(wù)器自動(dòng)限制放人QUERY-STRING環(huán)境變量中的數(shù)據(jù)的大小,而這正是GET發(fā)送給CGI程序的信息。
當(dāng)然,"黑客"們可以很容易地將表單由GET改為PUT從而繞過這種內(nèi)置的限制。至少,程序應(yīng)該檢查一下數(shù)據(jù)是否是用預(yù)期的方法提交的;最好是能正確且安全地處理兩種方法。
下一步,應(yīng)保證腳本知道在接收到不能識(shí)別的數(shù)據(jù)時(shí)該怎么辦,例如,如果某表單要求用戶選擇兩個(gè)單選按鈕之一,腳本就不應(yīng)該假設(shè)因?yàn)橐粋€(gè)按鈕未被選擇,另一個(gè)就一定被選擇了。下面的Perl代碼就犯了這樣的錯(cuò)誤:
if ($form_Data{"radio_choice"} eq "button_one"){
# Button One has been clicked }
else {
# Button Two has been clicked }
這段代碼假定因?yàn)楸韱蝺H提供了兩個(gè)選項(xiàng),而第一項(xiàng)未被選中,那么第二項(xiàng)就肯定被選中了。這不一定是真的。盡管前面的例子沒有什么害處,但在某些情況下這樣的假設(shè)可能很危險(xiǎn)。
CGI腳本應(yīng)該能預(yù)期這種情形而相應(yīng)地進(jìn)行處理。例如,如果出現(xiàn)一些非預(yù)期的或"不可能"的情形,可以打印一個(gè)錯(cuò)誤,如下所述:
If ($form_Data{"radio_choice"} eq "button_one") {
#Button One seleted }
elsif ($form_Data{"radio_choice} eq "button_two") {
#Button Two Selected }
else {
#Error }
通過加入第二個(gè)if語句--顯式檢查"radio_choice"實(shí)際上是"button_two"--這樣腳本更安全了;它不再做假設(shè)了。
當(dāng)然,錯(cuò)誤不一定是期望腳本在這些情形下生成的。有些腳本過于小心,驗(yàn)證每個(gè)字段,即使是最輕微的非預(yù)期數(shù)據(jù)都生成錯(cuò)誤信息,這樣往往很掃用戶的興。讓CGI 腳本識(shí)別非預(yù)期數(shù)據(jù)然后扔掉它,并且自動(dòng)選擇一個(gè)缺省值也可以。
另一方面,腳本還可幫助用戶糾正錯(cuò)誤而不是簡(jiǎn)單地發(fā)一條錯(cuò)誤消息或設(shè)置一個(gè)缺省值。如果表單要求用戶輸入機(jī)密文字,腳本應(yīng)能在進(jìn)行比較之前自動(dòng)跳過輸入中的空白字符。下面即是一個(gè)完成此功能的Perl程序片段。
$user_input =~ s/\\\\s//;
#Remove white space by replacing it with an empty string
if ($user_input eq $secret_Word) {
#Match! }
最后,可以更進(jìn)一,讓CGI腳本能處理盡可能多的不同的輸入表單。盡管不可能預(yù)期到可能發(fā)送給CGI程序的所有內(nèi)容,但對(duì)某個(gè)特定方面一般經(jīng)常有幾種常用的方式,因而可以逐個(gè)檢查。
例如,僅僅因?yàn)樗鶎懙谋韱问褂肞OST方法向CGI腳本提交數(shù)據(jù),并不意味著數(shù)據(jù)必須按那種方法進(jìn)來。應(yīng)該檢查REQUEET_METHOD環(huán)境變量來確定是使用了GET還是POST方法并相應(yīng)地讀取數(shù)據(jù),而不是假定數(shù)據(jù)都是來自預(yù)期的標(biāo)準(zhǔn)輸入(stdin)。一個(gè)真正編寫成功的CGI腳本能接收無論使用什么方法提交的數(shù)據(jù)并在處理過程中很安全。以下程序清單即是用Perl編寫的一個(gè)例子。
程序清單 CGI_READ.PL 一個(gè)充滿活力的讀取格式輸入的程序
#Takes the maximum length allowed as a parameter
#Returns 1 and the raw form data,or "0" and the error text
sub cgi_Read
{
local($input_Max)=1024 unless $input_Max=$_[0];
local($input_Method)=$ENV{\\\'REOUEST_METHOO\\\');
#Check for each possible REQUEST_METHODS
if ($input_Method eq "GET") {
#"GET"
local($input_Size)=length($ENV{\\\'QUERY_STRING\\\'});
#Check the size of the input
if($input_Size>$input_Max) {
return(0,"input too big"); }
#Read the input from QUERY_STRING
return(1,$ENV{\\\'QUERY_TRING\\\'}); }
elsif ($input_Method eq "POST") {
#"POST"
local($input_Size)=$ENV{\\\'CONTENT_LENGTH\\\'};
local($input_Data);
#Check the size of the input
if ($input_Size>$input_Max) {
return(0,"Input too big"); }
#Read the input from stdin
unless (read(STDIN,$input_Data,$input_Size)) {
return(0,"Could not read STDIN"); }
return(1,$Input_Data);
}
#Unrecognized METHOD
return (0,"METHOD not GET POST");
}
總而言之,腳本應(yīng)該不對(duì)接收的表單數(shù)據(jù)進(jìn)行假設(shè),應(yīng)盡可能預(yù)計(jì)意料之外的情形并正確地處理不正確的或錯(cuò)誤的輸入數(shù)據(jù)。在使用數(shù)據(jù)之前應(yīng)按盡可能多的方式測(cè)試它;拒絕不合理的輸入并打印一條錯(cuò)誤消息;如果某項(xiàng)出錯(cuò)或漏了應(yīng)自動(dòng)選擇一個(gè)缺省值;甚至可以試圖對(duì)輸入進(jìn)行編碼以成為程序的合理的輸入。選擇哪種方式依賴于自己想花費(fèi)多少時(shí)間和精力,不過記住永遠(yuǎn)也不要盲目接收傳給CGI腳來的所有信息。
2.5不要相信路徑數(shù)據(jù)
用戶能修改的另一類型數(shù)據(jù)是PATH_INTO的服務(wù)器環(huán)境變量。該變量由CGI URL中緊跟在腳本文件名之后的任何路徑信息填充的。例如,如果foobar.sh是一個(gè)CGl shell腳本,那么當(dāng)foobar.sh運(yùn)行時(shí),URL http://www.server.com/cgi-bin/foobar.sh/extra/path/info將導(dǎo)致/extra/path/info被放進(jìn)PATH_INFO環(huán)境變量中。
如果使用這個(gè)PATH_INFO環(huán)境變量,就必須小心地完全驗(yàn)證它的內(nèi)容。就像表單數(shù)據(jù)能以許多種方式被修改一樣,PATH_INFO也可以修改。盲目地根據(jù)PATH_INFO的中指定的路徑文件進(jìn)行操作的CGI腳本可能會(huì)讓惡意的用戶對(duì)服務(wù)器造成傷害。
例如,如果某個(gè)CGI腳來設(shè)計(jì)用于簡(jiǎn)單地打印出PATH_INFO中引用的文件,那么編輯該CGI URL的用戶就可以讀取機(jī)器上的幾乎所有文件,如下所示:
#!/bin/sh
#Send the header
echo "Conext-type:text/html"
echo""
#Wrap the file in some HTML
#!/bin/sh
echo"<HTML><HEADER><TITLE>File</TITLE><HEADER><BODY>"
echo"Here is the file you requested:<PRE>\\n"
cat $PATH_INFO
echo "</PRE></BODY><HIML>"
盡管在用戶只單擊預(yù)定義的鏈接(即http://www.server.com/cgi-bin/foobar.sh/public/faq.txt)時(shí),該腳本正常工作,但是一個(gè)更有創(chuàng)造性的(或惡意的)用戶可能會(huì)利用它接收服務(wù)器上的任何文件。如果他想進(jìn)入http://www.server.com/cgi-bin/foobar.sh/etc/passwd,前面的腳本會(huì)很高興地返回機(jī)器的口令文件——這可是不希望發(fā)生的事。
另一種安全得多的方式是在可能時(shí)使用PATH_TRANSLATED環(huán)境變量。不是所有的服務(wù)器都支持該變量,所以腳本不能依賴于它。不過如果有的話,它能提供完全修飾的路徑名,而不是像PATH_INFO提供的相對(duì)URL。
不過在某種情形下,如果在CGI腳本中使用PATH_TRANSLATED的話,則可以訪問通過瀏覽器不能訪問到的文件。應(yīng)該知道這點(diǎn)及它的應(yīng)用。
在大部分UNIX服務(wù)器上,htaccess文件可以位于文檔樹的每個(gè)子目錄,負(fù)責(zé)控制誰能夠訪問該目錄中的特殊文件。例如它可以用于限制一組Web頁面只給公司雇員看。
雖然服務(wù)器知道如何解釋.htaccess,從而知道如何限制誰能還是不能看這些頁面,CGI腳本卻不知道。使用PATH_TRANSLATED訪問文件樹中任意文件的程序有可能碰巧覆蓋了服務(wù)器提供的保護(hù)。
無論使用PATH_INFO還是PATH_TRANSLATED,另一個(gè)重要的步驟是驗(yàn)證路徑以確保它或者是一個(gè)真正的相對(duì)路徑或者是腳本認(rèn)可的幾個(gè)準(zhǔn)確的、預(yù)知的路徑之一。對(duì)于預(yù)定的路徑,腳本將簡(jiǎn)單地將提供的數(shù)據(jù)與認(rèn)可可以使用的文件的內(nèi)部清單進(jìn)行比較,這就是說在增加文件或修改路徑時(shí)必須重新編譯腳本,但安全性卻有了保障。只允計(jì)用戶選擇幾個(gè)預(yù)定義的文件而不允許用戶指定實(shí)際的路徑和文件名。
下面是處理訪問者提供的路徑時(shí)應(yīng)遵循的一些規(guī)則。
1)相對(duì)路徑不以斜線開頭。斜線意味著"相對(duì)于根"或絕對(duì)路徑。如果有的話,CGI腳本也是很少需要訪問Web根之外的數(shù)據(jù)。這樣它們使用的路徑就是相對(duì)于Web根目錄,而不是絕對(duì)路徑。應(yīng)拒絕任何以斜線開始的內(nèi)容。
2)在路徑中單個(gè)點(diǎn)(.)和兩個(gè)點(diǎn)(..)的序列也有特殊含義。單點(diǎn)意味著對(duì)"對(duì)于當(dāng)前目錄",而雙點(diǎn)意味著"相對(duì)于當(dāng)前目錄的父目錄"。聰明的黑客可以建立象../../../etc/passwd這樣的串逆向三層,然后向下進(jìn)入/etc/passwd文件。應(yīng)拒絕任何包含雙點(diǎn)序列的內(nèi)容。
3)基于NT服務(wù)器使用驅(qū)動(dòng)器字母的概念來引用磁盤卷。包含對(duì)驅(qū)動(dòng)器的引用的路徑都以一個(gè)字母加上一個(gè)冒號(hào)開頭。應(yīng)拒絕任何以冒號(hào)為第二個(gè)字符的內(nèi)容。
4)基于NT的服務(wù)器還支持Univesal Naming Conventions(UNC)引用。一個(gè)UNC文件規(guī)格指定機(jī)器名和一個(gè)共享點(diǎn),其余部分與指定機(jī)器上的指定的共享點(diǎn)有關(guān)。UNC文件規(guī)格總是以兩個(gè)反斜線開頭。應(yīng)拒絕任何UNC路徑。
2.6一切看起來都正常,不過…
現(xiàn)在已經(jīng)知道了用戶能給CGI腳本提供非預(yù)期的數(shù)據(jù)的幾種方式以及如何對(duì)付它們了,余下的更大問題是如何驗(yàn)證用戶提交的合法數(shù)據(jù)。
大部分情況下,正確但聰明地編寫的表單提交會(huì)導(dǎo)致比越界數(shù)據(jù)更多的問題。忽略無意義的輸入很容易,但確定合法的、正確格式的輸入會(huì)不會(huì)導(dǎo)致問題就要困難得多。因?yàn)镃GI腳本非常靈活,幾乎可做計(jì)算機(jī)能做的任何事情,所以安全方面的一個(gè)很小失誤往往能被無限制地加以利用——而這正是最危險(xiǎn)的地方。
2.7 處理文件名
文件名是提交給CGI腳本的簡(jiǎn)單數(shù)據(jù),但如果不小心的話,卻能導(dǎo)致許多麻煩。如果用戶輸入的名字中包含路徑因素,如目錄斜杠和雙點(diǎn),盡管期望的是輸入一個(gè)簡(jiǎn)單的文件名--例如file.txt--但結(jié)果卻可能是/file.txt或../../../file.txt。根據(jù)Web服務(wù)器的安裝以及對(duì)提交的文件名做什么操作,系統(tǒng)中的所有文件就有可能都暴露給了一個(gè)聰明的黑客。
進(jìn)一步,如果用戶輸入了一個(gè)已有文件的名字或者一個(gè)對(duì)系統(tǒng)的運(yùn)行很重要的文件名,怎么辦?對(duì)如果輸入的名字是/etc/passwd或C:\\WINNT\\SYSTEM32\\KRNL32.DLL怎么辦?根據(jù)在CGI腳本中對(duì)這些文件進(jìn)行什么操作,它們有可能被發(fā)送給用戶或者被垃圾覆蓋了。在Windows 95和Windows NT下,如果不檢查反斜杠字符(\\),可能會(huì)允許Web 瀏覽器通過UNC文件名訪問甚至不在該Web機(jī)器上的文件。
如果用戶在文件名中輸入了不合法的字符怎么辦?在UNIX下,任何以句點(diǎn)(.)開頭的文件名都是不可見的。在Windows下斜杠(/)和反斜杠(\\)都是目錄分隔符。很可能不小心寫了一個(gè)Perl程序,當(dāng)文件名以管(pipe)(|)開頭時(shí),盡管自己以為僅僅是打開了一個(gè)文件,實(shí)際上卻是執(zhí)行了一個(gè)外部程序。如果用戶知道怎么辦的話,甚至可以把控制字符(例如Escape鍵或Return鍵)作為文件名的一部分送給腳本。
更壞的情況是,在shell腳本中,分號(hào)用于結(jié)束一條命令并開始另一條命令。如果腳本設(shè)計(jì)目的是cat用戶輸入的文件,用戶可能輸入file.txt;rm-rf/作為文件名,導(dǎo)致返回fi1e.txt,然后清除整個(gè)硬盤而不經(jīng)任何確認(rèn)。
2.8 輸入合理,輸出卻不合理
為了避免所有這些問題,關(guān)閉由它們打開的所有安全縫隙,檢查用戶輸入的每個(gè)文件名。必須確保輸入正是程序預(yù)期的輸入。
這樣做的最好辦法是將輸入的文件名的每個(gè)字符與可接收字符的清單進(jìn)行比較,如果不匹配就返回一個(gè)錯(cuò)誤。這比維持一個(gè)所有合法字符的清單并比較它們要安全得多——要想讓什么字符溜掉太容易了。
以下程序清單是用Perl如何完成這種比較的例子。它允許任何字符字母(大寫或小寫調(diào))、任何數(shù)字、下劃線和句點(diǎn)。它還進(jìn)行檢查以確保文件名不以句點(diǎn)開頭。這樣,該段代碼就不允許可以改變目錄的斜杠,不允許可以將多條命令放在一行的分號(hào),或者破壞Perl的Open()調(diào)用的Pipes了。
程序清單 保證所有字符都是合法的
if (($file_Name =~ /[^a-zA-Z_\\.]/) || ($file_Name =~ /^\\./)) {
#File name contains an illegal characgter or starts with a period
}
警告
盡管上述程序清單中的代碼清除了大部分不合法的文件名,但操作系可能還有一些限制,而該代碼沒有覆蓋到。例如,文件名可以用數(shù)字開頭嗎?或者以下劃線開頭?如果文件中包含多個(gè)句點(diǎn)或者句點(diǎn)后多于三個(gè)字符怎么辦?整個(gè)文件名足夠短得能滿足文件系統(tǒng)的限制嗎?
必須不斷向自己提出這種問題。在寫CGI腳本時(shí)最危險(xiǎn)的事是認(rèn)為用戶會(huì)遵守指令。其實(shí)用戶是不會(huì)的。保證用戶不犯錯(cuò)誤是編程者自己的事。
2.9 處理HTML
另外一種看起來無害的但卻能導(dǎo)致很大麻煩的輸入是在請(qǐng)求用戶輸入文本信息時(shí)得到的HTML。以下的程序清單是一個(gè)Perl程序片段;它向任何在$user_Name變量中輸入了一個(gè)名字的人,例如John Smith,發(fā)出問候信息。
程序清單 發(fā)出定制的問候腳本
print ("<HTML><TITLE>Greetings!<TITLE><BODY>\\n");
print ("Hello,$user_Name! It\'s good to see you!\\n");
print ("</BODY><HTL>\\n");
想像一下,如果用戶不是僅僅輸入一個(gè)名字,而是輸入了<HR><H1><P ALIGN="CENTER">John Smith</P><H1><HR>或想像一下當(dāng)腳本希望得到用戶名時(shí),黑客輸入了<IMG SRC="/secret/cutekid.gif">,結(jié)果是公開了本該保密的信息。允許輸入HTML可能很危險(xiǎn)。
比輸入簡(jiǎn)單的HTML修改頁面或訪問畫面更危險(xiǎn)的是惡意的黑客可能輸入一條服務(wù)器端的include指令。如果web服務(wù)器設(shè)置為服從服務(wù)器端include,用戶就可以輸入
<!--#include file="/secret/project/p1an.txt"-->
而不是他的名字,以便看到秘密計(jì)劃的全部文本,或者用戶可以輸入<!--#inc1ude fi1e-"/etc/passwd"-->來獲取機(jī)器的口令文件??赡茏顗牡那闆r是黑客可能輸入<!--#exec cmd="rm-rf/"-->而不是他的名字。這樣上述程序清單中的代碼會(huì)刪掉硬盤上幾乎所有內(nèi)容。
警告
由于經(jīng)常被惡意地使用,服務(wù)器端的include經(jīng)常被禁止使用以保護(hù)站點(diǎn)免受侵害?,F(xiàn)在假定這些都沒問題。即使關(guān)閉了服務(wù)器端的include并且不介意用戶能看到自己硬盤上的任何圖片或者改變頁面顯示的外觀,也仍然有問題--不僅是針對(duì)編程者的,而且針對(duì)其他用戶。
CGI腳本的一個(gè)通常用途是留名冊(cè)(guestbook):訪問站點(diǎn)的顧客可能簽個(gè)名,讓別人知道他們已經(jīng)在那兒了。一般情況下用戶簡(jiǎn)單地輸入他的名字,該名字會(huì)在訪問者清單中出現(xiàn)。但是,如果將The last signee!<FORM><SELECT>作為用戶名輸入怎么辦?<SELECT>標(biāo)記將導(dǎo)致Web瀏覽器忽略位于<SELECT>和一個(gè)不存在的</SELECT>之間的所有內(nèi)容,包括以后清單中加入的任何名字。即使有10個(gè)人簽了名,僅有前3個(gè)會(huì)顯示出來,因?yàn)榈谌齻€(gè)名字包含一個(gè)<FORM>和一個(gè)<SELECT>標(biāo)記。因?yàn)榈谌齻€(gè)簽名者在他的名字中使用了HTML標(biāo)記,他后面的任何名字都不會(huì)顯示出來。
對(duì)于用戶輸入HTML而不是普通的文本的情況有兩種解決辦法:
1)快速但比較粗糙的辦法是不允許小于號(hào)(<)和大于號(hào)(>),因?yàn)樗蠬TML標(biāo)記必須包含在這兩個(gè)字符中,所以清除它們(或者如果碰到它們就返回一個(gè)錯(cuò)誤)是一種防止HTML被提交并返回的簡(jiǎn)單的辦法。下面一行Perl代碼簡(jiǎn)單地清除了這兩個(gè)字符:$user_Input=~s/<>//g;
2)更精細(xì)一點(diǎn)的辦法是將這兩個(gè)字符轉(zhuǎn)換成它們的HTML換碼--—種特殊的代碼,用于表示每個(gè)字符而不使用該字符本身。下面的代碼通過全部用<替換了小于符號(hào),用>替換了大于符號(hào),從而完成了轉(zhuǎn)換:
$user_Input=~s/</&1t;/g;
$user_Input=~s/>/>/g;
2.10 處理外部進(jìn)程
最后,CGI腳本如何與帶有外部過程的用戶輸入打交道是應(yīng)該警惕的另一區(qū)域。因?yàn)閳?zhí)行一個(gè)位于自己的CGI腳本之外的程序意味著無法控制它做什么,必須盡最大努力在執(zhí)行開始前驗(yàn)證發(fā)送給它的輸入。
例如,shell腳本經(jīng)常錯(cuò)誤地將一個(gè)命令行程序和表單輸入合在一起執(zhí)行。如果用戶輸入符合要求,一切都挺正常,但是有可能會(huì)加入其它命令并非法執(zhí)行。
下面即是一個(gè)產(chǎn)生了這種錯(cuò)誤的腳本的例子:
FINGER_OUTPUT=\'finger$USER_INPUT\'
echo $FINGER_OUTPUT
如果用戶很禮貌地給finger輸入了某人的e-mail地址,一切都會(huì)正常工作,但是如果他輸入了一個(gè)e-mail地址,后面再跟一個(gè)分號(hào)和另一條命令,那么該命令也會(huì)被執(zhí)行,如果用戶輸入了webmaster@www.server.com;rm-rf/,那麻煩可就大了。
即使沒有什么隱藏的命令被加入用戶數(shù)據(jù),無意的輸入錯(cuò)誤也可能帶來麻煩。例如,下面的代碼行會(huì)產(chǎn)生一個(gè)意料之外的結(jié)果——列出目錄中的所有文件——如果用戶輸入是一個(gè)星號(hào)的話。
echo "Your input:"$USER_INPUT
當(dāng)通過shell發(fā)送用戶數(shù)據(jù)時(shí),就象前面的代碼片段所做的那樣,最好檢查一下shell的meta-character(元字符)——這些可能會(huì)導(dǎo)致意外的行為。
這些字符包括分號(hào)(允許一行中有多條命令),星號(hào)和問號(hào)(完成文件匹配),感嘆號(hào)(在csh下指運(yùn)行的作業(yè)),單引號(hào)(執(zhí)行一條包含其中的命令)等等。就像過濾文件名一樣,維護(hù)一個(gè)允許的字符清單一般要比試圖找出每個(gè)不允許的字符容易一些。下面的Perl代碼片段驗(yàn)證一個(gè)e-mail地址:
if ($email_Address ~= /[^a-zA-z0-9_\\-\\+\\@\\.]) {
#lllegal character! }
else { system("finger $email_Address"); }
如果決定在輸入中允許shell元字符,也有辦法讓它們安全一些。盡管可以簡(jiǎn)單地給未驗(yàn)證的用戶輸入加上引號(hào)以免shell按特殊字符進(jìn)行操作,但這實(shí)際上不起什么作用。請(qǐng)看下的語句:
echo"Finger information:<HR><PRE>"
finger"$USER_INPUT
echo"</PRE>
盡管$USER_INPUT上的引號(hào)可以使shell不再解釋一個(gè)分號(hào),從而不允許黑客簡(jiǎn)單地插進(jìn)來一條命令,但該腳本仍有許多安全方面的漏洞。例如,輸入可能是\'rm-rf/\',其中單引號(hào)可以導(dǎo)致甚至在finger不知道的情況下執(zhí)行黑客的命令。
一種處理特殊字符的較好的辦法是對(duì)它們進(jìn)行換碼,這樣腳本只是取它們的值而不解釋它們。通過對(duì)用戶輸入進(jìn)行換碼,所有的shell元字符都被忽略并作為增加的數(shù)據(jù)傳給程序。下面的Perl代碼即對(duì)非字母數(shù)字字符完成這種處理。
$user_Input=~s/([^w])/\\\\\\1/g;
現(xiàn)在,如果用戶輸入加在某條命令之后,每個(gè)字符——即便是特殊字符——都會(huì)由shell傳送給finger。
不過請(qǐng)記住,驗(yàn)證用戶輸入——不相信發(fā)送給自己的任何信息——會(huì)使自己的代碼更易讀并且執(zhí)行起來更安全。最好不是在已經(jīng)執(zhí)行了命令之后再去對(duì)付黑客,而應(yīng)在門口就對(duì)數(shù)據(jù)進(jìn)行一次性的檢查。
--------------------------------------------
處理內(nèi)部函數(shù)
對(duì)于解釋型語言,例如Shell和Perl,如果用戶輸入的數(shù)據(jù)不正確的話,有可能導(dǎo)致程序生成本來沒有的錯(cuò)誤。如果用戶數(shù)據(jù)被解釋為一部分執(zhí)行代碼,用戶輸入的任何內(nèi)容都必須符合語言的規(guī)則,否則就會(huì)出錯(cuò)。
例如,下面的Perl代碼片段也許會(huì)正常工作也許會(huì)產(chǎn)生錯(cuò)誤,這取決于用戶輸入的是什么:
if ($search_Text =~ /$user_Pattern/) {
#Match! }
如果$user_Pattern是一個(gè)正確的表達(dá)式,一切都會(huì)正常,但是如果$user_Pattern不合法;Perl就會(huì)失敗,導(dǎo)致CGI程序失敗——這可能是一種不安全的方式。為了避免這種情況,在Perl中至少應(yīng)有eval()操作符,它計(jì)算表達(dá)式的值并與執(zhí)行它無關(guān),返回一個(gè)碼值表示表達(dá)式是有效的還是無效的。下面的代碼即是前面代碼的改進(jìn)版。
if (eval{$search_Text =~ /$user_Pattern/}) {
if ($search_Text =~ /$user_Pattern/) {
#Match!
}
}
不幸的是,大部分shells(包括最常用的,/bin/sh)都沒有像這樣的簡(jiǎn)單的辦法檢查錯(cuò)誤,這也是避免它們的另一原因。
--------------------------------------------
在執(zhí)行外部程序時(shí),還必須知道傳送給那些程序的用戶輸入是如何影響程序的。編程者可以保護(hù)自己CGI腳本不受黑客侵犯,但是如果輕率地將某個(gè)黑客輸入的內(nèi)容傳送給了外部程序而不知道那些程序是如何使用這些數(shù)據(jù)的,也會(huì)徒勞無益。
例如,許多CGI腳本會(huì)執(zhí)行mail程序給某人發(fā)送一個(gè)包含用戶輸入信息的e-mail。這可能會(huì)非常危險(xiǎn),因?yàn)閙ail有許多內(nèi)部命令,任何一個(gè)命令都有可能被用戶輸入激活。例如,如果用mail發(fā)送用戶輸入的文本而該文本有一行以代字號(hào)(~)開頭,mail會(huì)將該行的下一字符解釋為它能執(zhí)行的許多命令之一。例如,~r/etc/passwd,會(huì)導(dǎo)致mail讀取機(jī)器的口令文件并發(fā)送給收信人(也許正是黑客自己)。
在這樣的例子中,應(yīng)該使用sendmail(一個(gè)更底層的郵寄程序,它少了許多mail的特性),而不是使用mail在UNIX機(jī)器上發(fā)送e-mail。
作為一般規(guī)則,在執(zhí)行外部程序時(shí)應(yīng)該使用盡可能貼近自己要求的程序,不必有過多不必要的功能。外部程序能干的事越少,它被利用來干壞事的機(jī)會(huì)就越少。
警告
下面是使用mail和sendmail的另一個(gè)問題:必須保證發(fā)送給mail系統(tǒng)的是一個(gè)合法的e-mail地址。許多mail系統(tǒng)都會(huì)把以"|"開頭的e-mail地址作為要執(zhí)行的命令,從而為輸入這樣一個(gè)地址的黑客打開方便之門,請(qǐng)?jiān)僖淮斡涀∫?yàn)證數(shù)據(jù)。
怎樣才能更好地了解外部程序以便有效地使用它們的另一個(gè)例子是grep。grep是一個(gè)簡(jiǎn)單的命令行實(shí)用程序,它在文件中搜索一個(gè)常用表達(dá)式,表達(dá)式可以是一個(gè)簡(jiǎn)單的串也可以是復(fù)雜的字符序列。大部分人會(huì)說使用grep不會(huì)出什么問題,但是盡管grep可能不會(huì)造成什么損失,它卻能被愚弄,下面將說明它是怎么被愚弄的,如下面的代碼所示。它假定在許多文件中完成對(duì)用戶輸入項(xiàng)的區(qū)分大小寫的搜索。
print("The following lines contain your term:<HR><PRE>");
$search_Term=~s/([^w])/\\\\\\1/g;
system("grep $search_Term/public/files/*.txt");
print(<"PRE>");
這一切看起來挺好,除非考慮到用戶可能會(huì)輸入-i。它不會(huì)被搜索,而是作為與grep的切換,就像任何以連字符開頭的輸入一樣。這會(huì)導(dǎo)致grep或者因等待將搜索的串輸入標(biāo)準(zhǔn)輸入而掛起,或者如果-i后的內(nèi)容被解釋為其他切換字符時(shí)產(chǎn)生錯(cuò)誤。毫無疑問這不是編程者本來的意圖。在這種情況下它還不太危險(xiǎn),但在其他情況下卻有可能。記住,沒有什么無害的命令,對(duì)每條命令部必須從各個(gè)角度仔細(xì)考慮。
一般情況下,應(yīng)該盡可能熟悉自己的CGI腳本執(zhí)行的每個(gè)外部程序。對(duì)程序知道得越多,就越能保護(hù)它們免受數(shù)據(jù)破壞--一方面可以監(jiān)視數(shù)據(jù),另一方面可以禁止某些選項(xiàng)或特性。外部程序經(jīng)常是許多CGI程序問題的一種快速方便的解決辦法——它們都經(jīng)過了測(cè)試,可以得到,并且靈活多樣。但它們也可以成為黑客入侵的方便之門。不要害怕使用外部程序——它們經(jīng)常是完成CGI程序中某種功能的唯一辦法——但是要知道它們可能帶來的危害。
3 內(nèi)部傷害
到目前為止,僅僅考慮了通過Web例覽站點(diǎn)的人——從幾千里之外——可能帶來的潛在的安全危險(xiǎn)。但實(shí)際上還存在另一種離得更近的危險(xiǎn)因素。
在CGI安全問題上常犯的一種錯(cuò)誤是忘記了本地用戶。盡管通過Web瀏覽站點(diǎn)的人不影響本地安全,如文件保護(hù)和所有者,但Web服務(wù)器的本地用戶卻能這樣,必須做出更多努力防止這些入侵。大部分多用戶系統(tǒng)上,如UNIX,Web服務(wù)器是作為一個(gè)程序運(yùn)行的,而機(jī)器仍被許多人使用做著許多事情。僅僅因?yàn)闉槟橙伺c自己一起工作或訪問自己的學(xué)并不意味著他能抵制住誘惑,而不去搗鼓Web安裝從而引起問題。
3.1 CGI腳本用戶
大部分Web服務(wù)器是作為運(yùn)行CGI腳本的特殊用戶而安裝的。這是在CGI程序運(yùn)行時(shí)擁有該CGI程序的用戶,并且他所擁有的權(quán)限能限制該腳本能做什么事情。
在UNIX下,服務(wù)器自己也是作為root(系統(tǒng)的超級(jí)用戶或管理員)運(yùn)行的,并允許它使用端口80作為瀏覽器與之通信的地方(只有root能使用這些被稱為"保留的"端口0到1023;所有用戶都可以使用其余的端口)。當(dāng)服務(wù)器執(zhí)行CGI程序時(shí),大部分Web服務(wù)器都能設(shè)置為以另外一個(gè)用戶而不是Web服務(wù)器本身來運(yùn)行該程序——盡管不是所有服務(wù)器都能這么做。
將CGI腳本作為root運(yùn)行是很危險(xiǎn)的!服務(wù)器應(yīng)被設(shè)為利用一個(gè)普通用戶,如常用的nobody來運(yùn)行CGI腳本。用戶權(quán)限越小,運(yùn)行的CGI腳本能造成的危害就越小。
3.2 Setuid 危險(xiǎn)
編程者還應(yīng)知道自己的UNIX CGI腳本中是否設(shè)置Setuid位。如果對(duì)某個(gè)可執(zhí)行文件允許該選項(xiàng),將能使該程序與擁有該文件的用戶有同樣權(quán)限,而不是執(zhí)行它的用戶。如果自己的CGI腳本上設(shè)置setuid位,無論服務(wù)器作為什么用戶來運(yùn)行它,它的權(quán)限都等同于該文件的擁有者。這當(dāng)然有很大的隱患--可能會(huì)對(duì)以其權(quán)限運(yùn)行腳本的用戶失去控制。幸運(yùn)的是Setuid位很容易被禁止。對(duì)所有CGI腳本執(zhí)行chmod a-s即能關(guān)閉所有的setuid,程序即能以允許的權(quán)限運(yùn)行。
當(dāng)然,在某些情況下也許希望設(shè)置setuid位--例如如果腳本需要以特殊用戶身份來運(yùn)行以訪問一個(gè)數(shù)據(jù)庫。在這種情況下,必須加倍小心確保該程序的其他文件保護(hù)能將可以訪問它的用戶限制在允許范圍內(nèi)。
3.3 "Community" Web服務(wù)器
即使Web服務(wù)器以一個(gè)常用的用戶來執(zhí)行腳本,仍有一個(gè)潛在的問題,那就是一個(gè)人并不總是能控制服務(wù)器。如果許多人共同控制服務(wù)器,每個(gè)人都可以將CGI腳本安裝作為nobody用戶來運(yùn)行。這就使這些人的任何一個(gè)都可以利用CGI程序訪問他們?cè)炔荒茉L問的地方,而這些地方是nobody允許進(jìn)入的。
也許潛在的安全問題的解決辦法是將CGI的控制限制為一個(gè)人。在某些情況下盡管這似乎是合理的,但對(duì)較大站點(diǎn)卻經(jīng)常不太可能。例如,一個(gè)大學(xué)有幾百個(gè)學(xué)生,每個(gè)學(xué)生都想試著去編寫并安裝CGI腳本。
3.4 使用CGI Wrap
當(dāng)有多個(gè)用戶可以訪問CGI時(shí),對(duì)于確定腳本以什么用戶運(yùn)行的問題的一個(gè)較好的解決辦法是CGI wrap程序。CGI Wrap,可以在using CGI Web站點(diǎn)中找到,是一個(gè)簡(jiǎn)單的包裝,它以擁有該文件的用戶而不是服務(wù)器指定的用戶來運(yùn)行CGI腳本。這種簡(jiǎn)單的預(yù)防措施使腳本擁有者對(duì)它可能的危害負(fù)責(zé)。
因?yàn)镃GI wrap使得CGI腳本的作者負(fù)責(zé)他們自己的腳本權(quán)限,所以它不僅是一個(gè)保護(hù)其他人擁有的重要文件的有力的工具,而且是促使人們編寫安全的腳本的有力的工具。只有他們自己的文件會(huì)處于危險(xiǎn)之中,這樣的現(xiàn)實(shí)對(duì)腳本作者會(huì)是極大的促進(jìn)。
3.5 CGI腳本權(quán)限
還應(yīng)該清楚了解CGI腳本被哪個(gè)用戶擁有以及腳本自身的文件權(quán)限。包含腳本的目錄的權(quán)限也非常重要。
例如,如果Web服務(wù)器上的cgi-bin目錄是所有人可寫的,那任何本地用戶將能刪除CGI腳本并用另一個(gè)來代替。如果腳本本身是所有人可寫的話,那么任何人將能修改腳本完成任何事情。
請(qǐng)看下面這段無害的UNIX CGI腳本:
#!/bin/sh
#Send the header
echo"Content-type:tex/html"
echo""
#Send some HTML
echo "<HTML><HEADER><TITLE>Fortune</1TLE><HEADER>
echo "<Body>Your fortune:<HR><PRE>
forune
echo"</BODY><HIML>"
現(xiàn)在,如果腳本上設(shè)置的權(quán)限允許某個(gè)惡意的用戶將程序改變?nèi)缦?
#!/bin/sh
#Send the header
echo "content-type:text/html"
echo""
#Do some damage!
rm-rf/
echo"<HTML><TITLE>Got you! <TITLE><BODY>"
echO"<H1>Ha ha!<H1></BODY></HTML>"
那么下一個(gè)在Web上訪問該腳本的用戶即使他沒做什么壞事也會(huì)導(dǎo)致大量問題。在Web上檢查用戶輸入的完整性很重要,但更重要的是保證腳本本身未被修改且不能被修改。
3.6 本地文件安全
腳本在本地硬盤上創(chuàng)建的文件的完整性也同樣重要。在得到Web用戶輸入的一個(gè)合理的文件名之后,使用該文件名干什么也很重要。根據(jù)Web服務(wù)器運(yùn)行的操作系統(tǒng),權(quán)限和擁有者信息可以與文件中的數(shù)據(jù)一起存在文件上。
例如,UNIX系統(tǒng)能記錄文件訪問權(quán)限,包括創(chuàng)建該文件的用戶的權(quán)限、同組用戶的權(quán)限、以及系統(tǒng)其他人的權(quán)限。windows NT使用的是一個(gè)更復(fù)雜訪問控制清單系統(tǒng),但完成的功能大致相同。根據(jù)這些標(biāo)志如設(shè)置以及授予或禁止什么權(quán)限,Web服務(wù)器機(jī)器的用戶也可能引起麻煩。
例如,在創(chuàng)建一個(gè)文件時(shí)就應(yīng)知道給它設(shè)置的權(quán)限。大部分Web服務(wù)器軟件將umask或權(quán)限碼設(shè)為0000,意味著可以創(chuàng)建一個(gè)任何人可讀寫的文件。盡管文件上的權(quán)限設(shè)置對(duì)在Web上瀏覽的人可能沒什么不同,但本地訪問的用戶卻能利用不嚴(yán)格的權(quán)限設(shè)置造成危害?;谶@種現(xiàn)實(shí),應(yīng)該盡可能嚴(yán)格地限制文件權(quán)限。
保證每個(gè)打開文件的調(diào)用都有一個(gè)最小限制集合的最簡(jiǎn)單的辦法是設(shè)置腳本的umask。umask()是一個(gè)UNIX調(diào)用,它能對(duì)每個(gè)后續(xù)的文件創(chuàng)建限制權(quán)限。umask()的參數(shù)是一個(gè)數(shù)字,用于對(duì)后續(xù)的文件創(chuàng)建的權(quán)限碼進(jìn)行屏蔽。如果umask為0022,則不管在打開文件時(shí)給組用戶和其他用戶賦予了什么顯式的權(quán)限,都將導(dǎo)致創(chuàng)建的文件僅能被用戶自己寫。即使已經(jīng)設(shè)置了umask,創(chuàng)建文件時(shí)也應(yīng)該顯式指定權(quán)限。如果只有CGI腳本能訪問文件,那么只有運(yùn)行CGI程序的用戶才能訪問該文件——權(quán)限為0600。如果另一個(gè)程序需要訪問該文件,可以使該程序的擁有者成為與CGI腳本同一組的用戶,這樣只需設(shè)置組用戶權(quán)限——權(quán)限為0660。如果必須讓所有人都能訪問該文件,應(yīng)使該文件只能讀,不能寫——權(quán)限為0644。
3.7 使用顯式路徑
最后,本地用戶還可以最后一種方式攻擊Web服務(wù)器——欺騙服務(wù)器運(yùn)行他寫的一個(gè)外部程序,而不是運(yùn)行在CGI腳本中指定的程序。下面是一個(gè)簡(jiǎn)單的程序,從UNIX的fortune命令可以看出該瀏覽者還比較聰明。
#!/bin/sh
# Send the header
echo"conten_type:text/html"
echo""
#Send the fortune
echo"<HTML><HEADER><TITLE>Fortune</TITLE></HEADER><BODY>"
echo "<You crack open the cookie and the fortune reads:<HR><PRE>"
fortune
echo "</PRE><BODY></HTML>"
該腳本看起來可一點(diǎn)沒有害處。它不接收用戶輸入,所以用戶不能籍此搞什么把戲。因?yàn)樗鼉H由Web服務(wù)器運(yùn)行,所以腳本本身的權(quán)限設(shè)置可以非常嚴(yán)格,可以防止任何有企圖的本地用戶修改它。如果對(duì)該腳本所在的目錄也設(shè)置了正確的權(quán)限的話,看起來就沒什么地方可以出問題了,是不是?
當(dāng)然還有問題。記住得要有點(diǎn)偏激。
上述程序清單調(diào)用了外部程序,在本例中是echo和fortune。因?yàn)檫@些腳本沒有用顯式路徑指明它們?cè)谟脖P上的位置,該shell即使用PATH環(huán)境變量來找到它們,從變量中的每一項(xiàng)查找要執(zhí)行的程序。這可能很危險(xiǎn)。例如,如果fortune程序安裝在/usr/games中,但PATH中在它之前列出了/TMP,那么任何碰巧命名為"fortune"并位于臨時(shí)目錄的程序都會(huì)被執(zhí)行,而不是真正的fortune。
該程序可以做它的創(chuàng)建者想做的任何事情,可以刪除文件,也可以登記有關(guān)請(qǐng)求信息并將數(shù)據(jù)傳給真正的fortune——使用戶和編程者誰也不聰明。在CGI腳本中運(yùn)行外部程序時(shí)一定要指定顯式的路徑。PATH環(huán)境變量有很大作用,但它與其他變量一樣也能被非法使用。
4 使用他人CGI腳本時(shí)的注意事項(xiàng)
關(guān)于CGI,可以從很多地方獲得信息——從Internet上,從學(xué)校圖書館中,從像本書這樣的書中,UseNet組中以及朋友和同事中。從這些地方不僅可以獲得信息,還可以得到實(shí)際的程序和庫。有些程序和庫如果已經(jīng)有人做過了為什么自己還要從頭再做一遍呢?但就像不能盲目聽從別人的意見一樣,關(guān)于如何理財(cái),如何駕車或者生活中的別的方面,同樣,也不能在自己的服務(wù)器上盲目地運(yùn)行另從的代碼。從Net上得到的腳本也可能真正是很好的腳本。但也許并不是。花些時(shí)間考察一下腳本的來源以及獲取它的站點(diǎn)的可*性是值得的。
4.1 追根求源
某些Web擁有者。如果不能看到并研究源代碼的話,他們甚至都不會(huì)運(yùn)行一個(gè)公共的、免費(fèi)的或商業(yè)性的腳本。這可能有點(diǎn)偏激。如果某個(gè)聲譽(yù)很好的公司銷售一個(gè)文檔詳細(xì)且廣為使用的腳本,該腳本應(yīng)該比自己寫的腳本更安全一些。原因有二。首先,專業(yè)人才知道并能避免一些常見的安全漏洞;其次,公司是為了嫌錢而做生意,如果他們以次充好或銷售那些惡意的產(chǎn)品就不能再做生意賺錢了。
從另一方面來看,如果UseNet組中看到一個(gè)編譯好的可執(zhí)行文件出自一個(gè)從沒聽說過的人,沒有什么文檔可以看,也沒有該程序的用戶可以交流交流,那么在將它放入自己的服務(wù)器之前一定要仔細(xì)考慮。也有可能這是來自一個(gè)像自己一樣的另一個(gè)CGI編程者的完全合法的貢獻(xiàn),目的是想讓全世界共享他的編程成果。但它也可能來自某個(gè)惡意的,具有變態(tài)幽默感的,只想看到自己能使多少人清盤的人。
在評(píng)價(jià)公共的免費(fèi)軟件或商業(yè)性軟件時(shí),應(yīng)考慮下面這些方面:
該腳本來自一個(gè)聲譽(yù)好的站點(diǎn)嗎?該站點(diǎn)存在很長(zhǎng)一段時(shí)間了嗎?它維護(hù)得好嗎?Web擁有者在發(fā)布文件前進(jìn)行檢查嗎?
有沒有足夠的文檔說明該程序如何工作以及用戶如何使用等信息?
有多少人已經(jīng)下載了該腳本?該站點(diǎn)愿意提供顧客名單嗎?(僅在有疑問時(shí)才去詢問;Web擁有者不會(huì)整天去回答這類問題。)
有人在UseNet上討論該腳本嗎?如果有,他們說好還是不好?如果沒人提到該腳本可以進(jìn)一步請(qǐng)求別人的見解。一般總會(huì)有人響應(yīng)的。
提示
在評(píng)價(jià)腳本時(shí)檢查下面這些useNet組: comp.security.announce,comp.securiy.unix,以及comp.infosystem.www.authority.cgi。另外還可以訪問位于ftp.cert.org的Computer
Emergency Response Team,以了解安全問題的歷史及有關(guān)工作以及安全保護(hù)的軟件。
5)該腳本的作者有沒有一些別的好名聲的腳本?
6)源代碼能得到嗎?免費(fèi)的或有價(jià)的都行。
7)該程序是不是過份宣傳它的能力?如果是,這可能是一個(gè)編程新手。
8)該站點(diǎn)自己運(yùn)了該腳本嗎?如果沒有,為什么?能找到別的站點(diǎn)運(yùn)行該腳本嗎?過分偏激以及時(shí)間限制
盡管游覽取自Web的所有代碼是個(gè)好主意,但要花費(fèi)很多時(shí)間,特別是當(dāng)代碼比較復(fù)雜時(shí)更是如此。
例如,NCSA HTTPd就太大了,一般用戶不可能一行行去讀,但是從它的主站點(diǎn)http://www.ncsa.uiuc.edu下載它卻能保證極好的完整性,滿足任何用戶的需要。實(shí)際上,任何從NCSA下載的東西都是有保障的。
實(shí)際上,Web上的許多著名的站點(diǎn)已經(jīng)為用戶做了大部分的幾乎偏激的代碼檢查工作。從它閃中下載代碼是可能利用的另一層另一層保護(hù)。這些站點(diǎn)包括:
ftp://ftp.ncsa.uiuc.edu/Web/httpd/Unix/ncsa_httpd/cgi(the NCSA Archive)
http://www.novia.net/~geewhiz(Virtual Webwerx Division Zero-CGI Land)
http://www.lpage.com/cgi(the World-Famous Guestbook Server)
http://sweetbay.will.uiuc.edu/cgi++(cgi++)
http://www.aee.com/wdw(the Web Developers Warehouse)
4.2 注意禮貌
最后,如果確實(shí)希望從Web上下載一些CGI代碼,或者完整地使用它,或者用作自己編寫的更大程序的一部分,還應(yīng)了解一些事情。
代碼是兔費(fèi)的并不意味著可以自由地用它作自己想做的任何事情。通常程序和庫是禁止拷貝的,如果原始作者沒有放棄這個(gè)權(quán)力,他即能限制如何使用該程序。例如,作者可能禁止拆散該腳本,及禁止用作別的腳本的一部分。
一跟來說,在使用別人的代碼之前(即使已經(jīng)確定它是安全的),最好與作者進(jìn)行聯(lián)系取得許可。至少這樣做比較有禮貌。而大部分情況下,作者會(huì)很高興他的代碼能被別人利用。當(dāng)然,如果在自己程序某個(gè)片段處注明原始作者將是很禮貌的。