| 
 
     
- UID
 - 1 
 - 威望
 - 1240 点 
 - 金钱
 - 24019 金币 
 - 点卡
 - 317 点 
 
  | 
1#
 
发表于 2005-8-24 14:15
 |  只看该作者
 
 
 
 Perl的安全性监测
作者:icewater 
日期:2000-11-22 16:27:55 
Perl的安全性监测  
作者: Nathan Torkington  
日期: 2000年04月26日  
from www.zdnet.com.cn) 
<编者按:不要让 CGI 担上“世界上最流行的安全漏洞”的名声。 Nathan将教给你如何使用Perl的内部安全机制。> 
本月的专栏将介绍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 (文件::KGlob 和文件::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和其他有趣的用法 
  
  
   
  
  |   
 
 
 
 
                     我是一个呼吸着现在的空气而生活在过去的人 
               这样的注定孤独,孤独的身处闹市却犹如置身于荒漠 
                                     我已习惯了孤独,爱上孤独 
                                 他让我看清了自我,还原了自我 
                             让我再静静的沉思中得到快乐和满足 
                                   再孤独的世界里我一遍又一遍 
                                   不厌其烦的改写着自己的过去 
                                             延伸到现在与未来 
                                       然而那只是泡沫般的美梦 
                                 产生的时刻又伴随着破灭的到来 
                         在灰飞烟灭的瞬间我看到的是过程的美丽 
                                      而不是结果的悲哀。。。 
 | 
 
 
 
 |