本月的專欄將介紹Perl的稱為“tainting”的內部安全機制,它可以讓Perl捕捉到任何可能導致安全性問題的系統調用。我強烈推薦在你的所有CGI程序中打開“tainting”機制。
CGI使得互聯網上的任何人都可以在你的計算機上運行程序,這就使得CGI成為世界上最流行的安全漏洞。作為程序員,我們的責任是不讓壞人侵入我們的系統,對于我們所編制的程序來說,要做到沒有漏洞可鉆。
例如,下面這個CGI程序,就是個壞程序:
#!/usr/bin/perl -w
# cgi-bad – 一個不好的cgi 腳本的例子
...
$file = param("FILE")
or die "Must fill out the FILE field\n";
unlink("/usr/local/public/data/$file")
or die "Can't delete $file : $!\n";
該腳本所做的是讀出在表單中所輸入的文件名,并從目錄/usr/local/public/data/中刪除該名稱的文件。錯了!該腳本所做的實際上是讓任何用戶對在網絡服務器上usercode可以刪除的任何文件作刪除操作。請看:
% setuid-bad ../../etc/apache/var/userdb
天哪!那里是用戶數據庫!
我們本來要做的是檢查程序的參數,以確定其是否為文件名。問題是你的程序外部所產生的數據用到了系統調用上,如nlink(), open(),和system()。而你并不打算讓在你的程序之外產生的數據影響到外部世界。
Perl有個選項,打開后,可以強迫你檢查常數,環境,輸入,或其它有可能被不懷好意的人利用的漏洞。該選項稱為“tainting”
打開Taint檢查選項
要打開taint檢查選項,讓Perl帶一個 -T 選項:
#!/usr/bin/perl -wT
如果我們在上述程序運行時,帶有 –T選項,我們會看到如下信息:
Insecure dependency in unlink while running with
-T switch at setuid-bad line 5.
Perl跟蹤$file中的值,它是在你的程序外部生成的,(它被稱為“tainted”)。 unlink() 被認為是個不安全的操作,因為它對外部世界有影響:文件。在不安全的操作下,企圖使用沒有信任度的(tainted)數據是危險的。正如我們已經看到的,數據可能有詐。
這些漏洞可以由Perl的taint檢查選項在運行時捕捉到,并且使得程序停下來。
Tainted數據
Tainted 數據來源很多,包括:來源于你的環境散列表 (the %ENV) ,參數 (@ARGV),讀入的文件和目錄,來源于運行的程序中,以及一些系統調用的結果(用getpw讀出口令數據庫中的GECOS域)。任何對tainted值的操作(添加,合并,插入),其結果值也是tainted。這就好像是數據一旦被粘上了污點,那么無論數據傳播到哪里,污點就會被帶到哪里。
僅有三種方式,可以得到“untainted”值:數據直接在程序中指明;數據來自于安全的函數(如localtime);或者使用正則表達式提取來自不安全函數的tainted 串的一部分。
$a = 4; # untainted
$file = $ARGV[0]; # tainted
$file =~ m{^([^/]+)$}
or die "$file is not a good filename.\n";
$untainted = $1; # untainted
通過正則表達式用括號括起來,創建了$1, $2, ... 變量。這些都是untainted數據。通過正則表達式,你可以確信它就是你所期望的值。如果匹配失敗,你會得到失敗信息。如果匹配成功,$1 ...變量包含了你可以使用的untainted 數據。
如果我們已經打開tainting 選項,當我們試圖做unlink()操作時,Perl 解釋器會停下來,告訴你$file 中包含了tainted 數據。文件名是 tainted的,因為它來自于不信任源:使用你的程序的人。
壞動作
如果你所使用的數據是tainted的,你想要Perl程序所做的大多數事情會產生出錯信息。如果文件名或程序名是tainted的,那么運行程序,打開文件來寫入,以及刪除文件,這些操作都將被禁止進行。
這一節將演示如何在這種場合下,解除tainted狀態。
考慮:
system("ls *.h");
Perl 在你的串中看到了 *,并決定調用shell,這樣:
sh -c "ls *.h"
但是,的確有人可能用假的路徑環境變量來運行你的程序,從而導致調用了錯誤的sh或ls。所以,對于PATH變量以及SHELL中可以用來修改其行為的其他變量,應該進行 untaint操作。
一般,運行其它程序時,你應采取三項步驟:
明確你的環境變量,使得運行的是實際程序。
關閉shell
對程序的參數進行untaint操作。
用如下的等簡單方式清除你的環境變量:
delete @ENV{"IFS", "CDPATH", "ENV", "BASH_ENV"};
$ENV{PATH} = "/bin:/usr/bin";
第一行刪除掉可能會引起問題的環境變量,第二行給出一個確保安全的PATH。你可以添加其他的目錄到PATH中,但務必確保它們同該處一樣,是有確定值的。
關閉shell也要把握好分寸。Perl 在涉及到有關shell的操作,如 open(), system(), backticks,和exec() 調用時,有自己的規則,這些規則不太容易掌握。最好的規則是:避免使用backticks 和pipe open() 調用,而是使用system() 和exec() ,并傳給它們參數表。
大多數人習慣于看到如下的寫法:
system("someprogram arg1 arg2 arg3");
他們不知道還可這樣寫:
system("someprogram", "arg1", "arg2", "arg3");
這樣的寫法,可以精確地告訴Perl的各個參數是什么,Perl將不會調用shell。 exec() 也具有讀參數表和不調用shell的特點。而如果要使用piped open() 和backticks,就無法保證不會用到shell。
如果你打算使用piped open 或 backticks,你得用如下的方法重新實現:
$pid = open(COMMAND, "-|");
die "Couldn't fork: $!" unless defined $pid;
if ($pid) {
@lines = <COMMAND>;
close(COMMAND);
} else {
exec("some", "program", "with", "args") or die "execing: $!";
}
一般來說,即使你的PATH已經作了安全處理,給出所運行的程序的完整路徑是個好主意。這就會避免了錯誤地調用了/usr/bin/boom 而不是/home/user/bin/boom這種情況的發生,因為在PATH中 /usr/bin 位于/home/usr/bin/boom.之前。
文件名
對文件名進行操作時,使用unlink() 或 <*.h> ,或者用open()時,是有危險的。
從目錄中讀入的文件名是tainted的。你可以打開一個tainted 文件名來讀入,但你不能打開它來寫入。從文件中讀數據,不管文件名是否 tainted,已經是tainted的。因為用到了shell,你不能用<*.h> 來得到文件清單。
為了檢查文件名是否是好的,你得寫出一個正則表達式,并同合法的文件名進行匹配。在一些場合,可以用如下的簡單方法來檢查你的數據:
$file = $ARGV[0];
($file =~ m{^([^/]+)$} && $file ne "." && $file ne "..")
or die "Bad filename $file\n";
$file = $1;
根據任何不包含斜杠的串的正則表達式來檢查文件名,這就把子目錄排除在外,然后排除掉“.”(當前目錄)和“..”(當前目錄的父目錄)。如果這些測試都通過了,$1變量中存放的就是我們可以使用的文件名。
為了得到匹配某種模式的文件名清單,你既可以從CPAN (File::KGlob 和File::BSD 是兩個有用的模塊)安裝有關模塊,也可以使用讀目錄操作和正則表達式:
opendir(DH, "/path/to/directory") or die "opening directory: $!\n";
while (defined ($thing = readdir(DH))) {
next unless /^(.*\.h)$/;
push(@files, $1);
}
closedir(DH);
# @files is the list of untainted *.h filenames
檢查 Taintedness
如果你需要檢查 taintedness,你可以使用如下技巧:
sub is_tainted {
return ! eval {
join('',@_), kill 0;
1;
};
}
你需要了解兩件事情:kill 0 除了返回“true”之外,什么也不做;如果表達式的部分使用了tainted數據,那么,該表達式是tainted。所以,如果is_tainted調用時使用了tainted數據,對@_ 進行kill,就足以使得Perl 程序die。
Untainting過了頭也會有問題
在不多的場合,盲目地untaint你的數據也產生安全漏洞。所以也此時需要Tainting的存在。如果象下面一樣,盲目地對任何數據都untaint:
$var =~ /(.*)/s; # 愚蠢
$var = $1;
正則表達式中的 /s 符號使得句點可以匹配串中的任何換行符。
通過用 .* 我們匹配了串中的一切符號,并用$1存放該數據的untainted的副本。
正如注釋所說的,這樣做是愚蠢的。
總結
-T 打開tainting選項。來自你程序之外的數據是tainted,不能使用這些數據,以免影響外部世界。
用正則表達式和$1, $2, ... 變量進行untaint。要運行其他程序,設置好path,不要使用shell,并對參數進行untaint。
進一步的閱讀
在perlsec manpage 中詳細闡述了tainting的機制,并給出了較多的例子。Chapter Perl Cookbook的第十六章談了進程管理,演示了non-shell 版的 piped opens和其他有趣的用法
文章來源于領測軟件測試網 http://www.kjueaiud.com/