CGI教學(xué):CGI安全問題(續(xù))
發(fā)布時(shí)間:2008-08-06 閱讀數(shù): 次 來源:網(wǎ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)的地方。