NAME
perlsec - Perl 安全DESCRIPTION 描述
Perl可以輕鬆寫出安全的程式,即使執行時有特殊許可權,比如setuid或setgid程式。許多指令碼的命令列裡有多項替換語句,Perl卻不是這樣,它使用更多傳統方法而少有艱深。而且,由於perl語言有更多內在功能,它可以更少的依賴於其他(可能不可信的)程式來完成根本目的。當Perl檢測到程式中真實的使用者或組ID與有效使用者或組ID不同時,它自動地開啟一種叫做“汙染模式”特殊的安全性檢測。setuid的unix的許可權位是04000,setgid 的UNIX許可權位是02000;它們都有可能被設定。你也可以用命令列標識 -T 明確地開啟“汙染模式”。強烈建議伺服器程式或者在以其他人身份執行的程式(比如CGI指令碼)使用此識別符號。一旦汙染模式被開啟,它在指令碼的餘下內容中一直開啟。 在“汙染模式”中,Perl使用叫做“汙染檢測”的特殊預防方法來防止明顯的和不易被察覺的陷阱。一些檢測相當簡單,如檢查路徑目錄以確定它們對其他人是不可寫的;小心的程式設計師一向做此類檢測。其他的檢測已經得到Perl本身最好的支援,這些檢測尤其使寫一個set-id的Perl程式比相應的C程式更安全。 你不可以使用來自程式之外的資料來影響程式之外的事情——至少不是偶然的。所有命令列引數,環境變數,本地資訊(參見perllocale),特定系統呼叫的結果(readdir(),readlink(),shmread()的變數,msgrcv()的返回資訊,getpwxxx()呼叫返回的密碼、gcos和shell域)和所有檔案輸入都被標記成“汙染的”。“汙染的”資料既不可以直接或間接在任何呼叫一個子shell命令中使用,也不能在任何修改檔案、目錄或程序的命令中使用,但有以下例外:
- •
- print和syswrite的引數不被檢查是否被汙染。
- •
- 符號方法
$obj->$method(@args);
以及符號的子引用&{$foo}(@args); $foo->(@args);
不會被檢查是否被汙染。這要求額外的小心,除非你希望外部資料影響你的控制流。除非你小心地限制這些符號值是什麼,人們可以從 Perl 程式碼外部呼叫函式,類似 POSIX::system,來執行任意外部程式碼。
$arg = shift; # $arg 是汙染的 $hid = $arg, 'bar'; # $hid 也是汙染的 $line = <>; # 汙染的 $line = <STDIN>; # 仍舊是汙染的 open FOO, "/home/me/bar" or die $!; $line = <FOO>; # 還是汙染的 $path = $ENV{'PATH'}; # 汙染的, 但是請看下面 $data = 'abc'; # 非汙染的
system "echo $arg"; # 不安全的 system "/bin/echo", $arg; # 認為不安全 # (Perl 不知道 /bin/echo) system "echo $hid"; # 不安全的 system "echo $data"; # 如果PATH被設定,那麼才是安全的
$path = $ENV{'PATH'}; # $path 現在是汙染的
$ENV{'PATH'} = '/bin:/usr/bin'; delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
$path = $ENV{'PATH'}; # $path 現在不是汙染的 system "echo $data"; # 現在是安全的!
open(FOO, "< $arg"); # OK - 只讀檔案 open(FOO, "> $arg"); # Not OK - 試圖去寫
open(FOO,"echo $arg⎪"); # Not OK open(FOO,"-⎪") or exec 'echo', $arg; # 同樣 not OK
$shout = `echo $arg`; # 不安全的, $shout 現在是汙染的
unlink $data, $arg; # 不安全的 umask $arg; # 不安全的
exec "echo $arg"; # 不安全的 exec "echo", $arg; # 不安全的 exec "sh", '-c', $arg; # 非常不安全!
@files = <*.c>; # 不安全的 (使用 readdir() 或其他) @files = glob('*.c'); # 不安全的 (使用 readdir() 或其他)
# In Perl releases older than 5.6.0 the <*.c> and glob('*.c') would # have used an external program to do the filename expansion; but in # either case the result is tainted since the list of filenames comes # from outside of the program.
$bad = ($arg, 23); # $bad will be tainted $arg, `true`; # Insecure (although it isn't really)如果你試圖做一些不安全的事情,你會得到類似"Insecure dependency"或"Insecure $ENV{PATH}"的致命錯誤。
sub is_tainted { return ! eval { eval("#" . substr(join("", @_), 0, 0)); 1 }; }此函式利用了“表示式中任何一部分存在的汙染資料致使整個表示式都被汙染”。操作員測試每個引數是否被汙染會使效率低下。相反,稍稍高效且穩定的方法是,只要一個表示式中任何一部分存取一個被汙染的值,那麼這個表示式被認為是被汙染的。 但是僅僅測試資料是否被汙染還不夠。有時你必須清除資料的汙染。唯一的透過汙染機制的方法是引用正則表示式中的一個子模式。Perl假定如果你用$1, $2等等引用一個子串,那麼你就知道你在做什麼。也就是說你必須思考而不是盲目的解除汙染,或者違抗整個機制。校驗變數是否只含有好的字元(已知的好的字元)比檢查它是否含有壞的字元要好。是因為很可能就把意料之外的壞字元漏掉。 下面的例子是一個檢查資料中是否只含有單詞(字母、數字、下劃線)、連字元、'@'符號或者是'.'。
if ($data =~ /^([-\@\w.]+)$/) { $data = $1; # $data now untainted } else { die "Bad data in '$data'"; # log this somewhere }這完全沒有問題,因為/1152/從理論上講會不安全,因為它匹配任何字元,而Perl將不再檢查它們。我們的經驗是當你解除汙染時,必須對匹配模式極其的小心。使用正則表示式清洗資料是解除汙染的唯一機制,除非你使用下面才詳細敘述的派生一個特權被降低的字程序的方法。 如果程式中使用了use locale,那麼上面的例子將不會解除$data的汙染,因為168[u914D]的字元是由locale決定的。Perl認為locale的定義是不可信的,因為它們包含程式之外 的資料。如果你在寫一個locale-aware的程式,並且想使用包含168[u6B63]則表示式清洗資料,那麼請在同一塊內的表示式之前加上no locale。參見perllocale/SECURITY以獲 得更多的資訊。
perl -Mlib=/foo programThe benefit of using "-Mlib=/foo" over "-I/foo", is that the former will automagically remove any duplicated directories, while the later will not.
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # 使 %ENV 更安全當然,無論是否使用汙染變數都有可能出現麻煩。在處理任何由使用者提供的檔名的檔案時,要做周密的測試。必須時,可以在去掉使用者(或組!)的特權之後再進行類似open的操作。Perl不阻止你開啟汙染的檔名並讀取內容,所以要小心對待打印出的內容。汙染機制的目的是防止愚蠢的錯誤,不是使人懶惰不去思考。 當你傳遞給system和exec明確的引數列表而非含有萬用字元的字串時,Perl不會呼叫shell去擴充套件萬用字元。不幸的是,open,glob,backtick(譯註:backtick為反引號)函式並不提供這樣的特性,所以當使用它們的時候必須非常仔細。 Perl為從一個setuid或setgid程式開啟檔案或管道提供了一個安全的方法:建立一個減少許可權的子程序來為你完成那些“骯髒”的工作。首先,用特殊的OPEN語法建立一個子程序,使其和父程序透過一個管道相連。現在子程序把它的ID和其他諸如環境變數,umask,當前工作目錄的性質重新設定回原始的或安全的變數。然後讓該不具有任何特權的子程序來完成OPEN和其他的系統呼叫。最終,子程序把它成功存取的資料傳遞給父程序。因為檔案或管道是由運行於比父程序許可權低的子程序開啟的,所以它不容易被欺騙去做它不該做的事情。 這裡有一個安全使用backtick的方法。注意當shell可能擴充套件時,exec是如何不被呼叫的。這是目前來呼叫可能被shell轉義的東西最好的方法:從不呼叫shell。
use English '-no_match_vars'; die "Can't fork: $!" unless defined($pid = open(KID, "-⎪")); if ($pid) { # parent while (<KID>) { # do something } close KID; } else { my @temp = ($EUID, $EGID); my $orig_uid = $UID; my $orig_gid = $GID; $EUID = $UID; $EGID = $GID; # Drop privileges $UID = $orig_uid; $GID = $orig_gid; # Make sure privs are really gone ($EUID, $EGID) = @temp; die "Can't drop privileges" unless $UID == $EUID && $GID eq $EGID; $ENV{PATH} = "/bin:/usr/bin"; # Minimal PATH. # Consider sanitizing the environment even more. exec 'myprog', 'arg1', 'arg2' or die "can't exec myprog: $!"; }
使用類似的策略可以讓glob使用萬用字元擴充套件,雖然也可以用readdir。 當你雖然相信自己並沒有寫有問題的程式,但並不信任程式的最終使用者不會企圖讓它做壞事時,汙染檢測最為有用。此類安全檢查對set-id和以其他使用者身份執行的程式(如CGI)非常有用。 若連程式的作者都不可信的話,情況就不同了。當某人給你一段程式並和你說,“給,試試看。”對於此類安全問題,使用包含在Perl發行版中的Safe模組。這個模組允許程式設計師建立特殊的隔間,在其中所有的系統呼叫都被截獲,並且名字空間入口被嚴格控制。
#define REAL_PATH "/path/to/script" main(ac, av) char **av; { execv(REAL_PATH, av); }把此C Wrapper編譯成可執行二進位制檔案,對它setuid或setgid而不是你的指令碼。 近幾年,軟體商開始提供沒有此安全問題的系統。在它們中,當核心把將要被開啟的set-id指令碼的名字傳遞給直譯器時,它將不會傳遞可能出現問題的路徑名而是傳遞/dev/fd/3。這是一個已經在指令碼上開啟的特殊檔案,所以將不會出現條件競爭問題。在這些系統中,Perl需要在編譯時帶上-DSETUID_SCRIPTS_ARE_SECURE_NOW引數。Configure程式將自己完成這個任務,所以你永遠不必要自己指出此點。現在SVR4和BSD4.4都採用此種方法來避免核心條件競爭。 在Perl 5.6.1 發行之前,suidperl的程式碼問題可能導致安全漏洞。
- •
- Hash Function - the algorithm used to "order" hash elements has been changed several times during the development of Perl, mainly to be reasonably fast. In Perl 5.8.1 also the security aspect was taken into account. In Perls before 5.8.1 one could rather easily generate data that as hash keys would cause Perl to consume large amounts of time because internal structure of hashes would badly degenerate. In Perl 5.8.1 the hash function is randomly perturbed by a pseudorandom seed which makes generating such naughty hash keys harder. See "PERL_HASH_SEED" in perlrun for more information. The random perturbation is done by default but if one wants for some reason emulate the old behaviour one can set the environment variable PERL_HASH_SEED to zero (or any other integer). One possible reason for wanting to emulate the old behaviour is that in the new behaviour consecutive runs of Perl will order hash keys differently, which may confuse some applications (like Data::Dumper: the outputs of two different runs are no more identical). Perl has never guaranteed any ordering of the hash keys, and the ordering has already changed several times during the lifetime of Perl 5. Also, the ordering of hash keys has always been, and continues to be, affected by the insertion order. Also note that while the order of the hash elements might be randomised, this "pseudoordering" should not be used for applications like shuffling a list randomly (use List::Util::shuffle() for that, see List::Util, a standard core module since Perl 5.8.0; or the CPAN module Algorithm::Numerical::Shuffle), or for generating permutations (use e.g. the CPAN modules Algorithm::Permute or Algorithm::FastPermute), or for any cryptographic applications.
- •
- Regular expressions - Perl's regular expression engine is so called NFA (Non-Finite Automaton), which among other things means that it can rather easily consume large amounts of both time and space if the regular expression may match in several ways. Careful crafting of the regular expressions can help but quite often there really isn't much one can do (the book "Mastering Regular Expressions" is required reading, see perlfaq2). Running out of space manifests itself by Perl running out of memory.
- •
- Sorting - the quicksort algorithm used in Perls before 5.8.0 to implement the sort() function is very easy to trick into misbehaving so that it consumes a lot of time. Nothing more is required than resorting a list already sorted. Starting from Perl 5.8.0 a different sorting algorithm, mergesort, is used. Mergesort is insensitive to its input data, so it cannot be similarly fooled.
SEE ALSO 參見
perlrun中關於清理環境變數的描述中文版維護人
nan1nan1 <[email protected]>中文版最新更新
2001年12月23日星期日中文手冊頁翻譯計劃
http://cmpp.linuxforum.net2003-11-25 | perl v5.8.3 |